2 * ui-grid - v4.0.2 - 2016-12-30
3 * Copyright (c) 2016 ; License: MIT
8 angular.module('ui.grid.i18n', []);
9 angular.module('ui.grid', ['ui.grid.i18n']);
16 * @name ui.grid.service:uiGridConstants
17 * @description Constants for use across many grid features
22 angular.module('ui.grid').constant('uiGridConstants', {
23 LOG_DEBUG_MESSAGES: true,
24 LOG_WARN_MESSAGES: true,
25 LOG_ERROR_MESSAGES: true,
26 CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
27 COL_FIELD: /COL_FIELD/g,
28 MODEL_COL_FIELD: /MODEL_COL_FIELD/g,
29 TOOLTIP: /title=\"TOOLTIP\"/g,
30 DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
31 TEMPLATE_REGEXP: /<.+>/,
32 FUNC_REGEXP: /(\([^)]*\))?$/,
35 BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
36 COL_CLASS_PREFIX: 'ui-grid-col',
37 ENTITY_BINDING: '$$this',
39 GRID_SCROLL: 'uiGridScroll',
40 COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
41 ITEM_DRAGGING: 'uiGridItemDragStart', // For any item being dragged
42 COLUMN_HEADER_CLICK: 'uiGridColumnHeaderClick'
44 // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
88 * @propertyOf ui.grid.service:uiGridConstants
89 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
90 * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
91 * to configure the sorting direction of the column
97 * @propertyOf ui.grid.service:uiGridConstants
98 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
99 * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
100 * to configure the sorting direction of the column
108 * @propertyOf ui.grid.service:uiGridConstants
109 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_filter columnDef.filter}
110 * to configure filtering on the column
112 * `SELECT` and `INPUT` are used with the `type` property of the filter, the rest are used to specify
113 * one of the built-in conditions.
115 * Available `condition` options are:
116 * - `uiGridConstants.filter.STARTS_WITH`
117 * - `uiGridConstants.filter.ENDS_WITH`
118 * - `uiGridConstants.filter.CONTAINS`
119 * - `uiGridConstants.filter.GREATER_THAN`
120 * - `uiGridConstants.filter.GREATER_THAN_OR_EQUAL`
121 * - `uiGridConstants.filter.LESS_THAN`
122 * - `uiGridConstants.filter.LESS_THAN_OR_EQUAL`
123 * - `uiGridConstants.filter.NOT_EQUAL`
124 * - `uiGridConstants.filter.STARTS_WITH`
127 * Available `type` options are:
128 * - `uiGridConstants.filter.SELECT` - use a dropdown box for the cell header filter field
129 * - `uiGridConstants.filter.INPUT` - use a text box for the cell header filter field
137 GREATER_THAN_OR_EQUAL: 64,
139 LESS_THAN_OR_EQUAL: 256,
147 * @name aggregationTypes
148 * @propertyOf ui.grid.service:uiGridConstants
149 * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_aggregationType columnDef.aggregationType}
150 * to specify the type of built-in aggregation the column should use.
152 * Available options are:
153 * - `uiGridConstants.aggregationTypes.sum` - add the values in this column to produce the aggregated value
154 * - `uiGridConstants.aggregationTypes.count` - count the number of rows to produce the aggregated value
155 * - `uiGridConstants.aggregationTypes.avg` - average the values in this column to produce the aggregated value
156 * - `uiGridConstants.aggregationTypes.min` - use the minimum value in this column as the aggregated value
157 * - `uiGridConstants.aggregationTypes.max` - use the maximum value in this column as the aggregated value
169 * @name CURRENCY_SYMBOLS
170 * @propertyOf ui.grid.service:uiGridConstants
171 * @description A list of all presently circulating currency symbols that was copied from
172 * https://en.wikipedia.org/wiki/Currency_symbol#List_of_presently-circulating_currency_symbols
174 * Can be used on {@link ui.grid.class:rowSorter} to create a number string regex that ignores currency symbols.
176 CURRENCY_SYMBOLS: ['¤', '؋', 'Ar', 'Ƀ', '฿', 'B/.', 'Br', 'Bs.', 'Bs.F.', 'GH₵', '¢', 'c', 'Ch.', '₡', 'C$', 'D', 'ден',
177 'دج', '.د.ب', 'د.ع', 'JD', 'د.ك', 'ل.د', 'дин', 'د.ت', 'د.م.', 'د.إ', 'Db', '$', '₫', 'Esc', '€', 'ƒ', 'Ft', 'FBu',
178 'FCFA', 'CFA', 'Fr', 'FRw', 'G', 'gr', '₲', 'h', '₴', '₭', 'Kč', 'kr', 'kn', 'MK', 'ZK', 'Kz', 'K', 'L', 'Le', 'лв',
179 'E', 'lp', 'M', 'KM', 'MT', '₥', 'Nfk', '₦', 'Nu.', 'UM', 'T$', 'MOP$', '₱', 'Pt.', '£', 'ج.م.', 'LL', 'LS', 'P', 'Q',
180 'q', 'R', 'R$', 'ر.ع.', 'ر.ق', 'ر.س', '៛', 'RM', 'p', 'Rf.', '₹', '₨', 'SRe', 'Rp', '₪', 'Ksh', 'Sh.So.', 'USh', 'S/',
181 'SDR', 'сом', '৳ ', 'WS$', '₮', 'VT', '₩', '¥', 'zł'],
185 * @name scrollDirection
186 * @propertyOf ui.grid.service:uiGridConstants
187 * @description Set on {@link ui.grid.class:Grid#properties_scrollDirection Grid.scrollDirection},
188 * to indicate the direction the grid is currently scrolling in
190 * Available options are:
191 * - `uiGridConstants.scrollDirection.UP` - set when the grid is scrolling up
192 * - `uiGridConstants.scrollDirection.DOWN` - set when the grid is scrolling down
193 * - `uiGridConstants.scrollDirection.LEFT` - set when the grid is scrolling left
194 * - `uiGridConstants.scrollDirection.RIGHT` - set when the grid is scrolling right
195 * - `uiGridConstants.scrollDirection.NONE` - set when the grid is not scrolling, this is the default
209 * @propertyOf ui.grid.service:uiGridConstants
210 * @description Used with {@link ui.grid.core.api:PublicApi#methods_notifyDataChange PublicApi.notifyDataChange},
211 * {@link ui.grid.class:Grid#methods_callDataChangeCallbacks Grid.callDataChangeCallbacks},
212 * and {@link ui.grid.class:Grid#methods_registerDataChangeCallback Grid.registerDataChangeCallback}
213 * to specify the type of the event(s).
215 * Available options are:
216 * - `uiGridConstants.dataChange.ALL` - listeners fired on any of these events, fires listeners on all events.
217 * - `uiGridConstants.dataChange.EDIT` - fired when the data in a cell is edited
218 * - `uiGridConstants.dataChange.ROW` - fired when a row is added or removed
219 * - `uiGridConstants.dataChange.COLUMN` - fired when the column definitions are modified
220 * - `uiGridConstants.dataChange.OPTIONS` - fired when the grid options are modified
233 * @propertyOf ui.grid.service:uiGridConstants
234 * @description Used with {@link ui.grid.class:GridOptions#properties_enableHorizontalScrollbar GridOptions.enableHorizontalScrollbar}
235 * and {@link ui.grid.class:GridOptions#properties_enableVerticalScrollbar GridOptions.enableVerticalScrollbar}
236 * to specify the scrollbar policy for that direction.
238 * Available options are:
239 * - `uiGridConstants.scrollbars.NEVER` - never show scrollbars in this direction
240 * - `uiGridConstants.scrollbars.ALWAYS` - always show scrollbars in this direction
252 angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
257 compile: function() {
259 pre: function($scope, $elm, $attrs, uiGridCtrl) {
260 function compileTemplate() {
261 var compiledElementFn = $scope.col.compiledElementFn;
263 compiledElementFn($scope, function(clonedElement, scope) {
264 $elm.append(clonedElement);
268 // If the grid controller is present, use it to get the compiled cell template function
269 if (uiGridCtrl && $scope.col.compiledElementFn) {
272 // No controller, compile the element manually (for unit tests)
274 if ( uiGridCtrl && !$scope.col.compiledElementFn ){
275 // gridUtil.logError('Render has been called before pronapile. Please log a ui-grid issue');
277 $scope.col.getCompiledElementFn()
278 .then(function (compiledElementFn) {
279 compiledElementFn($scope, function(clonedElement, scope) {
280 $elm.append(clonedElement);
285 var html = $scope.col.cellTemplate
286 .replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
287 .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
289 var cellElement = $compile(html)($scope);
290 $elm.append(cellElement);
294 post: function($scope, $elm, $attrs, uiGridCtrl) {
295 var initColClass = $scope.col.getColClass(false);
296 $elm.addClass(initColClass);
299 var updateClass = function( grid ){
302 contents.removeClass( classAdded );
306 if (angular.isFunction($scope.col.cellClass)) {
307 classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
310 classAdded = $scope.col.cellClass;
312 contents.addClass(classAdded);
315 if ($scope.col.cellClass) {
319 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
320 var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
322 // watch the col and row to see if they change - which would indicate that we've scrolled or sorted or otherwise
323 // changed the row/col that this cell relates to, and we need to re-evaluate cell classes and maybe other things
324 var cellChangeFunction = function( n, o ){
326 if ( classAdded || $scope.col.cellClass ){
330 // See if the column's internal class has changed
331 var newColClass = $scope.col.getColClass(false);
332 if (newColClass !== initColClass) {
333 $elm.removeClass(initColClass);
334 $elm.addClass(newColClass);
335 initColClass = newColClass;
340 // TODO(c0bra): Turn this into a deep array watch
341 /* shouldn't be needed any more given track by col.name
342 var colWatchDereg = $scope.$watch( 'col', cellChangeFunction );
344 var rowWatchDereg = $scope.$watch( 'row', cellChangeFunction );
347 var deregisterFunction = function() {
353 $scope.$on( '$destroy', deregisterFunction );
354 $elm.on( '$destroy', deregisterFunction );
366 angular.module('ui.grid')
367 .service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil',
368 function ( i18nService, uiGridConstants, gridUtil ) {
371 * @name ui.grid.service:uiGridColumnMenuService
373 * @description Services for working with column menus, factored out
374 * to make the code easier to understand
380 * @methodOf ui.grid.service:uiGridColumnMenuService
382 * @description Sets defaults, puts a reference to the $scope on
383 * the uiGridController
384 * @param {$scope} $scope the $scope from the uiGridColumnMenu
385 * @param {controller} uiGridCtrl the uiGridController for the grid
389 initialize: function( $scope, uiGridCtrl ){
390 $scope.grid = uiGridCtrl.grid;
392 // Store a reference to this link/controller in the main uiGrid controller
393 // to allow showMenu later
394 uiGridCtrl.columnMenuScope = $scope;
396 // Save whether we're shown or not so the columns can check
397 $scope.menuShown = false;
403 * @methodOf ui.grid.service:uiGridColumnMenuService
404 * @name setColMenuItemWatch
405 * @description Setup a watch on $scope.col.menuItems, and update
406 * menuItems based on this. $scope.col needs to be set by the column
407 * before calling the menu.
408 * @param {$scope} $scope the $scope from the uiGridColumnMenu
409 * @param {controller} uiGridCtrl the uiGridController for the grid
413 setColMenuItemWatch: function ( $scope ){
414 var deregFunction = $scope.$watch('col.menuItems', function (n) {
415 if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
416 n.forEach(function (item) {
417 if (typeof(item.context) === 'undefined' || !item.context) {
420 item.context.col = $scope.col;
423 $scope.menuItems = $scope.defaultMenuItems.concat(n);
426 $scope.menuItems = $scope.defaultMenuItems;
430 $scope.$on( '$destroy', deregFunction );
436 * @name enableSorting
437 * @propertyOf ui.grid.class:GridOptions.columnDef
438 * @description (optional) True by default. When enabled, this setting adds sort
439 * widgets to the column header, allowing sorting of the data in the individual column.
443 * @methodOf ui.grid.service:uiGridColumnMenuService
445 * @description determines whether this column is sortable
446 * @param {$scope} $scope the $scope from the uiGridColumnMenu
449 sortable: function( $scope ) {
450 if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
460 * @methodOf ui.grid.service:uiGridColumnMenuService
462 * @description determines whether the requested sort direction is current active, to
463 * allow highlighting in the menu
464 * @param {$scope} $scope the $scope from the uiGridColumnMenu
465 * @param {string} direction the direction that we'd have selected for us to be active
468 isActiveSort: function( $scope, direction ){
469 return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
470 typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
476 * @methodOf ui.grid.service:uiGridColumnMenuService
477 * @name suppressRemoveSort
478 * @description determines whether we should suppress the removeSort option
479 * @param {$scope} $scope the $scope from the uiGridColumnMenu
482 suppressRemoveSort: function( $scope ) {
483 if ($scope.col && $scope.col.suppressRemoveSort) {
495 * @propertyOf ui.grid.class:GridOptions.columnDef
496 * @description (optional) True by default. When set to false, this setting prevents a user from hiding the column
497 * using the column menu or the grid menu.
501 * @methodOf ui.grid.service:uiGridColumnMenuService
503 * @description determines whether a column can be hidden, by checking the enableHiding columnDef option
504 * @param {$scope} $scope the $scope from the uiGridColumnMenu
507 hideable: function( $scope ) {
508 if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
519 * @methodOf ui.grid.service:uiGridColumnMenuService
520 * @name getDefaultMenuItems
521 * @description returns the default menu items for a column menu
522 * @param {$scope} $scope the $scope from the uiGridColumnMenu
525 getDefaultMenuItems: function( $scope ){
528 title: i18nService.getSafeText('sort.ascending'),
529 icon: 'ui-grid-icon-sort-alt-up',
530 action: function($event) {
531 $event.stopPropagation();
532 $scope.sortColumn($event, uiGridConstants.ASC);
535 return service.sortable( $scope );
538 return service.isActiveSort( $scope, uiGridConstants.ASC);
542 title: i18nService.getSafeText('sort.descending'),
543 icon: 'ui-grid-icon-sort-alt-down',
544 action: function($event) {
545 $event.stopPropagation();
546 $scope.sortColumn($event, uiGridConstants.DESC);
549 return service.sortable( $scope );
552 return service.isActiveSort( $scope, uiGridConstants.DESC);
556 title: i18nService.getSafeText('sort.remove'),
557 icon: 'ui-grid-icon-cancel',
558 action: function ($event) {
559 $event.stopPropagation();
560 $scope.unsortColumn();
563 return service.sortable( $scope ) &&
564 typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' &&
565 typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null &&
566 !service.suppressRemoveSort( $scope );
570 title: i18nService.getSafeText('column.hide'),
571 icon: 'ui-grid-icon-cancel',
573 return service.hideable( $scope );
575 action: function ($event) {
576 $event.stopPropagation();
586 * @methodOf ui.grid.service:uiGridColumnMenuService
587 * @name getColumnElementPosition
588 * @description gets the position information needed to place the column
589 * menu below the column header
590 * @param {$scope} $scope the $scope from the uiGridColumnMenu
591 * @param {GridCol} column the column we want to position below
592 * @param {element} $columnElement the column element we want to position below
593 * @returns {hash} containing left, top, offset, height, width
596 getColumnElementPosition: function( $scope, column, $columnElement ){
597 var positionData = {};
598 positionData.left = $columnElement[0].offsetLeft;
599 positionData.top = $columnElement[0].offsetTop;
600 positionData.parentLeft = $columnElement[0].offsetParent.offsetLeft;
602 // Get the grid scrollLeft
603 positionData.offset = 0;
604 if (column.grid.options.offsetLeft) {
605 positionData.offset = column.grid.options.offsetLeft;
608 positionData.height = gridUtil.elementHeight($columnElement, true);
609 positionData.width = gridUtil.elementWidth($columnElement, true);
617 * @methodOf ui.grid.service:uiGridColumnMenuService
618 * @name repositionMenu
619 * @description Reposition the menu below the new column. If the menu has no child nodes
620 * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
622 * @param {$scope} $scope the $scope from the uiGridColumnMenu
623 * @param {GridCol} column the column we want to position below
624 * @param {hash} positionData a hash containing left, top, offset, height, width
625 * @param {element} $elm the column menu element that we want to reposition
626 * @param {element} $columnElement the column element that we want to reposition underneath
629 repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
630 var menu = $elm[0].querySelectorAll('.ui-grid-menu');
632 // It's possible that the render container of the column we're attaching to is
633 // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
634 // between the render container and the grid
635 var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
636 var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
638 var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
640 // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
641 var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
642 var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
644 if ( menu.length !== 0 ){
645 var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
646 if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
647 myWidth = gridUtil.elementWidth(menu, true);
648 $scope.lastMenuWidth = myWidth;
649 column.lastMenuWidth = myWidth;
651 // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
652 // Get the column menu right padding
653 paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
654 $scope.lastMenuPaddingRight = paddingRight;
655 column.lastMenuPaddingRight = paddingRight;
659 var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight;
660 if (left < positionData.offset){
661 left = positionData.offset;
664 $elm.css('left', left + 'px');
665 $elm.css('top', (positionData.top + positionData.height) + 'px');
674 .directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', '$document',
675 function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $document) {
678 * @name ui.grid.directive:uiGridColumnMenu
679 * @description Provides the column menu framework, leverages uiGridMenu underneath
683 var uiGridColumnMenu = {
687 templateUrl: 'ui-grid/uiGridColumnMenu',
689 link: function ($scope, $elm, $attrs, uiGridCtrl) {
690 uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
692 $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
694 // Set the menu items for use with the column menu. The user can later add additional items via the watch
695 $scope.menuItems = $scope.defaultMenuItems;
696 uiGridColumnMenuService.setColMenuItemWatch( $scope );
701 * @methodOf ui.grid.directive:uiGridColumnMenu
703 * @description Shows the column menu. If the menu is already displayed it
704 * calls the menu to ask it to hide (it will animate), then it repositions the menu
705 * to the right place whilst hidden (it will make an assumption on menu width),
706 * then it asks the menu to show (it will animate), then it repositions the menu again
707 * once we can calculate it's size.
708 * @param {GridCol} column the column we want to position below
709 * @param {element} $columnElement the column element we want to position below
711 $scope.showMenu = function(column, $columnElement, event) {
712 // Swap to this column
715 // Get the position information for the column element
716 var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
718 if ($scope.menuShown) {
719 // we want to hide, then reposition, then show, but we want to wait for animations
720 // we set a variable, and then rely on the menu-hidden event to call the reposition and show
721 $scope.colElement = $columnElement;
722 $scope.colElementPosition = colElementPosition;
723 $scope.hideThenShow = true;
725 $scope.$broadcast('hide-menu', { originalEvent: event });
727 $scope.menuShown = true;
728 uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
730 $scope.colElement = $columnElement;
731 $scope.colElementPosition = colElementPosition;
732 $scope.$broadcast('show-menu', { originalEvent: event });
740 * @methodOf ui.grid.directive:uiGridColumnMenu
742 * @description Hides the column menu.
743 * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
744 * from the menu itself - in which case don't broadcast again as we'll get
747 $scope.hideMenu = function( broadcastTrigger ) {
748 $scope.menuShown = false;
749 if ( !broadcastTrigger ){
750 $scope.$broadcast('hide-menu');
755 $scope.$on('menu-hidden', function() {
756 if ( $scope.hideThenShow ){
757 delete $scope.hideThenShow;
759 uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
760 $scope.$broadcast('show-menu');
762 $scope.menuShown = true;
764 $scope.hideMenu( true );
767 //Focus on the menu button
768 gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
773 $scope.$on('menu-shown', function() {
774 $timeout( function() {
775 uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
776 //Focus on the first item
777 gridUtil.focus.bySelector($document, '.ui-grid-menu-items .ui-grid-menu-item', true);
778 delete $scope.colElementPosition;
779 delete $scope.columnElement;
785 $scope.sortColumn = function (event, dir) {
786 event.stopPropagation();
788 $scope.grid.sortColumn($scope.col, dir, true)
790 $scope.grid.refresh();
795 $scope.unsortColumn = function () {
798 $scope.grid.refresh();
802 //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
803 var setFocusOnHideColumn = function(){
805 // Get the UID of the first
806 var focusToGridMenu = function(){
807 return gridUtil.focus.byId('grid-menu', $scope.grid);
811 $scope.grid.columns.some(function(element, index){
812 if (angular.equals(element, $scope.col)) {
818 var previousVisibleCol;
819 // Try and find the next lower or nearest column to focus on
820 $scope.grid.columns.some(function(element, index){
821 if (!element.visible){
823 } // This columns index is below the current column index
824 else if ( index < thisIndex){
825 previousVisibleCol = element;
826 } // This elements index is above this column index and we haven't found one that is lower
827 else if ( index > thisIndex && !previousVisibleCol) {
828 // This is the next best thing
829 previousVisibleCol = element;
830 // We've found one so use it.
832 } // We've reached an element with an index above this column and the previousVisibleCol variable has been set
833 else if (index > thisIndex && previousVisibleCol) {
838 // If found then focus on it
839 if (previousVisibleCol){
840 var colClass = previousVisibleCol.getColClass();
841 gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
842 if (reason !== 'canceled'){ // If this is canceled then don't perform the action
843 //The fallback action is to focus on the grid menu
844 return focusToGridMenu();
848 // Fallback action to focus on the grid menu
854 $scope.hideColumn = function () {
855 $scope.col.colDef.visible = false;
856 $scope.col.visible = false;
858 $scope.grid.queueGridRefresh();
860 $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
861 $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col );
863 // We are hiding so the default action of focusing on the button that opened this menu will fail.
864 setFocusOnHideColumn();
870 controller: ['$scope', function ($scope) {
873 $scope.$watch('menuItems', function (n) {
879 return uiGridColumnMenu;
888 angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
891 compile: function() {
893 pre: function ($scope, $elm, $attrs, controllers) {
894 $scope.col.updateFilters = function( filterable ){
895 $elm.children().remove();
897 var template = $scope.col.filterHeaderTemplate;
899 $elm.append($compile(template)($scope));
903 $scope.$on( '$destroy', function() {
904 delete $scope.col.updateFilters;
907 post: function ($scope, $elm, $attrs, controllers){
908 $scope.aria = i18nService.getSafeText('headerCell.aria');
909 $scope.removeFilter = function(colFilter, index){
910 colFilter.term = null;
911 //Set the focus to the filter input after the action disables the button
912 gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
924 angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
925 function ($timeout, gridUtil, uiGridConstants, $compile) {
926 var uiGridFooterCell = {
935 compile: function compile(tElement, tAttrs, transclude) {
937 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
938 var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
939 $elm.append(cellFooter);
941 post: function ($scope, $elm, $attrs, uiGridCtrl) {
942 //$elm.addClass($scope.col.getColClass(false));
943 $scope.grid = uiGridCtrl.grid;
945 var initColClass = $scope.col.getColClass(false);
946 $elm.addClass(initColClass);
948 // apply any footerCellClass
950 var updateClass = function( grid ){
953 contents.removeClass( classAdded );
957 if (angular.isFunction($scope.col.footerCellClass)) {
958 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
961 classAdded = $scope.col.footerCellClass;
963 contents.addClass(classAdded);
966 if ($scope.col.footerCellClass) {
970 $scope.col.updateAggregationValue();
972 // Watch for column changes so we can alter the col cell class properly
973 /* shouldn't be needed any more, given track by col.name
974 $scope.$watch('col', function (n, o) {
976 // See if the column's internal class has changed
977 var newColClass = $scope.col.getColClass(false);
978 if (newColClass !== initColClass) {
979 $elm.removeClass(initColClass);
980 $elm.addClass(newColClass);
981 initColClass = newColClass;
988 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
989 var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
990 // listen for visible rows change and update aggregation values
991 $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
992 $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
993 $scope.$on( '$destroy', dataChangeDereg );
999 return uiGridFooterCell;
1007 angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
1013 require: ['^uiGrid', '^uiGridRenderContainer'],
1015 compile: function ($elm, $attrs) {
1017 pre: function ($scope, $elm, $attrs, controllers) {
1018 var uiGridCtrl = controllers[0];
1019 var containerCtrl = controllers[1];
1021 $scope.grid = uiGridCtrl.grid;
1022 $scope.colContainer = containerCtrl.colContainer;
1024 containerCtrl.footer = $elm;
1026 var footerTemplate = $scope.grid.options.footerTemplate;
1027 gridUtil.getTemplate(footerTemplate)
1028 .then(function (contents) {
1029 var template = angular.element(contents);
1031 var newElm = $compile(template)($scope);
1032 $elm.append(newElm);
1034 if (containerCtrl) {
1035 // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1036 var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
1038 if (footerViewport) {
1039 containerCtrl.footerViewport = footerViewport;
1045 post: function ($scope, $elm, $attrs, controllers) {
1046 var uiGridCtrl = controllers[0];
1047 var containerCtrl = controllers[1];
1049 // gridUtil.logDebug('ui-grid-footer link');
1051 var grid = uiGridCtrl.grid;
1053 // Don't animate footer cells
1054 gridUtil.disableAnimations($elm);
1056 containerCtrl.footer = $elm;
1058 var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
1059 if (footerViewport) {
1060 containerCtrl.footerViewport = footerViewport;
1072 angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
1080 compile: function ($elm, $attrs) {
1082 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1084 $scope.grid = uiGridCtrl.grid;
1088 var footerTemplate = $scope.grid.options.gridFooterTemplate;
1089 gridUtil.getTemplate(footerTemplate)
1090 .then(function (contents) {
1091 var template = angular.element(contents);
1093 var newElm = $compile(template)($scope);
1094 $elm.append(newElm);
1098 post: function ($scope, $elm, $attrs, controllers) {
1110 angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
1111 var defaultTemplate = 'ui-grid/ui-grid-group-panel';
1116 require: '?^uiGrid',
1118 compile: function($elm, $attrs) {
1120 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
1121 var groupPanelTemplate = $scope.grid.options.groupPanelTemplate || defaultTemplate;
1123 gridUtil.getTemplate(groupPanelTemplate)
1124 .then(function (contents) {
1125 var template = angular.element(contents);
1127 var newElm = $compile(template)($scope);
1128 $elm.append(newElm);
1132 post: function ($scope, $elm, $attrs, uiGridCtrl) {
1133 $elm.bind('$destroy', function() {
1134 // scrollUnbinder();
1146 angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
1147 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
1148 // Do stuff after mouse has been down this many ms on the header cell
1149 var mousedownTimeout = 500;
1150 var changeModeTimeout = 500; // length of time between a touch event and a mouse event being recognised again, and vice versa
1152 var uiGridHeaderCell = {
1159 require: ['^uiGrid', '^uiGridRenderContainer'],
1161 compile: function() {
1163 pre: function ($scope, $elm, $attrs) {
1164 var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
1165 $elm.append(cellHeader);
1168 post: function ($scope, $elm, $attrs, controllers) {
1169 var uiGridCtrl = controllers[0];
1170 var renderContainerCtrl = controllers[1];
1173 headerCell: i18nService.getSafeText('headerCell'),
1174 sort: i18nService.getSafeText('sort')
1176 $scope.isSortPriorityVisible = function() {
1177 //show sort priority if column is sorted and there is at least one other sorted column
1178 return angular.isNumber($scope.col.sort.priority) && $scope.grid.columns.some(function(element, index){
1179 return angular.isNumber(element.sort.priority) && element !== $scope.col;
1182 $scope.getSortDirectionAriaLabel = function(){
1183 var col = $scope.col;
1184 //Trying to recreate this sort of thing but it was getting messy having it in the template.
1185 //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
1186 var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
1187 var label = sortDirectionText;
1189 if ($scope.isSortPriorityVisible()) {
1190 label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
1195 $scope.grid = uiGridCtrl.grid;
1197 $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
1199 var initColClass = $scope.col.getColClass(false);
1200 $elm.addClass(initColClass);
1202 // Hide the menu by default
1203 $scope.menuShown = false;
1205 // Put asc and desc sort directions in scope
1206 $scope.asc = uiGridConstants.ASC;
1207 $scope.desc = uiGridConstants.DESC;
1209 // Store a reference to menu element
1210 var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
1212 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
1215 // apply any headerCellClass
1220 var filterDeregisters = [];
1224 * Our basic approach here for event handlers is that we listen for a down event (mousedown or touchstart).
1225 * Once we have a down event, we need to work out whether we have a click, a drag, or a
1226 * hold. A click would sort the grid (if sortable). A drag would be used by moveable, so
1227 * we ignore it. A hold would open the menu.
1229 * So, on down event, we put in place handlers for move and up events, and a timer. If the
1230 * timer expires before we see a move or up, then we have a long press and hence a column menu open.
1231 * If the up happens before the timer, then we have a click, and we sort if the column is sortable.
1232 * If a move happens before the timer, then we are doing column move, so we do nothing, the moveable feature
1235 * To deal with touch enabled devices that also have mice, we only create our handlers when
1236 * we get the down event, and we create the corresponding handlers - if we're touchstart then
1237 * we get touchmove and touchend, if we're mousedown then we get mousemove and mouseup.
1239 * We also suppress the click action whilst this is happening - otherwise after the mouseup there
1240 * will be a click event and that can cause the column menu to close
1244 $scope.downFn = function( event ){
1245 event.stopPropagation();
1247 if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
1248 event = event.originalEvent;
1251 // Don't show the menu if it's not the left button
1252 if (event.button && event.button !== 0) {
1255 previousMouseX = event.pageX;
1257 $scope.mousedownStartTime = (new Date()).getTime();
1258 $scope.mousedownTimeout = $timeout(function() { }, mousedownTimeout);
1260 $scope.mousedownTimeout.then(function () {
1261 if ( $scope.colMenu ) {
1262 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
1266 uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
1268 $scope.offAllEvents();
1269 if ( event.type === 'touchstart'){
1270 $document.on('touchend', $scope.upFn);
1271 $document.on('touchmove', $scope.moveFn);
1272 } else if ( event.type === 'mousedown' ){
1273 $document.on('mouseup', $scope.upFn);
1274 $document.on('mousemove', $scope.moveFn);
1278 $scope.upFn = function( event ){
1279 event.stopPropagation();
1280 $timeout.cancel($scope.mousedownTimeout);
1281 $scope.offAllEvents();
1282 $scope.onDownEvents(event.type);
1284 var mousedownEndTime = (new Date()).getTime();
1285 var mousedownTime = mousedownEndTime - $scope.mousedownStartTime;
1287 if (mousedownTime > mousedownTimeout) {
1288 // long click, handled above with mousedown
1292 if ( $scope.sortable ){
1293 $scope.handleClick(event);
1298 $scope.moveFn = function( event ){
1299 // Chrome is known to fire some bogus move events.
1300 var changeValue = event.pageX - previousMouseX;
1301 if ( changeValue === 0 ){ return; }
1303 // we're a move, so do nothing and leave for column move (if enabled) to take over
1304 $timeout.cancel($scope.mousedownTimeout);
1305 $scope.offAllEvents();
1306 $scope.onDownEvents(event.type);
1309 $scope.clickFn = function ( event ){
1310 event.stopPropagation();
1311 $contentsElm.off('click', $scope.clickFn);
1315 $scope.offAllEvents = function(){
1316 $contentsElm.off('touchstart', $scope.downFn);
1317 $contentsElm.off('mousedown', $scope.downFn);
1319 $document.off('touchend', $scope.upFn);
1320 $document.off('mouseup', $scope.upFn);
1322 $document.off('touchmove', $scope.moveFn);
1323 $document.off('mousemove', $scope.moveFn);
1325 $contentsElm.off('click', $scope.clickFn);
1328 $scope.onDownEvents = function( type ){
1329 // If there is a previous event, then wait a while before
1330 // activating the other mode - i.e. if the last event was a touch event then
1331 // don't enable mouse events for a wee while (500ms or so)
1332 // Avoids problems with devices that emulate mouse events when you have touch events
1337 $contentsElm.on('click', $scope.clickFn);
1338 $contentsElm.on('touchstart', $scope.downFn);
1339 $timeout(function(){
1340 $contentsElm.on('mousedown', $scope.downFn);
1341 }, changeModeTimeout);
1345 $contentsElm.on('click', $scope.clickFn);
1346 $contentsElm.on('mousedown', $scope.downFn);
1347 $timeout(function(){
1348 $contentsElm.on('touchstart', $scope.downFn);
1349 }, changeModeTimeout);
1352 $contentsElm.on('click', $scope.clickFn);
1353 $contentsElm.on('touchstart', $scope.downFn);
1354 $contentsElm.on('mousedown', $scope.downFn);
1359 var updateHeaderOptions = function( grid ){
1360 var contents = $elm;
1362 contents.removeClass( classAdded );
1366 if (angular.isFunction($scope.col.headerCellClass)) {
1367 classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
1370 classAdded = $scope.col.headerCellClass;
1372 contents.addClass(classAdded);
1374 $timeout(function (){
1375 var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
1376 $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
1379 // Figure out whether this column is sortable or not
1380 if ($scope.col.enableSorting) {
1381 $scope.sortable = true;
1384 $scope.sortable = false;
1387 // Figure out whether this column is filterable or not
1388 var oldFilterable = $scope.filterable;
1389 if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
1390 $scope.filterable = true;
1393 $scope.filterable = false;
1396 if ( oldFilterable !== $scope.filterable){
1397 if ( typeof($scope.col.updateFilters) !== 'undefined' ){
1398 $scope.col.updateFilters($scope.filterable);
1401 // if column is filterable add a filter watcher
1402 if ($scope.filterable) {
1403 $scope.col.filters.forEach( function(filter, i) {
1404 filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
1406 uiGridCtrl.grid.api.core.raise.filterChanged();
1407 uiGridCtrl.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
1408 uiGridCtrl.grid.queueGridRefresh();
1412 $scope.$on('$destroy', function() {
1413 filterDeregisters.forEach( function(filterDeregister) {
1418 filterDeregisters.forEach( function(filterDeregister) {
1425 // figure out whether we support column menus
1426 if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
1427 $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
1428 $scope.colMenu = true;
1430 $scope.colMenu = false;
1435 * @name enableColumnMenu
1436 * @propertyOf ui.grid.class:GridOptions.columnDef
1437 * @description if column menus are enabled, controls the column menus for this specific
1438 * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
1439 * using this option. If gridOptions.enableColumnMenus === false then you get no column
1440 * menus irrespective of the value of this option ). Defaults to true.
1445 * @name enableColumnMenus
1446 * @propertyOf ui.grid.class:GridOptions.columnDef
1447 * @description Override for column menus everywhere - if set to false then you get no
1448 * column menus. Defaults to true.
1452 $scope.offAllEvents();
1454 if ($scope.sortable || $scope.colMenu) {
1455 $scope.onDownEvents();
1457 $scope.$on('$destroy', function () {
1458 $scope.offAllEvents();
1464 $scope.$watch('col', function (n, o) {
1466 // See if the column's internal class has changed
1467 var newColClass = $scope.col.getColClass(false);
1468 if (newColClass !== initColClass) {
1469 $elm.removeClass(initColClass);
1470 $elm.addClass(newColClass);
1471 initColClass = newColClass;
1476 updateHeaderOptions();
1478 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
1479 var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateHeaderOptions, [uiGridConstants.dataChange.COLUMN]);
1481 $scope.$on( '$destroy', dataChangeDereg );
1483 $scope.handleClick = function(event) {
1484 // If the shift key is being held down, add this column to the sort
1486 if (event.shiftKey) {
1490 // Sort this column then rebuild the grid's rows
1491 uiGridCtrl.grid.sortColumn($scope.col, add)
1493 if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
1494 uiGridCtrl.grid.refresh();
1499 $scope.toggleMenu = function(event) {
1500 event.stopPropagation();
1502 // If the menu is already showing...
1503 if (uiGridCtrl.columnMenuScope.menuShown) {
1504 // ... and we're the column the menu is on...
1505 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
1507 uiGridCtrl.columnMenuScope.hideMenu();
1509 // ... and we're NOT the column the menu is on
1511 // ... move the menu to our column
1512 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1515 // If the menu is NOT showing
1517 // ... show it on our column
1518 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1526 return uiGridHeaderCell;
1534 angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
1535 function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
1536 var defaultTemplate = 'ui-grid/ui-grid-header';
1537 var emptyTemplate = 'ui-grid/ui-grid-no-header';
1541 // templateUrl: 'ui-grid/ui-grid-header',
1544 require: ['^uiGrid', '^uiGridRenderContainer'],
1546 compile: function($elm, $attrs) {
1548 pre: function ($scope, $elm, $attrs, controllers) {
1549 var uiGridCtrl = controllers[0];
1550 var containerCtrl = controllers[1];
1552 $scope.grid = uiGridCtrl.grid;
1553 $scope.colContainer = containerCtrl.colContainer;
1555 updateHeaderReferences();
1558 if (!$scope.grid.options.showHeader) {
1559 headerTemplate = emptyTemplate;
1562 headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;
1565 gridUtil.getTemplate(headerTemplate)
1566 .then(function (contents) {
1567 var template = angular.element(contents);
1569 var newElm = $compile(template)($scope);
1570 $elm.replaceWith(newElm);
1572 // And update $elm to be the new element
1575 updateHeaderReferences();
1577 if (containerCtrl) {
1578 // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1579 var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1582 if (headerViewport) {
1583 containerCtrl.headerViewport = headerViewport;
1584 angular.element(headerViewport).on('scroll', scrollHandler);
1585 $scope.$on('$destroy', function () {
1586 angular.element(headerViewport).off('scroll', scrollHandler);
1591 $scope.grid.queueRefresh();
1594 function updateHeaderReferences() {
1595 containerCtrl.header = containerCtrl.colContainer.header = $elm;
1597 var headerCanvases = $elm[0].getElementsByClassName('ui-grid-header-canvas');
1599 if (headerCanvases.length > 0) {
1600 containerCtrl.headerCanvas = containerCtrl.colContainer.headerCanvas = headerCanvases[0];
1603 containerCtrl.headerCanvas = null;
1607 function scrollHandler(evt) {
1608 if (uiGridCtrl.grid.isScrollingHorizontally) {
1611 var newScrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.headerViewport, uiGridCtrl.grid);
1612 var horizScrollPercentage = containerCtrl.colContainer.scrollHorizontal(newScrollLeft);
1614 var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
1615 scrollEvent.newScrollLeft = newScrollLeft;
1616 if ( horizScrollPercentage > -1 ){
1617 scrollEvent.x = { percentage: horizScrollPercentage };
1620 uiGridCtrl.grid.scrollContainers(null, scrollEvent);
1624 post: function ($scope, $elm, $attrs, controllers) {
1625 var uiGridCtrl = controllers[0];
1626 var containerCtrl = controllers[1];
1628 // gridUtil.logDebug('ui-grid-header link');
1630 var grid = uiGridCtrl.grid;
1632 // Don't animate header cells
1633 gridUtil.disableAnimations($elm);
1635 function updateColumnWidths() {
1636 // this styleBuilder always runs after the renderContainer, so we can rely on the column widths
1637 // already being populated correctly
1639 var columnCache = containerCtrl.colContainer.visibleColumnCache;
1642 // uiGridCtrl.grid.columns.forEach(function (column) {
1644 var canvasWidth = 0;
1645 columnCache.forEach(function (column) {
1646 ret = ret + column.getColClassDefinition();
1647 canvasWidth += column.drawnWidth;
1650 containerCtrl.colContainer.canvasWidth = canvasWidth;
1652 // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1656 containerCtrl.header = $elm;
1658 var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1659 if (headerViewport) {
1660 containerCtrl.headerViewport = headerViewport;
1663 //todo: remove this if by injecting gridCtrl into unit tests
1665 uiGridCtrl.grid.registerStyleComputation({
1667 func: updateColumnWidths
1680 angular.module('ui.grid')
1681 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
1684 * @name ui.grid.gridMenuService
1686 * @description Methods for working with the grid menu
1692 * @methodOf ui.grid.gridMenuService
1694 * @description Sets up the gridMenu. Most importantly, sets our
1695 * scope onto the grid object as grid.gridMenuScope, allowing us
1696 * to operate when passed only the grid. Second most importantly,
1697 * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
1699 * @param {$scope} $scope the scope of this gridMenu
1700 * @param {Grid} grid the grid to which this gridMenu is associated
1702 initialize: function( $scope, grid ){
1703 grid.gridMenuScope = $scope;
1705 $scope.registeredMenuItems = [];
1707 // not certain this is needed, but would be bad to create a memory leak
1708 $scope.$on('$destroy', function() {
1709 if ( $scope.grid && $scope.grid.gridMenuScope ){
1710 $scope.grid.gridMenuScope = null;
1715 if ( $scope.registeredMenuItems ){
1716 $scope.registeredMenuItems = null;
1720 $scope.registeredMenuItems = [];
1724 * @name addToGridMenu
1725 * @methodOf ui.grid.core.api:PublicApi
1726 * @description add items to the grid menu. Used by features
1727 * to add their menu items if they are enabled, can also be used by
1728 * end users to add menu items. This method has the advantage of allowing
1729 * remove again, which can simplify management of which items are included
1730 * in the menu when. (Noting that in most cases the shown and active functions
1731 * provide a better way to handle visibility of menu items)
1732 * @param {Grid} grid the grid on which we are acting
1733 * @param {array} items menu items in the format as described in the tutorial, with
1734 * the added note that if you want to use remove you must also specify an `id` field,
1735 * which is provided when you want to remove an item. The id should be unique.
1738 grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
1742 * @name removeFromGridMenu
1743 * @methodOf ui.grid.core.api:PublicApi
1744 * @description Remove an item from the grid menu based on a provided id. Assumes
1745 * that the id is unique, removes only the last instance of that id. Does nothing if
1746 * the specified id is not found
1747 * @param {Grid} grid the grid on which we are acting
1748 * @param {string} id the id we'd like to remove from the menu
1751 grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
1757 * @name addToGridMenu
1758 * @propertyOf ui.grid.gridMenuService
1759 * @description add items to the grid menu. Used by features
1760 * to add their menu items if they are enabled, can also be used by
1761 * end users to add menu items. This method has the advantage of allowing
1762 * remove again, which can simplify management of which items are included
1763 * in the menu when. (Noting that in most cases the shown and active functions
1764 * provide a better way to handle visibility of menu items)
1765 * @param {Grid} grid the grid on which we are acting
1766 * @param {array} items menu items in the format as described in the tutorial, with
1767 * the added note that if you want to use remove you must also specify an `id` field,
1768 * which is provided when you want to remove an item. The id should be unique.
1771 addToGridMenu: function( grid, menuItems ) {
1772 if ( !angular.isArray( menuItems ) ) {
1773 gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
1775 if ( grid.gridMenuScope ){
1776 grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
1777 grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
1779 gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present. Timing issue? Please log issue with ui-grid');
1787 * @name removeFromGridMenu
1788 * @methodOf ui.grid.gridMenuService
1789 * @description Remove an item from the grid menu based on a provided id. Assumes
1790 * that the id is unique, removes only the last instance of that id. Does nothing if
1791 * the specified id is not found. If there is no gridMenuScope or registeredMenuItems
1792 * then do nothing silently - the desired result is those menu items not be present and they
1794 * @param {Grid} grid the grid on which we are acting
1795 * @param {string} id the id we'd like to remove from the menu
1798 removeFromGridMenu: function( grid, id ){
1799 var foundIndex = -1;
1801 if ( grid && grid.gridMenuScope ){
1802 grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1803 if ( value.id === id ){
1804 if (foundIndex > -1) {
1805 gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
1814 if ( foundIndex > -1 ){
1815 grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
1822 * @name gridMenuCustomItems
1823 * @propertyOf ui.grid.class:GridOptions
1824 * @description (optional) An array of menu items that should be added to
1825 * the gridMenu. Follow the format documented in the tutorial for column
1826 * menu customisation. The context provided to the action function will
1827 * include context.grid. An alternative if working with dynamic menus is to use the
1828 * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
1829 * some of the management of items for you.
1834 * @name gridMenuShowHideColumns
1835 * @propertyOf ui.grid.class:GridOptions
1836 * @description true by default, whether the grid menu should allow hide/show
1842 * @methodOf ui.grid.gridMenuService
1843 * @name getMenuItems
1844 * @description Decides the menu items to show in the menu. This is a
1847 * - the default menu items that are always included,
1848 * - any menu items that have been provided through the addMenuItem api. These
1849 * are typically added by features within the grid
1850 * - any menu items included in grid.options.gridMenuCustomItems. These can be
1851 * changed dynamically, as they're always recalculated whenever we show the
1853 * @param {$scope} $scope the scope of this gridMenu, from which we can find all
1854 * the information that we need
1855 * @returns {array} an array of menu items that can be shown
1857 getMenuItems: function( $scope ) {
1859 // this is where we add any menu items we want to always include
1862 if ( $scope.grid.options.gridMenuCustomItems ){
1863 if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
1864 gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
1866 menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
1870 var clearFilters = [{
1871 title: i18nService.getSafeText('gridMenu.clearAllFilters'),
1872 action: function ($event) {
1873 $scope.grid.clearAllFilters();
1876 return $scope.grid.options.enableFiltering;
1880 menuItems = menuItems.concat( clearFilters );
1882 menuItems = menuItems.concat( $scope.registeredMenuItems );
1884 if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1885 menuItems = menuItems.concat( service.showHideColumns( $scope ) );
1888 menuItems.sort(function(a, b){
1889 return a.order - b.order;
1898 * @name gridMenuTitleFilter
1899 * @propertyOf ui.grid.class:GridOptions
1900 * @description (optional) A function that takes a title string
1901 * (usually the col.displayName), and converts it into a display value. The function
1902 * must return either a string or a promise.
1904 * Used for internationalization of the grid menu column names - for angular-translate
1905 * you can pass $translate as the function, for i18nService you can pass getSafeText as the
1910 * gridMenuTitleFilter: $translate
1916 * @methodOf ui.grid.gridMenuService
1917 * @name showHideColumns
1918 * @description Adds two menu items for each of the columns in columnDefs. One
1919 * menu item for hide, one menu item for show. Each is visible when appropriate
1920 * (show when column is not visible, hide when column is visible). Each toggles
1921 * the visible property on the columnDef using toggleColumnVisibility
1922 * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
1924 showHideColumns: function( $scope ){
1925 var showHideColumns = [];
1926 if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
1927 return showHideColumns;
1930 // add header for columns
1931 showHideColumns.push({
1932 title: i18nService.getSafeText('gridMenu.columns'),
1936 $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
1938 $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1939 if ( colDef.enableHiding !== false ){
1940 // add hide menu item - shows an OK icon as we only show when column is already visible
1942 icon: 'ui-grid-icon-ok',
1943 action: function($event) {
1944 $event.stopPropagation();
1945 service.toggleColumnVisibility( this.context.gridCol );
1948 return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1950 context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1952 order: 301 + index * 2
1954 service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1955 showHideColumns.push( menuItem );
1957 // add show menu item - shows no icon as we only show when column is invisible
1959 icon: 'ui-grid-icon-cancel',
1960 action: function($event) {
1961 $event.stopPropagation();
1962 service.toggleColumnVisibility( this.context.gridCol );
1965 return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1967 context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
1969 order: 301 + index * 2 + 1
1971 service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1972 showHideColumns.push( menuItem );
1975 return showHideColumns;
1981 * @methodOf ui.grid.gridMenuService
1982 * @name setMenuItemTitle
1983 * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
1984 * item if it returns a string, otherwise waiting for the promise to resolve or reject then
1985 * putting the result into the title
1986 * @param {object} menuItem the menuItem we want to put the title on
1987 * @param {object} colDef the colDef from which we can get displayName, name or field
1988 * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
1991 setMenuItemTitle: function( menuItem, colDef, grid ){
1992 var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
1994 if ( typeof(title) === 'string' ){
1995 menuItem.title = title;
1996 } else if ( title.then ){
1997 // must be a promise
1998 menuItem.title = "";
1999 title.then( function( successValue ) {
2000 menuItem.title = successValue;
2001 }, function( errorValue ) {
2002 menuItem.title = errorValue;
2005 gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
2006 menuItem.title = 'badconfig';
2012 * @methodOf ui.grid.gridMenuService
2013 * @name toggleColumnVisibility
2014 * @description Toggles the visibility of an individual column. Expects to be
2015 * provided a context that has on it a gridColumn, which is the column that
2016 * we'll operate upon. We change the visibility, and refresh the grid as appropriate
2017 * @param {GridCol} gridCol the column that we want to toggle
2020 toggleColumnVisibility: function( gridCol ) {
2021 gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
2023 gridCol.grid.refresh();
2024 gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
2025 gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
2034 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
2035 function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
2040 require: ['^uiGrid'],
2041 templateUrl: 'ui-grid/ui-grid-menu-button',
2044 link: function ($scope, $elm, $attrs, controllers) {
2045 var uiGridCtrl = controllers[0];
2047 // For the aria label
2049 aria: i18nService.getSafeText('gridMenu.aria')
2052 uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
2054 $scope.shown = false;
2056 $scope.toggleMenu = function () {
2057 if ( $scope.shown ){
2058 $scope.$broadcast('hide-menu');
2059 $scope.shown = false;
2061 $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
2062 $scope.$broadcast('show-menu');
2063 $scope.shown = true;
2067 $scope.$on('menu-hidden', function() {
2068 $scope.shown = false;
2069 gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
2082 * @name ui.grid.directive:uiGridMenu
2087 * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2090 <doc:example module="app">
2093 var app = angular.module('app', ['ui.grid']);
2095 app.controller('MainCtrl', ['$scope', function ($scope) {
2100 <div ng-controller="MainCtrl">
2101 <div ui-grid-menu shown="true" ></div>
2108 angular.module('ui.grid')
2110 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
2111 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
2119 require: '?^uiGrid',
2120 templateUrl: 'ui-grid/uiGridMenu',
2122 link: function ($scope, $elm, $attrs, uiGridCtrl) {
2123 $scope.dynamicStyles = '';
2124 if (uiGridCtrl && uiGridCtrl.grid && uiGridCtrl.grid.options && uiGridCtrl.grid.options.gridMenuTemplate) {
2125 var gridMenuTemplate = uiGridCtrl.grid.options.gridMenuTemplate;
2126 gridUtil.getTemplate(gridMenuTemplate).then(function (contents) {
2127 var template = angular.element(contents);
2128 var newElm = $compile(template)($scope);
2129 $elm.replaceWith(newElm);
2133 var setupHeightStyle = function(gridHeight) {
2134 //menu appears under header row, so substract that height from it's total
2135 // additional 20px for general padding
2136 var gridMenuMaxHeight = gridHeight - uiGridCtrl.grid.headerHeight - 20;
2137 $scope.dynamicStyles = [
2138 '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
2139 'max-height: ' + gridMenuMaxHeight + 'px;',
2145 setupHeightStyle(uiGridCtrl.grid.gridHeight);
2146 uiGridCtrl.grid.api.core.on.gridDimensionChanged($scope, function(oldGridHeight, oldGridWidth, newGridHeight, newGridWidth) {
2147 setupHeightStyle(newGridHeight);
2152 close: i18nService.getSafeText('columnMenu.close')
2155 // *** Show/Hide functions ******
2156 $scope.showMenu = function(event, args) {
2157 if ( !$scope.shown ){
2160 * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
2161 * animate the removal of the ng-hide. We can't successfully (so far as I can tell)
2162 * animate removal of the ng-if, as the menu items aren't there yet. And we don't want
2163 * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
2166 * Note when testing animation that animations don't run on the tutorials. When debugging it looks
2167 * like they do, but angular has a default $animate provider that is just a stub, and that's what's
2168 * being called. ALso don't be fooled by the fact that your browser has actually loaded the
2169 * angular-translate.js, it's not using it. You need to test animations in an external application.
2171 $scope.shown = true;
2173 $timeout( function() {
2174 $scope.shownMid = true;
2175 $scope.$emit('menu-shown');
2177 } else if ( !$scope.shownMid ) {
2178 // we're probably doing a hide then show, so we don't need to wait for ng-if
2179 $scope.shownMid = true;
2180 $scope.$emit('menu-shown');
2183 var docEventType = 'click';
2184 if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
2185 docEventType = args.originalEvent.type;
2188 // Turn off an existing document click handler
2189 angular.element(document).off('click touchstart', applyHideMenu);
2190 $elm.off('keyup', checkKeyUp);
2191 $elm.off('keydown', checkKeyDown);
2193 // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
2194 $timeout(function() {
2195 angular.element(document).on(docEventType, applyHideMenu);
2196 $elm.on('keyup', checkKeyUp);
2197 $elm.on('keydown', checkKeyDown);
2200 //automatically set the focus to the first button element in the now open menu.
2201 gridUtil.focus.bySelector($elm, 'button[type=button]', true);
2205 $scope.hideMenu = function(event) {
2206 if ( $scope.shown ){
2208 * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
2209 * set the ng-if (shown = false) after the animation runs. In theory we can cascade off the
2210 * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
2212 * The user may have clicked on the menu again whilst
2213 * we're waiting, so we check that the mid isn't shown before applying the ng-if.
2215 $scope.shownMid = false;
2216 $timeout( function() {
2217 if ( !$scope.shownMid ){
2218 $scope.shown = false;
2219 $scope.$emit('menu-hidden');
2224 angular.element(document).off('click touchstart', applyHideMenu);
2225 $elm.off('keyup', checkKeyUp);
2226 $elm.off('keydown', checkKeyDown);
2229 $scope.$on('hide-menu', function (event, args) {
2230 $scope.hideMenu(event, args);
2233 $scope.$on('show-menu', function (event, args) {
2234 $scope.showMenu(event, args);
2238 // *** Auto hide when click elsewhere ******
2239 var applyHideMenu = function(){
2241 $scope.$apply(function () {
2247 // close menu on ESC and keep tab cyclical
2248 var checkKeyUp = function(event) {
2249 if (event.keyCode === 27) {
2254 var checkKeyDown = function(event) {
2255 var setFocus = function(elm) {
2257 event.preventDefault();
2260 if (event.keyCode === 9) {
2261 var firstMenuItem, lastMenuItem;
2262 var menuItemButtons = $elm[0].querySelectorAll('button:not(.ng-hide)');
2263 if (menuItemButtons.length > 0) {
2264 firstMenuItem = menuItemButtons[0];
2265 lastMenuItem = menuItemButtons[menuItemButtons.length - 1];
2266 if (event.target === lastMenuItem && !event.shiftKey) {
2267 setFocus(firstMenuItem);
2268 } else if (event.target === firstMenuItem && event.shiftKey) {
2269 setFocus(lastMenuItem);
2275 if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
2276 $scope.autoHide = true;
2279 if ($scope.autoHide) {
2280 angular.element($window).on('resize', applyHideMenu);
2283 $scope.$on('$destroy', function unbindEvents() {
2284 angular.element($window).off('resize', applyHideMenu);
2285 angular.element(document).off('click touchstart', applyHideMenu);
2286 $elm.off('keyup', checkKeyUp);
2287 $elm.off('keydown', checkKeyDown);
2291 $scope.$on('$destroy', uiGridCtrl.grid.api.core.on.scrollBegin($scope, applyHideMenu ));
2294 $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
2301 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
2302 var uiGridMenuItem = {
2313 screenReaderOnly: '='
2315 require: ['?^uiGrid'],
2316 templateUrl: 'ui-grid/uiGridMenuItem',
2318 compile: function() {
2320 pre: function ($scope, $elm) {
2321 if ($scope.templateUrl) {
2322 gridUtil.getTemplate($scope.templateUrl)
2323 .then(function (contents) {
2324 var template = angular.element(contents);
2326 var newElm = $compile(template)($scope);
2327 $elm.replaceWith(newElm);
2331 post: function ($scope, $elm, $attrs, controllers) {
2332 var uiGridCtrl = controllers[0];
2334 // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
2335 // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
2336 // throw new TypeError("$scope.shown is defined but not a function");
2338 if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
2339 $scope.shown = function() { return true; };
2342 $scope.itemShown = function () {
2344 if ($scope.context) {
2345 context.context = $scope.context;
2348 if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2349 context.grid = uiGridCtrl.grid;
2352 return $scope.shown.call(context);
2355 $scope.itemAction = function($event,title) {
2356 $event.stopPropagation();
2358 if (typeof($scope.action) === 'function') {
2361 if ($scope.context) {
2362 context.context = $scope.context;
2365 // Add the grid to the function call context if the uiGrid controller is present
2366 if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
2367 context.grid = uiGridCtrl.grid;
2370 $scope.action.call(context, $event, title);
2372 if ( !$scope.leaveOpen ){
2373 $scope.$emit('hide-menu');
2376 * XXX: Fix after column refactor
2377 * Ideally the focus would remain on the item.
2378 * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
2380 gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
2385 $scope.i18n = i18nService.get();
2391 return uiGridMenuItem;
2400 * @name ui.grid.directive:uiGridOneBind
2401 * @summary A group of directives that provide a one time bind to a dom element.
2402 * @description A group of directives that provide a one time bind to a dom element.
2403 * As one time bindings are not supported in Angular 1.2.* this directive provdes this capability.
2404 * This is done to reduce the number of watchers on the dom.
2406 * <h2>Short Example ({@link ui.grid.directive:uiGridOneBindSrc ui-grid-one-bind-src})</h2>
2408 <div ng-init="imageName = 'myImageDir.jpg'">
2409 <img ui-grid-one-bind-src="imageName"></img>
2414 <div ng-init="imageName = 'myImageDir.jpg'">
2415 <img ui-grid-one-bind-src="imageName" src="myImageDir.jpg"></img>
2419 <h2>Short Example ({@link ui.grid.directive:uiGridOneBindText ui-grid-one-bind-text})</h2>
2421 <div ng-init="text='Add this text'" ui-grid-one-bind-text="text"></div>
2425 <div ng-init="text='Add this text'" ui-grid-one-bind-text="text">Add this text</div>
2428 * <b>Note:</b> This behavior is slightly different for the {@link ui.grid.directive:uiGridOneBindIdGrid uiGridOneBindIdGrid}
2429 * and {@link ui.grid.directive:uiGridOneBindAriaLabelledbyGrid uiGridOneBindAriaLabelledbyGrid} directives.
2432 //https://github.com/joshkurz/Black-Belt-AngularJS-Directives/blob/master/directives/Optimization/oneBind.js
2433 var oneBinders = angular.module('ui.grid');
2437 * @name ui.grid.directive:uiGridOneBindSrc
2438 * @memberof ui.grid.directive:uiGridOneBind
2441 * @param {String} uiGridOneBindSrc The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2442 * @description One time binding for the src dom tag.
2445 {tag: 'Src', method: 'attr'},
2448 * @name ui.grid.directive:uiGridOneBindText
2451 * @param {String} uiGridOneBindText The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2452 * @description One time binding for the text dom tag.
2454 {tag: 'Text', method: 'text'},
2457 * @name ui.grid.directive:uiGridOneBindHref
2460 * @param {String} uiGridOneBindHref The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2461 * @description One time binding for the href dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2463 {tag: 'Href', method: 'attr'},
2466 * @name ui.grid.directive:uiGridOneBindClass
2469 * @param {String} uiGridOneBindClass The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2470 * @param {Object} uiGridOneBindClass The object that you want to bind. At least one of the values in the object must be something other than null or undefined for the watcher to be removed.
2471 * this is to prevent the watcher from being removed before the scope is initialized.
2472 * @param {Array} uiGridOneBindClass An array of classes to bind to this element.
2473 * @description One time binding for the class dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2475 {tag: 'Class', method: 'addClass'},
2478 * @name ui.grid.directive:uiGridOneBindHtml
2481 * @param {String} uiGridOneBindHtml The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2482 * @description One time binding for the html method on a dom element. For more information see {@link ui.grid.directive:uiGridOneBind}.
2484 {tag: 'Html', method: 'html'},
2487 * @name ui.grid.directive:uiGridOneBindAlt
2490 * @param {String} uiGridOneBindAlt The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2491 * @description One time binding for the alt dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2493 {tag: 'Alt', method: 'attr'},
2496 * @name ui.grid.directive:uiGridOneBindStyle
2499 * @param {String} uiGridOneBindStyle The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2500 * @description One time binding for the style dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2502 {tag: 'Style', method: 'css'},
2505 * @name ui.grid.directive:uiGridOneBindValue
2508 * @param {String} uiGridOneBindValue The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2509 * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2511 {tag: 'Value', method: 'attr'},
2514 * @name ui.grid.directive:uiGridOneBindId
2517 * @param {String} uiGridOneBindId The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2518 * @description One time binding for the value dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2520 {tag: 'Id', method: 'attr'},
2523 * @name ui.grid.directive:uiGridOneBindIdGrid
2526 * @param {String} uiGridOneBindIdGrid The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2527 * @description One time binding for the id dom tag.
2528 * <h1>Important Note!</h1>
2529 * If the id tag passed as a parameter does <b>not</b> contain the grid id as a substring
2530 * then the directive will search the scope and the parent controller (if it is a uiGridController) for the grid.id value.
2531 * If this value is found then it is appended to the begining of the id tag. If the grid is not found then the directive throws an error.
2532 * This is done in order to ensure uniqueness of id tags across the grid.
2533 * This is to prevent two grids in the same document having duplicate id tags.
2535 {tag: 'Id', directiveName:'IdGrid', method: 'attr', appendGridId: true},
2538 * @name ui.grid.directive:uiGridOneBindTitle
2541 * @param {String} uiGridOneBindTitle The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2542 * @description One time binding for the title dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2544 {tag: 'Title', method: 'attr'},
2547 * @name ui.grid.directive:uiGridOneBindAriaLabel
2550 * @param {String} uiGridOneBindAriaLabel The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2551 * @description One time binding for the aria-label dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2554 <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text"></div>
2558 <div ng-init="text='Add this text'" ui-grid-one-bind-aria-label="text" aria-label="Add this text"></div>
2561 {tag: 'Label', method: 'attr', aria:true},
2564 * @name ui.grid.directive:uiGridOneBindAriaLabelledby
2567 * @param {String} uiGridOneBindAriaLabelledby The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2568 * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2571 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId"></div>
2575 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby="anId" aria-labelledby="gridID32"></div>
2578 {tag: 'Labelledby', method: 'attr', aria:true},
2581 * @name ui.grid.directive:uiGridOneBindAriaLabelledbyGrid
2584 * @param {String} uiGridOneBindAriaLabelledbyGrid The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2585 * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2586 * Works somewhat like {@link ui.grid.directive:uiGridOneBindIdGrid} however this one supports a list of ids (seperated by a space) and will dynamically add the
2587 * grid id to each one.
2590 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId"></div>
2592 * Will become ([grid.id] will be replaced by the actual grid id):
2594 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-labelledby-grid="anId" aria-labelledby-Grid="[grid.id]-gridID32"></div>
2597 {tag: 'Labelledby', directiveName:'LabelledbyGrid', appendGridId:true, method: 'attr', aria:true},
2600 * @name ui.grid.directive:uiGridOneBindAriaDescribedby
2603 * @param {String} uiGridOneBindAriaDescribedby The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2604 * @description One time binding for the aria-describedby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2607 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId"></div>
2611 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby="anId" aria-describedby="gridID32"></div>
2614 {tag: 'Describedby', method: 'attr', aria:true},
2617 * @name ui.grid.directive:uiGridOneBindAriaDescribedbyGrid
2620 * @param {String} uiGridOneBindAriaDescribedbyGrid The angular string you want to bind. Does not support interpolation. Don't use <code>{{scopeElt}}</code> instead use <code>scopeElt</code>.
2621 * @description One time binding for the aria-labelledby dom tag. For more information see {@link ui.grid.directive:uiGridOneBind}.
2622 * Works somewhat like {@link ui.grid.directive:uiGridOneBindIdGrid} however this one supports a list of ids (seperated by a space) and will dynamically add the
2623 * grid id to each one.
2626 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId"></div>
2628 * Will become ([grid.id] will be replaced by the actual grid id):
2630 <div ng-init="anId = 'gridID32'" ui-grid-one-bind-aria-describedby-grid="anId" aria-describedby="[grid.id]-gridID32"></div>
2633 {tag: 'Describedby', directiveName:'DescribedbyGrid', appendGridId:true, method: 'attr', aria:true}],
2636 var baseDirectiveName = 'uiGridOneBind';
2637 //If it is an aria tag then append the aria label seperately
2638 //This is done because the aria tags are formatted aria-* and the directive name can't have a '-' character in it.
2639 //If the diretiveName has to be overridden then it does so here. This is because the tag being modified and the directive sometimes don't match up.
2640 var directiveName = (v.aria ? baseDirectiveName + 'Aria' : baseDirectiveName) + (v.directiveName ? v.directiveName : v.tag);
2641 oneBinders.directive(directiveName, ['gridUtil', function(gridUtil){
2644 require: ['?uiGrid','?^uiGrid'],
2645 link: function(scope, iElement, iAttrs, controllers){
2646 /* Appends the grid id to the beginnig of the value. */
2647 var appendGridId = function(val){
2648 var grid; //Get an instance of the grid if its available
2649 //If its available in the scope then we don't need to try to find it elsewhere
2653 //Another possible location to try to find the grid
2654 else if (scope.col && scope.col.grid){
2655 grid = scope.col.grid;
2657 //Last ditch effort: Search through the provided controllers.
2658 else if (!controllers.some( //Go through the controllers till one has the element we need
2659 function(controller){
2660 if (controller && controller.grid) {
2661 grid = controller.grid;
2662 return true; //We've found the grid
2665 //We tried our best to find it for you
2666 gridUtil.logError("["+directiveName+"] A valid grid could not be found to bind id. Are you using this directive " +
2667 "within the correct scope? Trying to generate id: [gridID]-" + val);
2668 throw new Error("No valid grid could be found");
2672 var idRegex = new RegExp(grid.id.toString());
2673 //If the grid id hasn't been appended already in the template declaration
2674 if (!idRegex.test(val)){
2675 val = grid.id.toString() + '-' + val;
2681 // The watch returns a function to remove itself.
2682 var rmWatcher = scope.$watch(iAttrs[directiveName], function(newV){
2684 //If we are trying to add an id element then we also apply the grid id if it isn't already there
2685 if (v.appendGridId) {
2686 var newIdString = null;
2687 //Append the id to all of the new ids.
2688 angular.forEach( newV.split(' '), function(s){
2689 newIdString = (newIdString ? (newIdString + ' ') : '') + appendGridId(s);
2694 // Append this newValue to the dom element.
2696 case 'attr': //The attr method takes two paraams the tag and the value
2698 //If it is an aria element then append the aria prefix
2699 iElement[v.method]('aria-' + v.tag.toLowerCase(),newV);
2701 iElement[v.method](v.tag.toLowerCase(),newV);
2705 //Pulled from https://github.com/Pasvaz/bindonce/blob/master/bindonce.js
2706 if (angular.isObject(newV) && !angular.isArray(newV)) {
2708 var nonNullFound = false; //We don't want to remove the binding unless the key is actually defined
2709 angular.forEach(newV, function (value, index) {
2710 if (value !== null && typeof(value) !== "undefined"){
2711 nonNullFound = true; //A non null value for a key was found so the object must have been initialized
2712 if (value) {results.push(index);}
2715 //A non null value for a key wasn't found so assume that the scope values haven't been fully initialized
2717 return; // If not initialized then the watcher should not be removed yet.
2723 iElement.addClass(angular.isArray(newV) ? newV.join(' ') : newV);
2729 iElement[v.method](newV);
2733 //Removes the watcher on itself after the bind
2736 // True ensures that equality is determined using angular.equals instead of ===
2737 }, true); //End rm watchers
2738 } //End compile function
2739 }; //End directive return
2740 } // End directive function
2742 }); // End angular foreach
2748 var module = angular.module('ui.grid');
2750 module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil', 'ScrollEvent',
2751 function($timeout, $document, uiGridConstants, gridUtil, ScrollEvent) {
2755 templateUrl: 'ui-grid/uiGridRenderContainer',
2756 require: ['^uiGrid', 'uiGridRenderContainer'],
2759 rowContainerName: '=',
2760 colContainerName: '=',
2761 bindScrollHorizontal: '=',
2762 bindScrollVertical: '=',
2763 enableVerticalScrollbar: '=',
2764 enableHorizontalScrollbar: '='
2766 controller: 'uiGridRenderContainer as RenderContainer',
2767 compile: function () {
2769 pre: function prelink($scope, $elm, $attrs, controllers) {
2771 var uiGridCtrl = controllers[0];
2772 var containerCtrl = controllers[1];
2773 var grid = $scope.grid = uiGridCtrl.grid;
2775 // Verify that the render container for this element exists
2776 if (!$scope.rowContainerName) {
2777 throw "No row render container name specified";
2779 if (!$scope.colContainerName) {
2780 throw "No column render container name specified";
2783 if (!grid.renderContainers[$scope.rowContainerName]) {
2784 throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2786 if (!grid.renderContainers[$scope.colContainerName]) {
2787 throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2790 var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2791 var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2793 containerCtrl.containerId = $scope.containerId;
2794 containerCtrl.rowContainer = rowContainer;
2795 containerCtrl.colContainer = colContainer;
2797 post: function postlink($scope, $elm, $attrs, controllers) {
2799 var uiGridCtrl = controllers[0];
2800 var containerCtrl = controllers[1];
2802 var grid = uiGridCtrl.grid;
2803 var rowContainer = containerCtrl.rowContainer;
2804 var colContainer = containerCtrl.colContainer;
2805 var scrollTop = null;
2806 var scrollLeft = null;
2809 var renderContainer = grid.renderContainers[$scope.containerId];
2811 // Put the container name on this element as a class
2812 $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2814 // Scroll the render container viewport when the mousewheel is used
2815 gridUtil.on.mousewheel($elm, function (event) {
2816 var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.RenderContainerMouseWheel);
2817 if (event.deltaY !== 0) {
2818 var scrollYAmount = event.deltaY * -1 * event.deltaFactor;
2820 scrollTop = containerCtrl.viewport[0].scrollTop;
2822 // Get the scroll percentage
2823 scrollEvent.verticalScrollLength = rowContainer.getVerticalScrollLength();
2824 var scrollYPercentage = (scrollTop + scrollYAmount) / scrollEvent.verticalScrollLength;
2826 // If we should be scrolled 100%, make sure the scrollTop matches the maximum scroll length
2827 // Viewports that have "overflow: hidden" don't let the mousewheel scroll all the way to the bottom without this check
2828 if (scrollYPercentage >= 1 && scrollTop < scrollEvent.verticalScrollLength) {
2829 containerCtrl.viewport[0].scrollTop = scrollEvent.verticalScrollLength;
2832 // Keep scrollPercentage within the range 0-1.
2833 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2834 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2836 scrollEvent.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2838 if (event.deltaX !== 0) {
2839 var scrollXAmount = event.deltaX * event.deltaFactor;
2841 // Get the scroll percentage
2842 scrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.viewport, grid);
2843 scrollEvent.horizontalScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2844 var scrollXPercentage = (scrollLeft + scrollXAmount) / scrollEvent.horizontalScrollLength;
2846 // Keep scrollPercentage within the range 0-1.
2847 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2848 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2850 scrollEvent.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2853 // Let the parent container scroll if the grid is already at the top/bottom
2854 if ((event.deltaY !== 0 && (scrollEvent.atTop(scrollTop) || scrollEvent.atBottom(scrollTop))) ||
2855 (event.deltaX !== 0 && (scrollEvent.atLeft(scrollLeft) || scrollEvent.atRight(scrollLeft)))) {
2856 //parent controller scrolls
2859 event.preventDefault();
2860 event.stopPropagation();
2861 scrollEvent.fireThrottledScrollingEvent('', scrollEvent);
2866 $elm.bind('$destroy', function() {
2867 $elm.unbind('keydown');
2869 ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2870 $elm.unbind(eventName);
2874 // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2878 var canvasWidth = colContainer.canvasWidth;
2879 var viewportWidth = colContainer.getViewportWidth();
2881 var canvasHeight = rowContainer.getCanvasHeight();
2883 //add additional height for scrollbar on left and right container
2884 //if ($scope.containerId !== 'body') {
2885 // canvasHeight -= grid.scrollbarHeight;
2888 var viewportHeight = rowContainer.getViewportHeight();
2889 //shorten the height to make room for a scrollbar placeholder
2890 if (colContainer.needsHScrollbarPlaceholder()) {
2891 viewportHeight -= grid.scrollbarHeight;
2894 var headerViewportWidth,
2895 footerViewportWidth;
2896 headerViewportWidth = footerViewportWidth = colContainer.getHeaderViewportWidth();
2898 // Set canvas dimensions
2899 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2901 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2903 if (renderContainer.explicitHeaderCanvasHeight) {
2904 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: ' + renderContainer.explicitHeaderCanvasHeight + 'px; }';
2907 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { height: inherit; }';
2910 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2911 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2913 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + (canvasWidth + grid.scrollbarWidth) + 'px; }';
2914 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2919 uiGridCtrl.grid.registerStyleComputation({
2930 module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2939 angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2943 // templateUrl: 'ui-grid/ui-grid-row',
2944 require: ['^uiGrid', '^uiGridRenderContainer'],
2947 //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2950 compile: function() {
2952 pre: function($scope, $elm, $attrs, controllers) {
2953 var uiGridCtrl = controllers[0];
2954 var containerCtrl = controllers[1];
2956 var grid = uiGridCtrl.grid;
2958 $scope.grid = uiGridCtrl.grid;
2959 $scope.colContainer = containerCtrl.colContainer;
2961 // Function for attaching the template to this scope
2962 var clonedElement, cloneScope;
2963 function compileTemplate() {
2964 $scope.row.getRowTemplateFn.then(function (compiledElementFn) {
2965 // var compiledElementFn = $scope.row.compiledElementFn;
2967 // Create a new scope for the contents of this row, so we can destroy it later if need be
2968 var newScope = $scope.$new();
2970 compiledElementFn(newScope, function (newElm, scope) {
2971 // If we already have a cloned element, we need to remove it and destroy its scope
2972 if (clonedElement) {
2973 clonedElement.remove();
2974 cloneScope.$destroy();
2977 // Empty the row and append the new element
2978 $elm.empty().append(newElm);
2980 // Save the new cloned element and scope
2981 clonedElement = newElm;
2982 cloneScope = newScope;
2987 // Initially attach the compiled template to this scope
2990 // If the row's compiled element function changes, we need to replace this element's contents with the new compiled template
2991 $scope.$watch('row.getRowTemplateFn', function (newFunc, oldFunc) {
2992 if (newFunc !== oldFunc) {
2997 post: function($scope, $elm, $attrs, controllers) {
3011 * @name ui.grid.directive:uiGridStyle
3016 * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
3019 <doc:example module="app">
3022 var app = angular.module('app', ['ui.grid']);
3024 app.controller('MainCtrl', ['$scope', function ($scope) {
3025 $scope.myStyle = '.blah { border: 1px solid }';
3029 <div ng-controller="MainCtrl">
3030 <style ui-grid-style>{{ myStyle }}</style>
3031 <span class="blah">I am in a box.</span>
3035 it('should apply the right class to the element', function () {
3036 element(by.css('.blah')).getCssValue('border-top-width')
3038 expect(c).toContain('1px');
3046 angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
3050 // require: '?^uiGrid',
3051 link: function($scope, $elm, $attrs, uiGridCtrl) {
3052 // gridUtil.logDebug('ui-grid-style link');
3053 // if (uiGridCtrl === undefined) {
3054 // gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
3057 var interpolateFn = $interpolate($elm.text(), true);
3059 if (interpolateFn) {
3060 $scope.$watch(interpolateFn, function(value) {
3065 // uiGridCtrl.recalcRowStyles = function() {
3066 // var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
3067 // var rowHeight = scope.options.rowHeight;
3070 // var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
3071 // for (var i = 1; i <= rowStyleCount; i++) {
3072 // ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
3073 // offset = offset + rowHeight;
3076 // scope.rowStyles = ret;
3079 // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
3090 angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent','uiGridConstants', '$log',
3091 function(gridUtil, ScrollEvent, uiGridConstants, $log) {
3095 controllerAs: 'Viewport',
3096 templateUrl: 'ui-grid/uiGridViewport',
3097 require: ['^uiGrid', '^uiGridRenderContainer'],
3098 link: function($scope, $elm, $attrs, controllers) {
3099 // gridUtil.logDebug('viewport post-link');
3101 var uiGridCtrl = controllers[0];
3102 var containerCtrl = controllers[1];
3104 $scope.containerCtrl = containerCtrl;
3106 var rowContainer = containerCtrl.rowContainer;
3107 var colContainer = containerCtrl.colContainer;
3109 var grid = uiGridCtrl.grid;
3111 $scope.grid = uiGridCtrl.grid;
3113 // Put the containers in scope so we can get rows and columns from them
3114 $scope.rowContainer = containerCtrl.rowContainer;
3115 $scope.colContainer = containerCtrl.colContainer;
3117 // Register this viewport with its container
3118 containerCtrl.viewport = $elm;
3122 * @name customScroller
3123 * @methodOf ui.grid.class:GridOptions
3124 * @description (optional) uiGridViewport.on('scroll', scrollHandler) by default.
3125 * A function that allows you to provide your own scroller function. It is particularly helpful if you want to use third party scrollers
3126 * as this allows you to do that.
3128 * <div class="alert alert-info" role="alert"> <strong>NOTE:</strong> It is important to remember to always pass in an event object to
3129 * the scrollHandler as the grid scrolling behavior will break without it.</div>
3132 * $scope.gridOptions = {
3133 * customScroller: function myScrolling(uiGridViewport, scrollHandler) {
3134 * uiGridViewport.on('scroll', function myScrollingOverride(event) {
3135 * // Do something here
3137 * scrollHandler(event);
3142 * @param {object} uiGridViewport Element being scrolled. (this gets passed in by the grid).
3143 * @param {function} scrollHandler Function that needs to be called when scrolling happens. (this gets passed in by the grid).
3145 if (grid && grid.options && grid.options.customScroller) {
3146 grid.options.customScroller($elm, scrollHandler);
3148 $elm.on('scroll', scrollHandler);
3151 var ignoreScroll = false;
3153 function scrollHandler(evt) {
3154 //Leaving in this commented code in case it can someday be used
3155 //It does improve performance, but because the horizontal scroll is normalized,
3156 // using this code will lead to the column header getting slightly out of line with columns
3158 //if (ignoreScroll && (grid.isScrollingHorizontally || grid.isScrollingHorizontally)) {
3159 // //don't ask for scrollTop if we just set it
3160 // ignoreScroll = false;
3163 //ignoreScroll = true;
3165 var newScrollTop = $elm[0].scrollTop;
3166 var newScrollLeft = gridUtil.normalizeScrollLeft($elm, grid);
3168 var vertScrollPercentage = rowContainer.scrollVertical(newScrollTop);
3169 var horizScrollPercentage = colContainer.scrollHorizontal(newScrollLeft);
3171 var scrollEvent = new ScrollEvent(grid, rowContainer, colContainer, ScrollEvent.Sources.ViewPortScroll);
3172 scrollEvent.newScrollLeft = newScrollLeft;
3173 scrollEvent.newScrollTop = newScrollTop;
3174 if ( horizScrollPercentage > -1 ){
3175 scrollEvent.x = { percentage: horizScrollPercentage };
3178 if ( vertScrollPercentage > -1 ){
3179 scrollEvent.y = { percentage: vertScrollPercentage };
3182 grid.scrollContainers($scope.$parent.containerId, scrollEvent);
3185 if ($scope.$parent.bindScrollVertical) {
3186 grid.addVerticalScrollSync($scope.$parent.containerId, syncVerticalScroll);
3189 if ($scope.$parent.bindScrollHorizontal) {
3190 grid.addHorizontalScrollSync($scope.$parent.containerId, syncHorizontalScroll);
3191 grid.addHorizontalScrollSync($scope.$parent.containerId + 'header', syncHorizontalHeader);
3192 grid.addHorizontalScrollSync($scope.$parent.containerId + 'footer', syncHorizontalFooter);
3195 function syncVerticalScroll(scrollEvent){
3196 containerCtrl.prevScrollArgs = scrollEvent;
3197 var newScrollTop = scrollEvent.getNewScrollTop(rowContainer,containerCtrl.viewport);
3198 $elm[0].scrollTop = newScrollTop;
3202 function syncHorizontalScroll(scrollEvent){
3203 containerCtrl.prevScrollArgs = scrollEvent;
3204 var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3205 $elm[0].scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3208 function syncHorizontalHeader(scrollEvent){
3209 var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3210 if (containerCtrl.headerViewport) {
3211 containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3215 function syncHorizontalFooter(scrollEvent){
3216 var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
3217 if (containerCtrl.footerViewport) {
3218 containerCtrl.footerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
3222 $scope.$on('$destroy', function unbindEvents() {
3226 controller: ['$scope', function ($scope) {
3227 this.rowStyle = function (index) {
3228 var rowContainer = $scope.rowContainer;
3229 var colContainer = $scope.colContainer;
3233 if (rowContainer.currentTopRow !== 0){
3234 //top offset based on hidden rows count
3235 var translateY = "translateY("+ (rowContainer.currentTopRow * rowContainer.grid.options.rowHeight) +"px)";
3236 styles['transform'] = translateY;
3237 styles['-webkit-transform'] = translateY;
3238 styles['-ms-transform'] = translateY;
3241 if (colContainer.currentFirstColumn !== 0) {
3242 if (colContainer.grid.isRTL()) {
3243 styles['margin-right'] = colContainer.columnOffset + 'px';
3246 styles['margin-left'] = colContainer.columnOffset + 'px';
3261 angular.module('ui.grid')
3262 .directive('uiGridVisible', function uiGridVisibleAction() {
3263 return function ($scope, $elm, $attr) {
3264 $scope.$watch($attr.uiGridVisible, function (visible) {
3265 // $elm.css('visibility', visible ? 'visible' : 'hidden');
3266 $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
3275 angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
3276 '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
3277 function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
3278 $templateCache, gridClassFactory, $timeout, $parse, $compile) {
3279 // gridUtil.logDebug('ui-grid controller');
3283 self.grid = gridClassFactory.createGrid($scope.uiGrid);
3285 //assign $scope.$parent if appScope not already assigned
3286 self.grid.appScope = self.grid.appScope || $scope.$parent;
3288 $elm.addClass('grid' + self.grid.id);
3289 self.grid.rtl = gridUtil.getStyles($elm[0])['direction'] === 'rtl';
3292 // angular.extend(self.grid.options, );
3294 //all properties of grid are available on scope
3295 $scope.grid = self.grid;
3297 if ($attrs.uiGridColumns) {
3298 $attrs.$observe('uiGridColumns', function(value) {
3299 self.grid.options.columnDefs = value;
3300 self.grid.buildColumns()
3302 self.grid.preCompileCellTemplates();
3304 self.grid.refreshCanvas(true);
3310 // if fastWatch is set we watch only the length and the reference, not every individual object
3311 var deregFunctions = [];
3312 if (self.grid.options.fastWatch) {
3313 self.uiGrid = $scope.uiGrid;
3314 if (angular.isString($scope.uiGrid.data)) {
3315 deregFunctions.push( $scope.$parent.$watch($scope.uiGrid.data, dataWatchFunction) );
3316 deregFunctions.push( $scope.$parent.$watch(function() {
3317 if ( self.grid.appScope[$scope.uiGrid.data] ){
3318 return self.grid.appScope[$scope.uiGrid.data].length;
3322 }, dataWatchFunction) );
3324 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3325 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.data.length; }, function(){ dataWatchFunction($scope.uiGrid.data); }) );
3327 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3328 deregFunctions.push( $scope.$parent.$watch(function() { return $scope.uiGrid.columnDefs.length; }, function(){ columnDefsWatchFunction($scope.uiGrid.columnDefs); }) );
3330 if (angular.isString($scope.uiGrid.data)) {
3331 deregFunctions.push( $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction) );
3333 deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction) );
3335 deregFunctions.push( $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction) );
3339 function columnDefsWatchFunction(n, o) {
3341 self.grid.options.columnDefs = $scope.uiGrid.columnDefs;
3342 self.grid.buildColumns({ orderByColumnDefs: true })
3345 self.grid.preCompileCellTemplates();
3347 self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
3354 function dataWatchFunction(newData) {
3355 // gridUtil.logDebug('dataWatch fired');
3358 if ( self.grid.options.fastWatch ){
3359 if (angular.isString($scope.uiGrid.data)) {
3360 newData = self.grid.appScope[$scope.uiGrid.data];
3362 newData = $scope.uiGrid.data;
3366 mostRecentData = newData;
3369 // columns length is greater than the number of row header columns, which don't count because they're created automatically
3370 var hasColumns = self.grid.columns.length > (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0);
3373 // If we have no columns
3375 // ... and we don't have a ui-grid-columns attribute, which would define columns for us
3376 !$attrs.uiGridColumns &&
3377 // ... and we have no pre-defined columns
3378 self.grid.options.columnDefs.length === 0 &&
3379 // ... but we DO have data
3382 // ... then build the column definitions from the data that we have
3383 self.grid.buildColumnDefsFromData(newData);
3386 // If we haven't built columns before and either have some columns defined or some data defined
3387 if (!hasColumns && (self.grid.options.columnDefs.length > 0 || newData.length > 0)) {
3388 // Build the column set, then pre-compile the column cell templates
3389 promises.push(self.grid.buildColumns()
3391 self.grid.preCompileCellTemplates();
3395 $q.all(promises).then(function() {
3396 // use most recent data, rather than the potentially outdated data passed into watcher handler
3397 self.grid.modifyRows(mostRecentData)
3399 // if (self.viewport) {
3400 self.grid.redrawInPlace(true);
3403 $scope.$evalAsync(function() {
3404 self.grid.refreshCanvas(true);
3405 self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
3412 var styleWatchDereg = $scope.$watch(function () { return self.grid.styleComputations; }, function() {
3413 self.grid.refreshCanvas(true);
3416 $scope.$on('$destroy', function() {
3417 deregFunctions.forEach( function( deregFn ){ deregFn(); });
3421 self.fireEvent = function(eventName, args) {
3422 // Add the grid to the event arguments if it's not there
3423 if (typeof(args) === 'undefined' || args === undefined) {
3427 if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
3428 args.grid = self.grid;
3431 $scope.$broadcast(eventName, args);
3434 self.innerCompile = function innerCompile(elm) {
3435 $compile(elm)($scope);
3442 * @name ui.grid.directive:uiGrid
3445 * @param {Object} uiGrid Options for the grid to use
3447 * @description Create a very basic grid.
3450 <example module="app">
3451 <file name="app.js">
3452 var app = angular.module('app', ['ui.grid']);
3454 app.controller('MainCtrl', ['$scope', function ($scope) {
3456 { name: 'Bob', title: 'CEO' },
3457 { name: 'Frank', title: 'Lowly Developer' }
3461 <file name="index.html">
3462 <div ng-controller="MainCtrl">
3463 <div ui-grid="{ data: data }"></div>
3468 angular.module('ui.grid').directive('uiGrid', uiGridDirective);
3470 uiGridDirective.$inject = ['$compile', '$templateCache', '$timeout', '$window', 'gridUtil', 'uiGridConstants'];
3471 function uiGridDirective($compile, $templateCache, $timeout, $window, gridUtil, uiGridConstants) {
3473 templateUrl: 'ui-grid/ui-grid',
3479 controller: 'uiGridController',
3480 compile: function () {
3482 post: function ($scope, $elm, $attrs, uiGridCtrl) {
3483 var grid = uiGridCtrl.grid;
3484 // Initialize scrollbars (TODO: move to controller??)
3485 uiGridCtrl.scrollbars = [];
3486 grid.element = $elm;
3489 // See if the grid has a rendered width, if not, wait a bit and try again
3490 var sizeCheckInterval = 100; // ms
3491 var maxSizeChecks = 20; // 2 seconds total
3494 // Setup (event listeners) the grid
3497 // And initialize it
3500 // Mark rendering complete so API events can happen
3501 grid.renderingComplete();
3503 // If the grid doesn't have size currently, wait for a bit to see if it gets size
3508 function checkSize() {
3509 // If the grid has no width and we haven't checked more than <maxSizeChecks> times, check again in <sizeCheckInterval> milliseconds
3510 if ($elm[0].offsetWidth <= 0 && sizeChecks < maxSizeChecks) {
3511 setTimeout(checkSize, sizeCheckInterval);
3519 // Setup event listeners and watchers
3521 // Bind to window resize events
3522 angular.element($window).on('resize', gridResize);
3524 // Unbind from window resize events when the grid is destroyed
3525 $elm.on('$destroy', function () {
3526 angular.element($window).off('resize', gridResize);
3529 // If we add a left container after render, we need to watch and react
3530 $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3531 if (newValue === oldValue) {
3534 grid.refreshCanvas(true);
3537 // If we add a right container after render, we need to watch and react
3538 $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3539 if (newValue === oldValue) {
3542 grid.refreshCanvas(true);
3546 // Initialize the directive
3548 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3550 // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
3551 grid.canvasWidth = uiGridCtrl.grid.gridWidth;
3553 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3555 // If the grid isn't tall enough to fit a single row, it's kind of useless. Resize it to fit a minimum number of rows
3556 if (grid.gridHeight <= grid.options.rowHeight && grid.options.enableMinHeightCheck) {
3560 // Run initial canvas refresh
3561 grid.refreshCanvas(true);
3564 // Set the grid's height ourselves in the case that its height would be unusably small
3565 function autoAdjustHeight() {
3566 // Figure out the new height
3567 var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
3568 var headerHeight = grid.options.showHeader ? grid.options.headerRowHeight : 0;
3569 var footerHeight = grid.calcFooterHeight();
3571 var scrollbarHeight = 0;
3572 if (grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3573 scrollbarHeight = gridUtil.getScrollbarWidth();
3576 var maxNumberOfFilters = 0;
3577 // Calculates the maximum number of filters in the columns
3578 angular.forEach(grid.options.columnDefs, function(col) {
3579 if (col.hasOwnProperty('filter')) {
3580 if (maxNumberOfFilters < 1) {
3581 maxNumberOfFilters = 1;
3584 else if (col.hasOwnProperty('filters')) {
3585 if (maxNumberOfFilters < col.filters.length) {
3586 maxNumberOfFilters = col.filters.length;
3591 if (grid.options.enableFiltering && !maxNumberOfFilters) {
3592 var allColumnsHaveFilteringTurnedOff = grid.options.columnDefs.length && grid.options.columnDefs.every(function(col) {
3593 return col.enableFiltering === false;
3596 if (!allColumnsHaveFilteringTurnedOff) {
3597 maxNumberOfFilters = 1;
3601 var filterHeight = maxNumberOfFilters * headerHeight;
3603 var newHeight = headerHeight + contentHeight + footerHeight + scrollbarHeight + filterHeight;
3605 $elm.css('height', newHeight + 'px');
3607 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3610 // Resize the grid on window resize events
3611 function gridResize($event) {
3612 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3613 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3615 grid.refreshCanvas(true);
3628 // TODO: rename this file to ui-grid-pinned-container.js
3630 angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
3634 template: '<div class="ui-grid-pinned-container"><div ui-grid-render-container container-id="side" row-container-name="\'body\'" col-container-name="side" bind-scroll-vertical="true" class="{{ side }} ui-grid-render-container-{{ side }}"></div></div>',
3636 side: '=uiGridPinnedContainer'
3639 compile: function compile() {
3641 post: function ($scope, $elm, $attrs, uiGridCtrl) {
3642 // gridUtil.logDebug('ui-grid-pinned-container ' + $scope.side + ' link');
3644 var grid = uiGridCtrl.grid;
3648 $elm.addClass('ui-grid-pinned-container-' + $scope.side);
3650 // Monkey-patch the viewport width function
3651 if ($scope.side === 'left' || $scope.side === 'right') {
3652 grid.renderContainers[$scope.side].getViewportWidth = monkeyPatchedGetViewportWidth;
3655 function monkeyPatchedGetViewportWidth() {
3656 /*jshint validthis: true */
3659 var viewportWidth = 0;
3660 self.visibleColumnCache.forEach(function (column) {
3661 viewportWidth += column.drawnWidth;
3664 var adjustment = self.getViewportAdjustment();
3666 viewportWidth = viewportWidth + adjustment.width;
3668 return viewportWidth;
3671 function updateContainerWidth() {
3672 if ($scope.side === 'left' || $scope.side === 'right') {
3673 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
3675 for (var i = 0; i < cols.length; i++) {
3677 width += col.drawnWidth || col.width || 0;
3684 function updateContainerDimensions() {
3687 // Column containers
3688 if ($scope.side === 'left' || $scope.side === 'right') {
3689 myWidth = updateContainerWidth();
3691 // gridUtil.logDebug('myWidth', myWidth);
3693 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
3694 $elm.attr('style', null);
3696 // var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
3698 ret += '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.side + ', .grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.side + ' .ui-grid-render-container-' + $scope.side + ' .ui-grid-viewport { width: ' + myWidth + 'px; } ';
3704 grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
3705 myWidth = updateContainerWidth();
3707 // Subtract our own width
3708 adjustment.width -= myWidth;
3709 adjustment.side = $scope.side;
3714 // Register style computation to adjust for columns in `side`'s render container
3715 grid.registerStyleComputation({
3717 func: updateContainerDimensions
3728 angular.module('ui.grid')
3729 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout','ScrollEvent',
3730 function($q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout, ScrollEvent) {
3734 * @name ui.grid.core.api:PublicApi
3735 * @description Public Api for the core grid features
3741 * @name ui.grid.class:Grid
3742 * @description Grid is the main viewModel. Any properties or methods needed to maintain state are defined in
3743 * this prototype. One instance of Grid is created per Grid directive instance.
3744 * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
3746 var Grid = function Grid(options) {
3748 // Get the id out of the options, then remove it
3749 if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
3750 if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
3751 throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
3755 throw new Error('No ID provided. An ID must be given when creating a grid.');
3758 self.id = options.id;
3761 // Get default options
3762 self.options = GridOptions.initialize( options );
3767 * @propertyOf ui.grid.class:Grid
3768 * @description reference to the application scope (the parent scope of the ui-grid element). Assigned in ui-grid controller
3770 * use gridOptions.appScopeProvider to override the default assignment of $scope.$parent with any reference
3772 self.appScope = self.options.appScopeProvider;
3774 self.headerHeight = self.options.headerRowHeight;
3779 * @name footerHeight
3780 * @propertyOf ui.grid.class:Grid
3781 * @description returns the total footer height gridFooter + columnFooter
3783 self.footerHeight = self.calcFooterHeight();
3788 * @name columnFooterHeight
3789 * @propertyOf ui.grid.class:Grid
3790 * @description returns the total column footer height
3792 self.columnFooterHeight = self.calcColumnFooterHeight();
3795 self.gridHeight = 0;
3797 self.columnBuilders = [];
3798 self.rowBuilders = [];
3799 self.rowsProcessors = [];
3800 self.columnsProcessors = [];
3801 self.styleComputations = [];
3802 self.viewportAdjusters = [];
3803 self.rowHeaderColumns = [];
3804 self.dataChangeCallbacks = {};
3805 self.verticalScrollSyncCallBackFns = {};
3806 self.horizontalScrollSyncCallBackFns = {};
3808 // self.visibleRowCache = [];
3810 // Set of 'render' containers for self grid, which can render sets of rows
3811 self.renderContainers = {};
3814 self.renderContainers.body = new GridRenderContainer('body', self);
3816 self.cellValueGetterCache = {};
3818 // Cached function to use with custom row templates
3819 self.getRowTemplateFn = null;
3822 //representation of the rows on the grid.
3823 //these are wrapped references to the actual data rows (options.data)
3826 //represents the columns on the grid
3831 * @name isScrollingVertically
3832 * @propertyOf ui.grid.class:Grid
3833 * @description set to true when Grid is scrolling vertically. Set to false via debounced method
3835 self.isScrollingVertically = false;
3839 * @name isScrollingHorizontally
3840 * @propertyOf ui.grid.class:Grid
3841 * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
3843 self.isScrollingHorizontally = false;
3847 * @name scrollDirection
3848 * @propertyOf ui.grid.class:Grid
3849 * @description set one of the {@link ui.grid.service:uiGridConstants#properties_scrollDirection uiGridConstants.scrollDirection}
3850 * values (UP, DOWN, LEFT, RIGHT, NONE), which tells us which direction we are scrolling.
3851 * Set to NONE via debounced method
3853 self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3855 //if true, grid will not respond to any scroll events
3856 self.disableScrolling = false;
3859 function vertical (scrollEvent) {
3860 self.isScrollingVertically = false;
3861 self.api.core.raise.scrollEnd(scrollEvent);
3862 self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3865 var debouncedVertical = gridUtil.debounce(vertical, self.options.scrollDebounce);
3866 var debouncedVerticalMinDelay = gridUtil.debounce(vertical, 0);
3868 function horizontal (scrollEvent) {
3869 self.isScrollingHorizontally = false;
3870 self.api.core.raise.scrollEnd(scrollEvent);
3871 self.scrollDirection = uiGridConstants.scrollDirection.NONE;
3874 var debouncedHorizontal = gridUtil.debounce(horizontal, self.options.scrollDebounce);
3875 var debouncedHorizontalMinDelay = gridUtil.debounce(horizontal, 0);
3880 * @name flagScrollingVertically
3881 * @methodOf ui.grid.class:Grid
3882 * @description sets isScrollingVertically to true and sets it to false in a debounced function
3884 self.flagScrollingVertically = function(scrollEvent) {
3885 if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3886 self.api.core.raise.scrollBegin(scrollEvent);
3888 self.isScrollingVertically = true;
3889 if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3890 debouncedVerticalMinDelay(scrollEvent);
3893 debouncedVertical(scrollEvent);
3899 * @name flagScrollingHorizontally
3900 * @methodOf ui.grid.class:Grid
3901 * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
3903 self.flagScrollingHorizontally = function(scrollEvent) {
3904 if (!self.isScrollingVertically && !self.isScrollingHorizontally) {
3905 self.api.core.raise.scrollBegin(scrollEvent);
3907 self.isScrollingHorizontally = true;
3908 if (self.options.scrollDebounce === 0 || !scrollEvent.withDelay) {
3909 debouncedHorizontalMinDelay(scrollEvent);
3912 debouncedHorizontal(scrollEvent);
3916 self.scrollbarHeight = 0;
3917 self.scrollbarWidth = 0;
3918 if (self.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3919 self.scrollbarHeight = gridUtil.getScrollbarWidth();
3922 if (self.options.enableVerticalScrollbar === uiGridConstants.scrollbars.ALWAYS) {
3923 self.scrollbarWidth = gridUtil.getScrollbarWidth();
3928 self.api = new GridApi(self);
3933 * @methodOf ui.grid.core.api:PublicApi
3934 * @description Refresh the rendered grid on screen.
3935 * The refresh method re-runs both the columnProcessors and the
3936 * rowProcessors, as well as calling refreshCanvas to update all
3937 * the grid sizing. In general you should prefer to use queueGridRefresh
3938 * instead, which is basically a debounced version of refresh.
3940 * If you only want to resize the grid, not regenerate all the rows
3941 * and columns, you should consider directly calling refreshCanvas instead.
3943 * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed
3945 self.api.registerMethod( 'core', 'refresh', this.refresh );
3949 * @name queueGridRefresh
3950 * @methodOf ui.grid.core.api:PublicApi
3951 * @description Request a refresh of the rendered grid on screen, if multiple
3952 * calls to queueGridRefresh are made within a digest cycle only one will execute.
3953 * The refresh method re-runs both the columnProcessors and the
3954 * rowProcessors, as well as calling refreshCanvas to update all
3955 * the grid sizing. In general you should prefer to use queueGridRefresh
3956 * instead, which is basically a debounced version of refresh.
3959 self.api.registerMethod( 'core', 'queueGridRefresh', this.queueGridRefresh );
3964 * @methodOf ui.grid.core.api:PublicApi
3965 * @description Runs only the rowProcessors, columns remain as they were.
3966 * It then calls redrawInPlace and refreshCanvas, which adjust the grid sizing.
3967 * @returns {promise} promise that is resolved when render completes?
3970 self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
3974 * @name queueRefresh
3975 * @methodOf ui.grid.core.api:PublicApi
3976 * @description Requests execution of refreshCanvas, if multiple requests are made
3977 * during a digest cycle only one will run. RefreshCanvas updates the grid sizing.
3978 * @returns {promise} promise that is resolved when render completes?
3981 self.api.registerMethod( 'core', 'queueRefresh', this.queueRefresh );
3985 * @name handleWindowResize
3986 * @methodOf ui.grid.core.api:PublicApi
3987 * @description Trigger a grid resize, normally this would be picked
3988 * up by a watch on window size, but in some circumstances it is necessary
3989 * to call this manually
3990 * @returns {promise} promise that is resolved when render completes?
3993 self.api.registerMethod( 'core', 'handleWindowResize', this.handleWindowResize );
3998 * @name addRowHeaderColumn
3999 * @methodOf ui.grid.core.api:PublicApi
4000 * @description adds a row header column to the grid
4001 * @param {object} column def
4002 * @param {number} order Determines order of header column on grid. Lower order means header
4003 * is positioned to the left of higher order headers
4006 self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
4010 * @name scrollToIfNecessary
4011 * @methodOf ui.grid.core.api:PublicApi
4012 * @description Scrolls the grid to make a certain row and column combo visible,
4013 * in the case that it is not completely visible on the screen already.
4014 * @param {GridRow} gridRow row to make visible
4015 * @param {GridCol} gridCol column to make visible
4016 * @returns {promise} a promise that is resolved when scrolling is complete
4019 self.api.registerMethod( 'core', 'scrollToIfNecessary', function(gridRow, gridCol) { return self.scrollToIfNecessary(gridRow, gridCol);} );
4024 * @methodOf ui.grid.core.api:PublicApi
4025 * @description Scroll the grid such that the specified
4026 * row and column is in view
4027 * @param {object} rowEntity gridOptions.data[] array instance to make visible
4028 * @param {object} colDef to make visible
4029 * @returns {promise} a promise that is resolved after any scrolling is finished
4031 self.api.registerMethod( 'core', 'scrollTo', function (rowEntity, colDef) { return self.scrollTo(rowEntity, colDef);} );
4035 * @name registerRowsProcessor
4036 * @methodOf ui.grid.core.api:PublicApi
4038 * Register a "rows processor" function. When the rows are updated,
4039 * the grid calls each registered "rows processor", which has a chance
4040 * to alter the set of rows (sorting, etc) as long as the count is not
4043 * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4044 * is run in the context of the grid (i.e. this for the function will be the grid), and must
4045 * return the updated rows list, which is passed to the next processor in the chain
4046 * @param {number} priority the priority of this processor. In general we try to do them in 100s to leave room
4047 * for other people to inject rows processors at intermediate priorities. Lower priority rowsProcessors run earlier.
4049 * At present allRowsVisible is running at 50, sort manipulations running at 60-65, filter is running at 100,
4050 * sort is at 200, grouping and treeview at 400-410, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
4052 self.api.registerMethod( 'core', 'registerRowsProcessor', this.registerRowsProcessor );
4056 * @name registerColumnsProcessor
4057 * @methodOf ui.grid.core.api:PublicApi
4059 * Register a "columns processor" function. When the columns are updated,
4060 * the grid calls each registered "columns processor", which has a chance
4061 * to alter the set of columns as long as the count is not
4064 * @param {function(renderedColumnsToProcess, rows )} processorFunction columns processor function, which
4065 * is run in the context of the grid (i.e. this for the function will be the grid), and must
4066 * return the updated columns list, which is passed to the next processor in the chain
4067 * @param {number} priority the priority of this processor. In general we try to do them in 100s to leave room
4068 * for other people to inject columns processors at intermediate priorities. Lower priority columnsProcessors run earlier.
4070 * At present allRowsVisible is running at 50, filter is running at 100, sort is at 200, grouping at 400, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
4072 self.api.registerMethod( 'core', 'registerColumnsProcessor', this.registerColumnsProcessor );
4078 * @name sortHandleNulls
4079 * @methodOf ui.grid.core.api:PublicApi
4080 * @description A null handling method that can be used when building custom sort
4084 * mySortFn = function(a, b) {
4085 * var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
4086 * if ( nulls !== null ){
4089 * // your code for sorting here
4092 * @param {object} a sort value a
4093 * @param {object} b sort value b
4094 * @returns {number} null if there were no nulls/undefineds, otherwise returns
4095 * a sort value that should be passed back from the sort function
4098 self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
4104 * @methodOf ui.grid.core.api:PublicApi
4105 * @description The sort criteria on one or more columns has
4106 * changed. Provides as parameters the grid and the output of
4107 * getColumnSorting, which is an array of gridColumns
4108 * that have sorting on them, sorted in priority order.
4110 * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
4111 * @param {Function} callBack Will be called when the event is emited. The function passes back the grid and an array of
4112 * columns with sorts on them, in priority order.
4116 * gridApi.core.on.sortChanged( $scope, function(grid, sortColumns){
4121 self.api.registerEvent( 'core', 'sortChanged' );
4125 * @name columnVisibilityChanged
4126 * @methodOf ui.grid.core.api:PublicApi
4127 * @description The visibility of a column has changed,
4128 * the column itself is passed out as a parameter of the event.
4130 * @param {$scope} scope The scope of the controller. This is used to deregister this event when the scope is destroyed.
4131 * @param {Function} callBack Will be called when the event is emited. The function passes back the GridCol that has changed.
4135 * gridApi.core.on.columnVisibilityChanged( $scope, function (column) {
4140 self.api.registerEvent( 'core', 'columnVisibilityChanged' );
4144 * @name notifyDataChange
4145 * @methodOf ui.grid.core.api:PublicApi
4146 * @description Notify the grid that a data or config change has occurred,
4147 * where that change isn't something the grid was otherwise noticing. This
4148 * might be particularly relevant where you've changed values within the data
4149 * and you'd like cell classes to be re-evaluated, or changed config within
4150 * the columnDef and you'd like headerCellClasses to be re-evaluated.
4151 * @param {string} type one of the
4152 * {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4153 * values (ALL, ROW, EDIT, COLUMN), which tells us which refreshes to fire.
4156 self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
4160 * @name clearAllFilters
4161 * @methodOf ui.grid.core.api:PublicApi
4162 * @description Clears all filters and optionally refreshes the visible rows.
4163 * @param {object} refreshRows Defaults to true.
4164 * @param {object} clearConditions Defaults to false.
4165 * @param {object} clearFlags Defaults to false.
4166 * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
4168 self.api.registerMethod('core', 'clearAllFilters', this.clearAllFilters);
4170 self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]);
4171 self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]);
4172 self.registerDataChangeCallback( self.updateFooterHeightCallback, [uiGridConstants.dataChange.OPTIONS]);
4174 self.registerStyleComputation({
4176 func: self.getFooterStyles
4180 Grid.prototype.calcFooterHeight = function () {
4181 if (!this.hasFooter()) {
4186 if (this.options.showGridFooter) {
4187 height += this.options.gridFooterHeight;
4190 height += this.calcColumnFooterHeight();
4195 Grid.prototype.calcColumnFooterHeight = function () {
4198 if (this.options.showColumnFooter) {
4199 height += this.options.columnFooterHeight;
4205 Grid.prototype.getFooterStyles = function () {
4206 var style = '.grid' + this.id + ' .ui-grid-footer-aggregates-row { height: ' + this.options.columnFooterHeight + 'px; }';
4207 style += ' .grid' + this.id + ' .ui-grid-footer-info { height: ' + this.options.gridFooterHeight + 'px; }';
4211 Grid.prototype.hasFooter = function () {
4212 return this.options.showGridFooter || this.options.showColumnFooter;
4218 * @methodOf ui.grid.class:Grid
4219 * @description Returns true if grid is RightToLeft
4221 Grid.prototype.isRTL = function () {
4228 * @name registerColumnBuilder
4229 * @methodOf ui.grid.class:Grid
4230 * @description When the build creates columns from column definitions, the columnbuilders will be called to add
4231 * additional properties to the column.
4232 * @param {function(colDef, col, gridOptions)} columnBuilder function to be called
4234 Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
4235 this.columnBuilders.push(columnBuilder);
4240 * @name buildColumnDefsFromData
4241 * @methodOf ui.grid.class:Grid
4242 * @description Populates columnDefs from the provided data
4243 * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
4245 Grid.prototype.buildColumnDefsFromData = function (dataRows){
4246 this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
4251 * @name registerRowBuilder
4252 * @methodOf ui.grid.class:Grid
4253 * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
4254 * additional properties to the row.
4255 * @param {function(row, gridOptions)} rowBuilder function to be called
4257 Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
4258 this.rowBuilders.push(rowBuilder);
4264 * @name registerDataChangeCallback
4265 * @methodOf ui.grid.class:Grid
4266 * @description When a data change occurs, the data change callbacks of the specified type
4267 * will be called. The rules are:
4269 * - when the data watch fires, that is considered a ROW change (the data watch only notices
4270 * added or removed rows)
4271 * - when the api is called to inform us of a change, the declared type of that change is used
4272 * - when a cell edit completes, the EDIT callbacks are triggered
4273 * - when the columnDef watch fires, the COLUMN callbacks are triggered
4274 * - when the options watch fires, the OPTIONS callbacks are triggered
4276 * For a given event:
4277 * - ALL calls ROW, EDIT, COLUMN, OPTIONS and ALL callbacks
4278 * - ROW calls ROW and ALL callbacks
4279 * - EDIT calls EDIT and ALL callbacks
4280 * - COLUMN calls COLUMN and ALL callbacks
4281 * - OPTIONS calls OPTIONS and ALL callbacks
4283 * @param {function(grid)} callback function to be called
4284 * @param {array} types the types of data change you want to be informed of. Values from
4285 * the {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4286 * values ( ALL, EDIT, ROW, COLUMN, OPTIONS ). Optional and defaults to ALL
4287 * @returns {function} deregister function - a function that can be called to deregister this callback
4289 Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types, _this) {
4290 var uid = gridUtil.nextUid();
4292 types = [uiGridConstants.dataChange.ALL];
4294 if ( !Array.isArray(types)){
4295 gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
4297 this.dataChangeCallbacks[uid] = { callback: callback, types: types, _this:_this };
4300 var deregisterFunction = function() {
4301 delete self.dataChangeCallbacks[uid];
4303 return deregisterFunction;
4308 * @name callDataChangeCallbacks
4309 * @methodOf ui.grid.class:Grid
4310 * @description Calls the callbacks based on the type of data change that
4311 * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, COLUMN and OPTIONS callbacks if the
4312 * event type is matching, or if the type is ALL.
4313 * @param {string} type the type of event that occurred - one of the
4314 * {@link ui.grid.service:uiGridConstants#properties_dataChange uiGridConstants.dataChange}
4315 * values (ALL, ROW, EDIT, COLUMN, OPTIONS)
4317 Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
4318 angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
4319 if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
4320 callback.types.indexOf( type ) !== -1 ||
4321 type === uiGridConstants.dataChange.ALL ) {
4322 if (callback._this) {
4323 callback.callback.apply(callback._this,this);
4326 callback.callback( this );
4334 * @name notifyDataChange
4335 * @methodOf ui.grid.class:Grid
4336 * @description Notifies us that a data change has occurred, used in the public
4337 * api for users to tell us when they've changed data or some other event that
4338 * our watches cannot pick up
4339 * @param {string} type the type of event that occurred - one of the
4340 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
4342 Grid.prototype.notifyDataChange = function notifyDataChange(type) {
4343 var constants = uiGridConstants.dataChange;
4344 if ( type === constants.ALL ||
4345 type === constants.COLUMN ||
4346 type === constants.EDIT ||
4347 type === constants.ROW ||
4348 type === constants.OPTIONS ){
4349 this.callDataChangeCallbacks( type );
4351 gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
4358 * @name columnRefreshCallback
4359 * @methodOf ui.grid.class:Grid
4360 * @description refreshes the grid when a column refresh
4361 * is notified, which triggers handling of the visible flag.
4362 * This is called on uiGridConstants.dataChange.COLUMN, and is
4363 * registered as a dataChangeCallback in grid.js
4364 * @param {string} name column name
4366 Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
4367 grid.buildColumns();
4368 grid.queueGridRefresh();
4374 * @name processRowsCallback
4375 * @methodOf ui.grid.class:Grid
4376 * @description calls the row processors, specifically
4377 * intended to reset the sorting when an edit is called,
4378 * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
4379 * @param {string} name column name
4381 Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
4382 grid.queueGridRefresh();
4388 * @name updateFooterHeightCallback
4389 * @methodOf ui.grid.class:Grid
4390 * @description recalculates the footer height,
4391 * registered as a dataChangeCallback on uiGridConstants.dataChange.OPTIONS
4392 * @param {string} name column name
4394 Grid.prototype.updateFooterHeightCallback = function updateFooterHeightCallback( grid ){
4395 grid.footerHeight = grid.calcFooterHeight();
4396 grid.columnFooterHeight = grid.calcColumnFooterHeight();
4403 * @methodOf ui.grid.class:Grid
4404 * @description returns a grid column for the column name
4405 * @param {string} name column name
4407 Grid.prototype.getColumn = function getColumn(name) {
4408 var columns = this.columns.filter(function (column) {
4409 return column.colDef.name === name;
4411 return columns.length > 0 ? columns[0] : null;
4417 * @methodOf ui.grid.class:Grid
4418 * @description returns a grid colDef for the column name
4419 * @param {string} name column.field
4421 Grid.prototype.getColDef = function getColDef(name) {
4422 var colDefs = this.options.columnDefs.filter(function (colDef) {
4423 return colDef.name === name;
4425 return colDefs.length > 0 ? colDefs[0] : null;
4431 * @methodOf ui.grid.class:Grid
4432 * @description uses the first row of data to assign colDef.type for any types not defined.
4437 * @propertyOf ui.grid.class:GridOptions.columnDef
4438 * @description the type of the column, used in sorting. If not provided then the
4439 * grid will guess the type. Add this only if the grid guessing is not to your
4440 * satisfaction. One of:
4447 * Note that if you choose date, your dates should be in a javascript date type
4450 Grid.prototype.assignTypes = function(){
4452 self.options.columnDefs.forEach(function (colDef, index) {
4454 //Assign colDef type if not specified
4456 var col = new GridColumn(colDef, index, self);
4457 var firstRow = self.rows.length > 0 ? self.rows[0] : null;
4459 colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
4462 colDef.type = 'string';
4471 * @name isRowHeaderColumn
4472 * @methodOf ui.grid.class:Grid
4473 * @description returns true if the column is a row Header
4474 * @param {object} column column
4476 Grid.prototype.isRowHeaderColumn = function isRowHeaderColumn(column) {
4477 return this.rowHeaderColumns.indexOf(column) !== -1;
4482 * @name addRowHeaderColumn
4483 * @methodOf ui.grid.class:Grid
4484 * @description adds a row header column to the grid
4485 * @param {object} column def
4487 Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef, order) {
4491 if (order === undefined) {
4495 var rowHeaderCol = new GridColumn(colDef, gridUtil.nextUid(), self);
4496 rowHeaderCol.isRowHeader = true;
4498 self.createRightContainer();
4499 rowHeaderCol.renderContainer = 'right';
4502 self.createLeftContainer();
4503 rowHeaderCol.renderContainer = 'left';
4506 // relies on the default column builder being first in array, as it is instantiated
4507 // as part of grid creation
4508 self.columnBuilders[0](colDef,rowHeaderCol,self.options)
4510 rowHeaderCol.enableFiltering = false;
4511 rowHeaderCol.enableSorting = false;
4512 rowHeaderCol.enableHiding = false;
4513 rowHeaderCol.headerPriority = order;
4514 self.rowHeaderColumns.push(rowHeaderCol);
4515 self.rowHeaderColumns = self.rowHeaderColumns.sort(function (a, b) {
4516 return a.headerPriority - b.headerPriority;
4521 self.preCompileCellTemplates();
4522 self.queueGridRefresh();
4529 * @name getOnlyDataColumns
4530 * @methodOf ui.grid.class:Grid
4531 * @description returns all columns except for rowHeader columns
4533 Grid.prototype.getOnlyDataColumns = function getOnlyDataColumns() {
4536 self.columns.forEach(function (col) {
4537 if (self.rowHeaderColumns.indexOf(col) === -1) {
4546 * @name buildColumns
4547 * @methodOf ui.grid.class:Grid
4548 * @description creates GridColumn objects from the columnDefinition. Calls each registered
4549 * columnBuilder to further process the column
4550 * @param {object} options An object contains options to use when building columns
4552 * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions.
4554 * @returns {Promise} a promise to load any needed column resources
4556 Grid.prototype.buildColumns = function buildColumns(opts) {
4558 orderByColumnDefs: false
4561 angular.extend(options, opts);
4563 // gridUtil.logDebug('buildColumns');
4565 var builderPromises = [];
4566 var headerOffset = self.rowHeaderColumns.length;
4569 // Remove any columns for which a columnDef cannot be found
4570 // Deliberately don't use forEach, as it doesn't like splice being called in the middle
4571 // Also don't cache columns.length, as it will change during this operation
4572 for (i = 0; i < self.columns.length; i++){
4573 if (!self.getColDef(self.columns[i].name)) {
4574 self.columns.splice(i, 1);
4579 //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
4580 //rowHeaderColumns is ordered by priority so insert in reverse
4581 for (var j = self.rowHeaderColumns.length - 1; j >= 0; j--) {
4582 self.columns.unshift(self.rowHeaderColumns[j]);
4587 // look at each column def, and update column properties to match. If the column def
4588 // doesn't have a column, then splice in a new gridCol
4589 self.options.columnDefs.forEach(function (colDef, index) {
4590 self.preprocessColDef(colDef);
4591 var col = self.getColumn(colDef.name);
4594 col = new GridColumn(colDef, gridUtil.nextUid(), self);
4595 self.columns.splice(index + headerOffset, 0, col);
4598 // tell updateColumnDef that the column was pre-existing
4599 col.updateColumnDef(colDef, false);
4602 self.columnBuilders.forEach(function (builder) {
4603 builderPromises.push(builder.call(self, colDef, col, self.options));
4607 /*** Reorder columns if necessary ***/
4608 if (!!options.orderByColumnDefs) {
4609 // Create a shallow copy of the columns as a cache
4610 var columnCache = self.columns.slice(0);
4612 // We need to allow for the "row headers" when mapping from the column defs array to the columns array
4613 // If we have a row header in columns[0] and don't account for it we'll overwrite it with the column in columnDefs[0]
4615 // Go through all the column defs, use the shorter of columns length and colDefs.length because if a user has given two columns the same name then
4616 // columns will be shorter than columnDefs. In this situation we'll avoid an error, but the user will still get an unexpected result
4617 var len = Math.min(self.options.columnDefs.length, self.columns.length);
4618 for (i = 0; i < len; i++) {
4619 // If the column at this index has a different name than the column at the same index in the column defs...
4620 if (self.columns[i + headerOffset].name !== self.options.columnDefs[i].name) {
4621 // Replace the one in the cache with the appropriate column
4622 columnCache[i + headerOffset] = self.getColumn(self.options.columnDefs[i].name);
4625 // Otherwise just copy over the one from the initial columns
4626 columnCache[i + headerOffset] = self.columns[i + headerOffset];
4630 // Empty out the columns array, non-destructively
4631 self.columns.length = 0;
4633 // And splice in the updated, ordered columns from the cache
4634 Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache));
4637 return $q.all(builderPromises).then(function(){
4638 if (self.rows.length > 0){
4644 Grid.prototype.preCompileCellTemplate = function(col) {
4646 var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
4647 html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
4649 var compiledElementFn = $compile(html);
4650 col.compiledElementFn = compiledElementFn;
4652 if (col.compiledElementFnDefer) {
4653 col.compiledElementFnDefer.resolve(col.compiledElementFn);
4659 * @name preCompileCellTemplates
4660 * @methodOf ui.grid.class:Grid
4661 * @description pronapiles all cell templates
4663 Grid.prototype.preCompileCellTemplates = function() {
4665 self.columns.forEach(function (col) {
4666 if ( col.cellTemplate ){
4667 self.preCompileCellTemplate( col );
4668 } else if ( col.cellTemplatePromise ){
4669 col.cellTemplatePromise.then( function() {
4670 self.preCompileCellTemplate( col );
4678 * @name getGridQualifiedColField
4679 * @methodOf ui.grid.class:Grid
4680 * @description Returns the $parse-able accessor for a column within its $scope
4681 * @param {GridColumn} col col object
4683 Grid.prototype.getQualifiedColField = function (col) {
4684 var base = 'row.entity';
4685 if ( col.field === uiGridConstants.ENTITY_BINDING ) {
4688 return gridUtil.preEval(base + '.' + col.field);
4693 * @name createLeftContainer
4694 * @methodOf ui.grid.class:Grid
4695 * @description creates the left render container if it doesn't already exist
4697 Grid.prototype.createLeftContainer = function() {
4698 if (!this.hasLeftContainer()) {
4699 this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
4705 * @name createRightContainer
4706 * @methodOf ui.grid.class:Grid
4707 * @description creates the right render container if it doesn't already exist
4709 Grid.prototype.createRightContainer = function() {
4710 if (!this.hasRightContainer()) {
4711 this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
4717 * @name hasLeftContainer
4718 * @methodOf ui.grid.class:Grid
4719 * @description returns true if leftContainer exists
4721 Grid.prototype.hasLeftContainer = function() {
4722 return this.renderContainers.left !== undefined;
4727 * @name hasRightContainer
4728 * @methodOf ui.grid.class:Grid
4729 * @description returns true if rightContainer exists
4731 Grid.prototype.hasRightContainer = function() {
4732 return this.renderContainers.right !== undefined;
4737 * undocumented function
4738 * @name preprocessColDef
4739 * @methodOf ui.grid.class:Grid
4740 * @description defaults the name property from field to maintain backwards compatibility with 2.x
4741 * validates that name or field is present
4743 Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
4746 if (!colDef.field && !colDef.name) {
4747 throw new Error('colDef.name or colDef.field property is required');
4750 //maintain backwards compatibility with 2.x
4751 //field was required in 2.x. now name is required
4752 if (colDef.name === undefined && colDef.field !== undefined) {
4753 // See if the column name already exists:
4754 var newName = colDef.field,
4756 while (self.getColumn(newName)) {
4757 newName = colDef.field + counter.toString();
4760 colDef.name = newName;
4764 // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters
4765 Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
4769 for (var i = 0; i < n.length; i++) {
4770 var nV = nAccessor ? n[i][nAccessor] : n[i];
4773 for (var j = 0; j < o.length; j++) {
4774 var oV = oAccessor ? o[j][oAccessor] : o[j];
4775 if (self.options.rowEquality(nV, oV)) {
4791 * @methodOf ui.grid.class:Grid
4792 * @description returns the GridRow that contains the rowEntity
4793 * @param {object} rowEntity the gridOptions.data array element instance
4794 * @param {array} lookInRows [optional] the rows to look in - if not provided then
4795 * looks in grid.rows
4797 Grid.prototype.getRow = function getRow(rowEntity, lookInRows) {
4800 lookInRows = typeof(lookInRows) === 'undefined' ? self.rows : lookInRows;
4802 var rows = lookInRows.filter(function (row) {
4803 return self.options.rowEquality(row.entity, rowEntity);
4805 return rows.length > 0 ? rows[0] : null;
4812 * @methodOf ui.grid.class:Grid
4813 * @description creates or removes GridRow objects from the newRawData array. Calls each registered
4814 * rowBuilder to further process the row
4815 * @param {array} newRawData Modified set of data
4817 * This method aims to achieve three things:
4818 * 1. the resulting rows array is in the same order as the newRawData, we'll call
4819 * rowsProcessors immediately after to sort the data anyway
4820 * 2. if we have row hashing available, we try to use the rowHash to find the row
4821 * 3. no memory leaks - rows that are no longer in newRawData need to be garbage collected
4823 * The basic logic flow makes use of the newRawData, oldRows and oldHash, and creates
4824 * the newRows and newHash
4827 * newRawData.forEach newEntity
4828 * if (hashing enabled)
4829 * check oldHash for newEntity
4831 * look for old row directly in oldRows
4832 * if !oldRowFound // must be a new row
4834 * append to the newRows and add to newHash
4835 * run the processors
4838 * Rows are identified using the hashKey if configured. If not configured, then rows
4839 * are identified using the gridOptions.rowEquality function
4841 * This method is useful when trying to select rows immediately after loading data without
4842 * using a $timeout/$interval, e.g.:
4844 * $scope.gridOptions.data = someData;
4845 * $scope.gridApi.grid.modifyRows($scope.gridOptions.data);
4846 * $scope.gridApi.selection.selectRow($scope.gridOptions.data[0]);
4848 * OR to persist row selection after data update (e.g. rows selected, new data loaded, want
4849 * originally selected rows to be re-selected))
4851 Grid.prototype.modifyRows = function modifyRows(newRawData) {
4853 var oldRows = self.rows.slice(0);
4854 var oldRowHash = self.rowHashMap || self.createRowHashMap();
4855 self.rowHashMap = self.createRowHashMap();
4856 self.rows.length = 0;
4858 newRawData.forEach( function( newEntity, i ) {
4861 if ( self.options.enableRowHashing ){
4862 // if hashing is enabled, then this row will be in the hash if we already know about it
4863 oldRow = oldRowHash.get( newEntity );
4865 // otherwise, manually search the oldRows to see if we can find this row
4866 oldRow = self.getRow(newEntity, oldRows);
4869 // update newRow to have an entity
4872 newRow.entity = newEntity;
4875 // if we didn't find the row, it must be new, so create it
4877 newRow = self.processRowBuilders(new GridRow(newEntity, i, self));
4880 self.rows.push( newRow );
4881 self.rowHashMap.put( newEntity, newRow );
4886 var p1 = $q.when(self.processRowsProcessors(self.rows))
4887 .then(function (renderableRows) {
4888 return self.setVisibleRows(renderableRows);
4891 var p2 = $q.when(self.processColumnsProcessors(self.columns))
4892 .then(function (renderableColumns) {
4893 return self.setVisibleColumns(renderableColumns);
4896 return $q.all([p1, p2]);
4901 * Private Undocumented Method
4903 * @methodOf ui.grid.class:Grid
4904 * @description adds the newRawData array of rows to the grid and calls all registered
4905 * rowBuilders. this keyword will reference the grid
4907 Grid.prototype.addRows = function addRows(newRawData) {
4910 var existingRowCount = self.rows.length;
4911 for (var i = 0; i < newRawData.length; i++) {
4912 var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
4914 if (self.options.enableRowHashing) {
4915 var found = self.rowHashMap.get(newRow.entity);
4921 self.rows.push(newRow);
4927 * @name processRowBuilders
4928 * @methodOf ui.grid.class:Grid
4929 * @description processes all RowBuilders for the gridRow
4930 * @param {GridRow} gridRow reference to gridRow
4931 * @returns {GridRow} the gridRow with all additional behavior added
4933 Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
4936 self.rowBuilders.forEach(function (builder) {
4937 builder.call(self, gridRow, self.options);
4945 * @name registerStyleComputation
4946 * @methodOf ui.grid.class:Grid
4947 * @description registered a styleComputation function
4949 * If the function returns a value it will be appended into the grid's `<style>` block
4950 * @param {function($scope)} styleComputation function
4952 Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
4953 this.styleComputations.push(styleComputationInfo);
4957 // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4958 // Grid.prototype.registerRowFilter = function(filter) {
4959 // // TODO(c0bra): validate filter?
4961 // this.rowFilters.push(filter);
4964 // Grid.prototype.removeRowFilter = function(filter) {
4965 // var idx = this.rowFilters.indexOf(filter);
4967 // if (typeof(idx) !== 'undefined' && idx !== undefined) {
4968 // this.rowFilters.slice(idx, 1);
4972 // Grid.prototype.processRowFilters = function(rows) {
4974 // self.rowFilters.forEach(function (filter) {
4975 // filter.call(self, rows);
4982 * @name registerRowsProcessor
4983 * @methodOf ui.grid.class:Grid
4986 * Register a "rows processor" function. When the rows are updated,
4987 * the grid calls each registered "rows processor", which has a chance
4988 * to alter the set of rows (sorting, etc) as long as the count is not
4991 * @param {function(renderedRowsToProcess, columns )} processorFunction rows processor function, which
4992 * is run in the context of the grid (i.e. this for the function will be the grid), and must
4993 * return the updated rows list, which is passed to the next processor in the chain
4994 * @param {number} priority the priority of this processor. In general we try to do them in 100s to leave room
4995 * for other people to inject rows processors at intermediate priorities. Lower priority rowsProcessors run earlier.
4997 * At present all rows visible is running at 50, filter is running at 100, sort is at 200, grouping at 400, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
5000 Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor, priority) {
5001 if (!angular.isFunction(processor)) {
5002 throw 'Attempt to register non-function rows processor: ' + processor;
5005 this.rowsProcessors.push({processor: processor, priority: priority});
5006 this.rowsProcessors.sort(function sortByPriority( a, b ){
5007 return a.priority - b.priority;
5013 * @name removeRowsProcessor
5014 * @methodOf ui.grid.class:Grid
5015 * @param {function(renderableRows)} rows processor function
5016 * @description Remove a registered rows processor
5018 Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
5020 this.rowsProcessors.forEach(function(rowsProcessor, index){
5021 if ( rowsProcessor.processor === processor ){
5027 this.rowsProcessors.splice(idx, 1);
5032 * Private Undocumented Method
5033 * @name processRowsProcessors
5034 * @methodOf ui.grid.class:Grid
5035 * @param {Array[GridRow]} The array of "renderable" rows
5036 * @param {Array[GridColumn]} The array of columns
5037 * @description Run all the registered rows processors on the array of renderable rows
5039 Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
5042 // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
5043 var myRenderableRows = renderableRows.slice(0);
5045 // Return myRenderableRows with no processing if we have no rows processors
5046 if (self.rowsProcessors.length === 0) {
5047 return $q.when(myRenderableRows);
5050 // Counter for iterating through rows processors
5053 // Promise for when we're done with all the processors
5054 var finished = $q.defer();
5056 // This function will call the processor in self.rowsProcessors at index 'i', and then
5057 // when done will call the next processor in the list, using the output from the processor
5058 // at i as the argument for 'renderedRowsToProcess' on the next iteration.
5060 // If we're at the end of the list of processors, we resolve our 'finished' callback with
5062 function startProcessor(i, renderedRowsToProcess) {
5063 // Get the processor at 'i'
5064 var processor = self.rowsProcessors[i].processor;
5066 // Call the processor, passing in the rows to process and the current columns
5067 // (note: it's wrapped in $q.when() in case the processor does not return a promise)
5068 return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
5069 .then(function handleProcessedRows(processedRows) {
5071 if (!processedRows) {
5072 throw "Processor at index " + i + " did not return a set of renderable rows";
5075 if (!angular.isArray(processedRows)) {
5076 throw "Processor at index " + i + " did not return an array";
5079 // Processor is done, increment the counter
5082 // If we're not done with the processors, call the next one
5083 if (i <= self.rowsProcessors.length - 1) {
5084 return startProcessor(i, processedRows);
5086 // We're done! Resolve the 'finished' promise
5088 finished.resolve(processedRows);
5093 // Start on the first processor
5094 startProcessor(0, myRenderableRows);
5096 return finished.promise;
5099 Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
5102 // Reset all the render container row caches
5103 for (var i in self.renderContainers) {
5104 var container = self.renderContainers[i];
5106 container.canvasHeightShouldUpdate = true;
5108 if ( typeof(container.visibleRowCache) === 'undefined' ){
5109 container.visibleRowCache = [];
5111 container.visibleRowCache.length = 0;
5115 // rows.forEach(function (row) {
5116 for (var ri = 0; ri < rows.length; ri++) {
5119 var targetContainer = (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) ? row.renderContainer : 'body';
5121 // If the row is visible
5123 self.renderContainers[targetContainer].visibleRowCache.push(row);
5126 self.api.core.raise.rowsVisibleChanged(this.api);
5127 self.api.core.raise.rowsRendered(this.api);
5132 * @name registerColumnsProcessor
5133 * @methodOf ui.grid.class:Grid
5134 * @param {function(renderedColumnsToProcess, rows)} columnProcessor column processor function, which
5135 * is run in the context of the grid (i.e. this for the function will be the grid), and
5136 * which must return an updated renderedColumnsToProcess which can be passed to the next processor
5138 * @param {number} priority the priority of this processor. In general we try to do them in 100s to leave room
5139 * for other people to inject columns processors at intermediate priorities. Lower priority columnsProcessors run earlier.
5141 * At present all rows visible is running at 50, filter is running at 100, sort is at 200, grouping at 400, selectable rows at 500, pagination at 900 (pagination will generally want to be last)
5144 Register a "columns processor" function. When the columns are updated,
5145 the grid calls each registered "columns processor", which has a chance
5146 to alter the set of columns, as long as the count is not modified.
5148 Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor, priority) {
5149 if (!angular.isFunction(processor)) {
5150 throw 'Attempt to register non-function rows processor: ' + processor;
5153 this.columnsProcessors.push({processor: processor, priority: priority});
5154 this.columnsProcessors.sort(function sortByPriority( a, b ){
5155 return a.priority - b.priority;
5159 Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
5160 var idx = this.columnsProcessors.indexOf(processor);
5162 if (typeof(idx) !== 'undefined' && idx !== undefined) {
5163 this.columnsProcessors.splice(idx, 1);
5167 Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
5170 // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
5171 var myRenderableColumns = renderableColumns.slice(0);
5173 // Return myRenderableRows with no processing if we have no rows processors
5174 if (self.columnsProcessors.length === 0) {
5175 return $q.when(myRenderableColumns);
5178 // Counter for iterating through rows processors
5181 // Promise for when we're done with all the processors
5182 var finished = $q.defer();
5184 // This function will call the processor in self.rowsProcessors at index 'i', and then
5185 // when done will call the next processor in the list, using the output from the processor
5186 // at i as the argument for 'renderedRowsToProcess' on the next iteration.
5188 // If we're at the end of the list of processors, we resolve our 'finished' callback with
5190 function startProcessor(i, renderedColumnsToProcess) {
5191 // Get the processor at 'i'
5192 var processor = self.columnsProcessors[i].processor;
5194 // Call the processor, passing in the rows to process and the current columns
5195 // (note: it's wrapped in $q.when() in case the processor does not return a promise)
5196 return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
5197 .then(function handleProcessedRows(processedColumns) {
5199 if (!processedColumns) {
5200 throw "Processor at index " + i + " did not return a set of renderable rows";
5203 if (!angular.isArray(processedColumns)) {
5204 throw "Processor at index " + i + " did not return an array";
5207 // Processor is done, increment the counter
5210 // If we're not done with the processors, call the next one
5211 if (i <= self.columnsProcessors.length - 1) {
5212 return startProcessor(i, myRenderableColumns);
5214 // We're done! Resolve the 'finished' promise
5216 finished.resolve(myRenderableColumns);
5221 // Start on the first processor
5222 startProcessor(0, myRenderableColumns);
5224 return finished.promise;
5227 Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
5228 // gridUtil.logDebug('setVisibleColumns');
5232 // Reset all the render container row caches
5233 for (var i in self.renderContainers) {
5234 var container = self.renderContainers[i];
5236 container.visibleColumnCache.length = 0;
5239 for (var ci = 0; ci < columns.length; ci++) {
5240 var column = columns[ci];
5242 // If the column is visible
5243 if (column.visible) {
5244 // If the column has a container specified
5245 if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
5246 self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
5248 // If not, put it into the body container
5250 self.renderContainers.body.visibleColumnCache.push(column);
5258 * @name handleWindowResize
5259 * @methodOf ui.grid.class:Grid
5260 * @description Triggered when the browser window resizes; automatically resizes the grid
5261 * @returns {Promise} A resolved promise once the window resize has completed.
5263 Grid.prototype.handleWindowResize = function handleWindowResize($event) {
5266 self.gridWidth = gridUtil.elementWidth(self.element);
5267 self.gridHeight = gridUtil.elementHeight(self.element);
5269 return self.queueRefresh();
5274 * @name queueRefresh
5275 * @methodOf ui.grid.class:Grid
5276 * @description queues a grid refreshCanvas, a way of debouncing all the refreshes we might otherwise issue
5278 Grid.prototype.queueRefresh = function queueRefresh() {
5281 if (self.refreshCanceller) {
5282 $timeout.cancel(self.refreshCanceller);
5285 self.refreshCanceller = $timeout(function () {
5286 self.refreshCanvas(true);
5289 self.refreshCanceller.then(function () {
5290 self.refreshCanceller = null;
5293 return self.refreshCanceller;
5299 * @name queueGridRefresh
5300 * @methodOf ui.grid.class:Grid
5301 * @description queues a grid refresh, a way of debouncing all the refreshes we might otherwise issue
5303 Grid.prototype.queueGridRefresh = function queueGridRefresh() {
5306 if (self.gridRefreshCanceller) {
5307 $timeout.cancel(self.gridRefreshCanceller);
5310 self.gridRefreshCanceller = $timeout(function () {
5314 self.gridRefreshCanceller.then(function () {
5315 self.gridRefreshCanceller = null;
5318 return self.gridRefreshCanceller;
5324 * @name updateCanvasHeight
5325 * @methodOf ui.grid.class:Grid
5326 * @description flags all render containers to update their canvas height
5328 Grid.prototype.updateCanvasHeight = function updateCanvasHeight() {
5331 for (var containerId in self.renderContainers) {
5332 if (self.renderContainers.hasOwnProperty(containerId)) {
5333 var container = self.renderContainers[containerId];
5334 container.canvasHeightShouldUpdate = true;
5342 * @methodOf ui.grid.class:Grid
5343 * @description calls each styleComputation function
5345 // TODO: this used to take $scope, but couldn't see that it was used
5346 Grid.prototype.buildStyles = function buildStyles() {
5347 // gridUtil.logDebug('buildStyles');
5351 self.customStyles = '';
5353 self.styleComputations
5354 .sort(function(a, b) {
5355 if (a.priority === null) { return 1; }
5356 if (b.priority === null) { return -1; }
5357 if (a.priority === null && b.priority === null) { return 0; }
5358 return a.priority - b.priority;
5360 .forEach(function (compInfo) {
5361 // this used to provide $scope as a second parameter, but I couldn't find any
5362 // style builders that used it, so removed it as part of moving to grid from controller
5363 var ret = compInfo.func.call(self);
5365 if (angular.isString(ret)) {
5366 self.customStyles += '\n' + ret;
5372 Grid.prototype.minColumnsToRender = function minColumnsToRender() {
5374 var viewport = this.getViewportWidth();
5378 self.columns.forEach(function(col, i) {
5379 if (totalWidth < viewport) {
5380 totalWidth += col.drawnWidth;
5385 for (var j = i; j >= i - min; j--) {
5386 currWidth += self.columns[j].drawnWidth;
5388 if (currWidth < viewport) {
5397 Grid.prototype.getBodyHeight = function getBodyHeight() {
5398 // Start with the viewportHeight
5399 var bodyHeight = this.getViewportHeight();
5401 // Add the horizontal scrollbar height if there is one
5402 //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5403 // bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
5409 // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
5410 // TODO(c0bra): account for footer height
5411 Grid.prototype.getViewportHeight = function getViewportHeight() {
5414 var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
5416 // Account for native horizontal scrollbar, if present
5417 //if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
5418 // viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
5421 var adjustment = self.getViewportAdjustment();
5423 viewPortHeight = viewPortHeight + adjustment.height;
5425 //gridUtil.logDebug('viewPortHeight', viewPortHeight);
5427 return viewPortHeight;
5430 Grid.prototype.getViewportWidth = function getViewportWidth() {
5433 var viewPortWidth = this.gridWidth;
5435 //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5436 // viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
5439 var adjustment = self.getViewportAdjustment();
5441 viewPortWidth = viewPortWidth + adjustment.width;
5443 //gridUtil.logDebug('getviewPortWidth', viewPortWidth);
5445 return viewPortWidth;
5448 Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
5449 var viewPortWidth = this.getViewportWidth();
5451 //if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
5452 // viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
5455 return viewPortWidth;
5458 Grid.prototype.addVerticalScrollSync = function (containerId, callBackFn) {
5459 this.verticalScrollSyncCallBackFns[containerId] = callBackFn;
5462 Grid.prototype.addHorizontalScrollSync = function (containerId, callBackFn) {
5463 this.horizontalScrollSyncCallBackFns[containerId] = callBackFn;
5467 * Scroll needed containers by calling their ScrollSyncs
5468 * @param sourceContainerId the containerId that has already set it's top/left.
5469 * can be empty string which means all containers need to set top/left
5470 * @param scrollEvent
5472 Grid.prototype.scrollContainers = function (sourceContainerId, scrollEvent) {
5474 if (scrollEvent.y) {
5475 //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5476 var verts = ['body','left', 'right'];
5478 this.flagScrollingVertically(scrollEvent);
5480 if (sourceContainerId === 'body') {
5481 verts = ['left', 'right'];
5483 else if (sourceContainerId === 'left') {
5484 verts = ['body', 'right'];
5486 else if (sourceContainerId === 'right') {
5487 verts = ['body', 'left'];
5490 for (var i = 0; i < verts.length; i++) {
5492 if (this.verticalScrollSyncCallBackFns[id]) {
5493 this.verticalScrollSyncCallBackFns[id](scrollEvent);
5499 if (scrollEvent.x) {
5500 //default for no container Id (ex. mousewheel means that all containers must set scrollTop/Left)
5501 var horizs = ['body','bodyheader', 'bodyfooter'];
5503 this.flagScrollingHorizontally(scrollEvent);
5504 if (sourceContainerId === 'body') {
5505 horizs = ['bodyheader', 'bodyfooter'];
5508 for (var j = 0; j < horizs.length; j++) {
5509 var idh = horizs[j];
5510 if (this.horizontalScrollSyncCallBackFns[idh]) {
5511 this.horizontalScrollSyncCallBackFns[idh](scrollEvent);
5519 Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
5520 this.viewportAdjusters.push(func);
5523 Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
5524 var idx = this.viewportAdjusters.indexOf(func);
5526 if (typeof(idx) !== 'undefined' && idx !== undefined) {
5527 this.viewportAdjusters.splice(idx, 1);
5531 Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
5534 var adjustment = { height: 0, width: 0 };
5536 self.viewportAdjusters.forEach(function (func) {
5537 adjustment = func.call(this, adjustment);
5543 Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
5546 // this.rows.forEach(function (row) {
5547 // if (row.visible) {
5552 // return this.visibleRowCache.length;
5553 return this.renderContainers.body.visibleRowCache.length;
5556 Grid.prototype.getVisibleRows = function getVisibleRows() {
5557 return this.renderContainers.body.visibleRowCache;
5560 Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
5563 // this.rows.forEach(function (row) {
5564 // if (row.visible) {
5569 // return this.visibleRowCache.length;
5570 return this.renderContainers.body.visibleColumnCache.length;
5574 Grid.prototype.searchRows = function searchRows(renderableRows) {
5575 return rowSearcher.search(this, renderableRows, this.columns);
5578 Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
5579 return rowSorter.sort(this, renderableRows, this.columns);
5584 * @name getCellValue
5585 * @methodOf ui.grid.class:Grid
5586 * @description Gets the value of a cell for a particular row and column
5587 * @param {GridRow} row Row to access
5588 * @param {GridColumn} col Column to access
5590 Grid.prototype.getCellValue = function getCellValue(row, col){
5591 if ( typeof(row.entity[ '$$' + col.uid ]) !== 'undefined' ) {
5592 return row.entity[ '$$' + col.uid].rendered;
5593 } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined' ){
5594 return row.entity[col.field];
5596 if (!col.cellValueGetterCache) {
5597 col.cellValueGetterCache = $parse(row.getEntityQualifiedColField(col));
5600 return col.cellValueGetterCache(row);
5606 * @name getCellDisplayValue
5607 * @methodOf ui.grid.class:Grid
5608 * @description Gets the displayed value of a cell after applying any the `cellFilter`
5609 * @param {GridRow} row Row to access
5610 * @param {GridColumn} col Column to access
5612 Grid.prototype.getCellDisplayValue = function getCellDisplayValue(row, col) {
5613 if ( !col.cellDisplayGetterCache ) {
5614 var custom_filter = col.cellFilter ? " | " + col.cellFilter : "";
5616 if (typeof(row.entity['$$' + col.uid]) !== 'undefined') {
5617 col.cellDisplayGetterCache = $parse(row.entity['$$' + col.uid].rendered + custom_filter);
5618 } else if (this.options.flatEntityAccess && typeof(col.field) !== 'undefined') {
5619 col.cellDisplayGetterCache = $parse(row.entity[col.field] + custom_filter);
5621 col.cellDisplayGetterCache = $parse(row.getEntityQualifiedColField(col) + custom_filter);
5625 return col.cellDisplayGetterCache(row);
5629 Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
5633 self.columns.forEach(function (col) {
5634 if (col.sort && col.sort.priority !== undefined && col.sort.priority >= p) {
5635 p = col.sort.priority + 1;
5644 * @name resetColumnSorting
5645 * @methodOf ui.grid.class:Grid
5646 * @description Return the columns that the grid is currently being sorted by
5647 * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
5649 Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
5652 self.columns.forEach(function (col) {
5653 if (col !== excludeCol && !col.suppressRemoveSort) {
5661 * @name getColumnSorting
5662 * @methodOf ui.grid.class:Grid
5663 * @description Return the columns that the grid is currently being sorted by
5664 * @returns {Array[GridColumn]} An array of GridColumn objects
5666 Grid.prototype.getColumnSorting = function getColumnSorting() {
5669 var sortedCols = [], myCols;
5671 // Iterate through all the columns, sorted by priority
5672 // Make local copy of column list, because sorting is in-place and we do not want to
5673 // change the original sequence of columns
5674 myCols = self.columns.slice(0);
5675 myCols.sort(rowSorter.prioritySort).forEach(function (col) {
5676 if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
5677 sortedCols.push(col);
5687 * @methodOf ui.grid.class:Grid
5688 * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
5689 * Emits the sortChanged event whenever the sort criteria are changed.
5690 * @param {GridColumn} column Column to set the sorting on
5691 * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
5692 * If not provided, the column will iterate through the sort directions
5693 * specified in the {@link ui.grid.class:GridOptions.columnDef#sortDirectionCycle sortDirectionCycle} attribute.
5694 * @param {boolean} [add] Add this column to the sorting. If not provided or set to `false`, the Grid will reset any existing sorting and sort
5695 * by this column only
5696 * @returns {Promise} A resolved promise that supplies the column.
5699 Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
5703 if (typeof(column) === 'undefined' || !column) {
5704 throw new Error('No column parameter provided');
5707 // Second argument can either be a direction or whether to add this column to the existing sort.
5708 // If it's a boolean, it's an add, otherwise, it's a direction
5709 if (typeof(directionOrAdd) === 'boolean') {
5710 add = directionOrAdd;
5713 direction = directionOrAdd;
5717 self.resetColumnSorting(column);
5718 column.sort.priority = undefined;
5719 // Get the actual priority since there may be columns which have suppressRemoveSort set
5720 column.sort.priority = self.getNextColumnSortPriority();
5722 else if (column.sort.priority === undefined){
5723 column.sort.priority = self.getNextColumnSortPriority();
5727 // Find the current position in the cycle (or -1).
5728 var i = column.sortDirectionCycle.indexOf(column.sort.direction ? column.sort.direction : null);
5729 // Proceed to the next position in the cycle (or start at the beginning).
5730 i = (i+1) % column.sortDirectionCycle.length;
5731 // If suppressRemoveSort is set, and the next position in the cycle would
5732 // remove the sort, skip it.
5733 if (column.colDef && column.suppressRemoveSort && !column.sortDirectionCycle[i]) {
5734 i = (i+1) % column.sortDirectionCycle.length;
5737 if (column.sortDirectionCycle[i]) {
5738 column.sort.direction = column.sortDirectionCycle[i];
5740 removeSortOfColumn(column, self);
5744 column.sort.direction = direction;
5747 self.api.core.raise.sortChanged( self, self.getColumnSorting() );
5749 return $q.when(column);
5752 var removeSortOfColumn = function removeSortOfColumn(column, grid) {
5753 //Decrease priority for every col where priority is higher than the removed sort's priority.
5754 grid.columns.forEach(function (col) {
5755 if (col.sort && col.sort.priority !== undefined && col.sort.priority > column.sort.priority) {
5756 col.sort.priority -= 1;
5765 * communicate to outside world that we are done with initial rendering
5767 Grid.prototype.renderingComplete = function(){
5768 if (angular.isFunction(this.options.onRegisterApi)) {
5769 this.options.onRegisterApi(this.api);
5771 this.api.core.raise.renderingComplete( this.api );
5774 Grid.prototype.createRowHashMap = function createRowHashMap() {
5777 var hashMap = new RowHashMap();
5778 hashMap.grid = self;
5787 * @methodOf ui.grid.class:Grid
5788 * @description Refresh the rendered grid on screen.
5789 * @param {boolean} [rowsAltered] Optional flag for refreshing when the number of rows has changed.
5791 Grid.prototype.refresh = function refresh(rowsAltered) {
5794 var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
5795 self.setVisibleRows(renderableRows);
5798 var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
5799 self.setVisibleColumns(renderableColumns);
5802 return $q.all([p1, p2]).then(function () {
5803 self.redrawInPlace(rowsAltered);
5805 self.refreshCanvas(true);
5812 * @methodOf ui.grid.class:Grid
5813 * @description Refresh the rendered rows on screen? Note: not functional at present
5814 * @returns {promise} promise that is resolved when render completes?
5817 Grid.prototype.refreshRows = function refreshRows() {
5820 return self.processRowsProcessors(self.rows)
5821 .then(function (renderableRows) {
5822 self.setVisibleRows(renderableRows);
5824 self.redrawInPlace();
5826 self.refreshCanvas( true );
5832 * @name refreshCanvas
5833 * @methodOf ui.grid.class:Grid
5834 * @description Builds all styles and recalculates much of the grid sizing
5835 * @param {object} buildStyles optional parameter. Use TBD
5836 * @returns {promise} promise that is resolved when the canvas
5837 * has been refreshed
5840 Grid.prototype.refreshCanvas = function(buildStyles) {
5849 // Get all the header heights
5850 var containerHeadersToRecalc = [];
5851 for (var containerId in self.renderContainers) {
5852 if (self.renderContainers.hasOwnProperty(containerId)) {
5853 var container = self.renderContainers[containerId];
5855 // Skip containers that have no canvasWidth set yet
5856 if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5860 if (container.header || container.headerCanvas) {
5861 container.explicitHeaderHeight = container.explicitHeaderHeight || null;
5862 container.explicitHeaderCanvasHeight = container.explicitHeaderCanvasHeight || null;
5864 containerHeadersToRecalc.push(container);
5871 * Here we loop through the headers, measuring each element as well as any header "canvas" it has within it.
5873 * If any header is less than the largest header height, it will be resized to that so that we don't have headers
5874 * with different heights, which looks like a rendering problem
5876 * We'll do the same thing with the header canvases, and give the header CELLS an explicit height if their canvas
5877 * is smaller than the largest canvas height. That was header cells without extra controls like filtering don't
5878 * appear shorter than other cells.
5881 if (containerHeadersToRecalc.length > 0) {
5882 // Build the styles without the explicit header heights
5887 // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
5888 $timeout(function() {
5889 // var oldHeaderHeight = self.grid.headerHeight;
5890 // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
5892 var rebuildStyles = false;
5894 // Get all the header heights
5895 var maxHeaderHeight = 0;
5896 var maxHeaderCanvasHeight = 0;
5898 var getHeight = function(oldVal, newVal){
5899 if ( oldVal !== newVal){
5900 rebuildStyles = true;
5904 for (i = 0; i < containerHeadersToRecalc.length; i++) {
5905 container = containerHeadersToRecalc[i];
5907 // Skip containers that have no canvasWidth set yet
5908 if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
5912 if (container.header) {
5913 var headerHeight = container.headerHeight = getHeight(container.headerHeight, parseInt(gridUtil.outerElementHeight(container.header), 10));
5915 // Get the "inner" header height, that is the height minus the top and bottom borders, if present. We'll use it to make sure all the headers have a consistent height
5916 var topBorder = gridUtil.getBorderSize(container.header, 'top');
5917 var bottomBorder = gridUtil.getBorderSize(container.header, 'bottom');
5918 var innerHeaderHeight = parseInt(headerHeight - topBorder - bottomBorder, 10);
5920 innerHeaderHeight = innerHeaderHeight < 0 ? 0 : innerHeaderHeight;
5922 container.innerHeaderHeight = innerHeaderHeight;
5924 // If the header doesn't have an explicit height set, save the largest header height for use later
5925 // Explicit header heights are based off of the max we are calculating here. We never want to base the max on something we're setting explicitly
5926 if (!container.explicitHeaderHeight && innerHeaderHeight > maxHeaderHeight) {
5927 maxHeaderHeight = innerHeaderHeight;
5931 if (container.headerCanvas) {
5932 var headerCanvasHeight = container.headerCanvasHeight = getHeight(container.headerCanvasHeight, parseInt(gridUtil.outerElementHeight(container.headerCanvas), 10));
5935 // If the header doesn't have an explicit canvas height, save the largest header canvas height for use later
5936 // Explicit header heights are based off of the max we are calculating here. We never want to base the max on something we're setting explicitly
5937 if (!container.explicitHeaderCanvasHeight && headerCanvasHeight > maxHeaderCanvasHeight) {
5938 maxHeaderCanvasHeight = headerCanvasHeight;
5943 // Go through all the headers
5944 for (i = 0; i < containerHeadersToRecalc.length; i++) {
5945 container = containerHeadersToRecalc[i];
5948 1. We have a max header height
5949 2. This container has a header height defined
5950 3. And either this container has an explicit header height set, OR its header height is less than the max
5954 Give this container's header an explicit height so it will line up with the tallest header
5957 maxHeaderHeight > 0 && typeof(container.headerHeight) !== 'undefined' && container.headerHeight !== null &&
5958 (container.explicitHeaderHeight || container.headerHeight < maxHeaderHeight)
5960 container.explicitHeaderHeight = getHeight(container.explicitHeaderHeight, maxHeaderHeight);
5963 // Do the same as above except for the header canvas
5965 maxHeaderCanvasHeight > 0 && typeof(container.headerCanvasHeight) !== 'undefined' && container.headerCanvasHeight !== null &&
5966 (container.explicitHeaderCanvasHeight || container.headerCanvasHeight < maxHeaderCanvasHeight)
5968 container.explicitHeaderCanvasHeight = getHeight(container.explicitHeaderCanvasHeight, maxHeaderCanvasHeight);
5972 // Rebuild styles if the header height has changed
5973 // The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
5974 if (buildStyles && rebuildStyles) {
5982 // Timeout still needs to be here to trigger digest after styles have been rebuilt
5983 $timeout(function() {
5994 * @name redrawCanvas
5995 * @methodOf ui.grid.class:Grid
5996 * @description Redraw the rows and columns based on our current scroll position
5997 * @param {boolean} [rowsAdded] Optional to indicate rows are added and the scroll percentage must be recalculated
6000 Grid.prototype.redrawInPlace = function redrawInPlace(rowsAdded) {
6001 // gridUtil.logDebug('redrawInPlace');
6005 for (var i in self.renderContainers) {
6006 var container = self.renderContainers[i];
6008 // gridUtil.logDebug('redrawing container', i);
6011 container.adjustRows(container.prevScrollTop, null);
6012 container.adjustColumns(container.prevScrollLeft, null);
6015 container.adjustRows(null, container.prevScrolltopPercentage);
6016 container.adjustColumns(null, container.prevScrollleftPercentage);
6023 * @name hasLeftContainerColumns
6024 * @methodOf ui.grid.class:Grid
6025 * @description returns true if leftContainer has columns
6027 Grid.prototype.hasLeftContainerColumns = function () {
6028 return this.hasLeftContainer() && this.renderContainers.left.renderedColumns.length > 0;
6033 * @name hasRightContainerColumns
6034 * @methodOf ui.grid.class:Grid
6035 * @description returns true if rightContainer has columns
6037 Grid.prototype.hasRightContainerColumns = function () {
6038 return this.hasRightContainer() && this.renderContainers.right.renderedColumns.length > 0;
6043 * @methodOf ui.grid.class:Grid
6044 * @name scrollToIfNecessary
6045 * @description Scrolls the grid to make a certain row and column combo visible,
6046 * in the case that it is not completely visible on the screen already.
6047 * @param {GridRow} gridRow row to make visible
6048 * @param {GridCol} gridCol column to make visible
6049 * @returns {promise} a promise that is resolved when scrolling is complete
6051 Grid.prototype.scrollToIfNecessary = function (gridRow, gridCol) {
6054 var scrollEvent = new ScrollEvent(self, 'uiGrid.scrollToIfNecessary');
6056 // Alias the visible row and column caches
6057 var visRowCache = self.renderContainers.body.visibleRowCache;
6058 var visColCache = self.renderContainers.body.visibleColumnCache;
6060 /*-- Get the top, left, right, and bottom "scrolled" edges of the grid --*/
6062 // The top boundary is the current Y scroll position PLUS the header height, because the header can obscure rows when the grid is scrolled downwards
6063 var topBound = self.renderContainers.body.prevScrollTop + self.headerHeight;
6065 // Don't the let top boundary be less than 0
6066 topBound = (topBound < 0) ? 0 : topBound;
6068 // The left boundary is the current X scroll position
6069 var leftBound = self.renderContainers.body.prevScrollLeft;
6071 // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
6072 // Basically this is the viewport height added on to the scroll position
6073 var bottomBound = self.renderContainers.body.prevScrollTop + self.gridHeight - self.renderContainers.body.headerHeight - self.footerHeight - self.scrollbarWidth;
6075 // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
6076 //if (self.horizontalScrollbarHeight) {
6077 // bottomBound = bottomBound - self.horizontalScrollbarHeight;
6080 // The right position is the current X scroll position minus the grid width
6081 var rightBound = self.renderContainers.body.prevScrollLeft + Math.ceil(self.renderContainers.body.getViewportWidth());
6083 // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
6084 //if (self.verticalScrollbarWidth) {
6085 // rightBound = rightBound - self.verticalScrollbarWidth;
6088 // We were given a row to scroll to
6089 if (gridRow !== null) {
6090 // This is the index of the row we want to scroll to, within the list of rows that can be visible
6091 var seekRowIndex = visRowCache.indexOf(gridRow);
6093 // Total vertical scroll length of the grid
6094 var scrollLength = (self.renderContainers.body.getCanvasHeight() - self.renderContainers.body.getViewportHeight());
6096 // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
6097 //if (self.horizontalScrollbarHeight && self.horizontalScrollbarHeight > 0) {
6098 // scrollLength = scrollLength + self.horizontalScrollbarHeight;
6101 // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
6102 var pixelsToSeeRow = (seekRowIndex * self.options.rowHeight + self.headerHeight);
6104 // Don't let the pixels required to see the row be less than zero
6105 pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
6107 var scrollPixels, percentage;
6109 // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the self...
6110 if (pixelsToSeeRow < topBound) {
6111 // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
6112 // to get the full position we need
6113 scrollPixels = self.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
6115 // Turn the scroll position into a percentage and make it an argument for a scroll event
6116 percentage = scrollPixels / scrollLength;
6117 scrollEvent.y = { percentage: percentage };
6119 // Otherwise if the scroll position we need to see the row is MORE than the bottom boundary, i.e. obscured below the bottom of the self...
6120 else if (pixelsToSeeRow > bottomBound) {
6121 // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
6122 // to get the full position we need
6123 scrollPixels = pixelsToSeeRow - bottomBound + self.renderContainers.body.prevScrollTop;
6125 // Turn the scroll position into a percentage and make it an argument for a scroll event
6126 percentage = scrollPixels / scrollLength;
6127 scrollEvent.y = { percentage: percentage };
6131 // We were given a column to scroll to
6132 if (gridCol !== null) {
6133 // This is the index of the column we want to scroll to, within the list of columns that can be visible
6134 var seekColumnIndex = visColCache.indexOf(gridCol);
6136 // Total horizontal scroll length of the grid
6137 var horizScrollLength = (self.renderContainers.body.getCanvasWidth() - self.renderContainers.body.getViewportWidth());
6139 // This is the minimum amount of pixels we need to scroll horizontal in order to see this column
6140 var columnLeftEdge = 0;
6141 for (var i = 0; i < seekColumnIndex; i++) {
6142 var col = visColCache[i];
6143 columnLeftEdge += col.drawnWidth;
6145 columnLeftEdge = (columnLeftEdge < 0) ? 0 : columnLeftEdge;
6147 var columnRightEdge = columnLeftEdge + gridCol.drawnWidth;
6149 // Don't let the pixels required to see the column be less than zero
6150 columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
6152 var horizScrollPixels, horizPercentage;
6154 // If the scroll position we need to see the column is LESS than the left boundary, i.e. obscured before the left of the self...
6155 if (columnLeftEdge < leftBound) {
6156 // Get the different between the left boundary and the required scroll position and subtract it from the current scroll position\
6157 // to get the full position we need
6158 horizScrollPixels = self.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
6160 // Turn the scroll position into a percentage and make it an argument for a scroll event
6161 horizPercentage = horizScrollPixels / horizScrollLength;
6162 horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
6163 scrollEvent.x = { percentage: horizPercentage };
6165 // Otherwise if the scroll position we need to see the column is MORE than the right boundary, i.e. obscured after the right of the self...
6166 else if (columnRightEdge > rightBound) {
6167 // Get the different between the right boundary and the required scroll position and add it to the current scroll position
6168 // to get the full position we need
6169 horizScrollPixels = columnRightEdge - rightBound + self.renderContainers.body.prevScrollLeft;
6171 // Turn the scroll position into a percentage and make it an argument for a scroll event
6172 horizPercentage = horizScrollPixels / horizScrollLength;
6173 horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
6174 scrollEvent.x = { percentage: horizPercentage };
6178 var deferred = $q.defer();
6180 // If we need to scroll on either the x or y axes, fire a scroll event
6181 if (scrollEvent.y || scrollEvent.x) {
6182 scrollEvent.withDelay = false;
6183 self.scrollContainers('',scrollEvent);
6184 var dereg = self.api.core.on.scrollEnd(null,function() {
6185 deferred.resolve(scrollEvent);
6193 return deferred.promise;
6198 * @methodOf ui.grid.class:Grid
6200 * @description Scroll the grid such that the specified
6201 * row and column is in view
6202 * @param {object} rowEntity gridOptions.data[] array instance to make visible
6203 * @param {object} colDef to make visible
6204 * @returns {promise} a promise that is resolved after any scrolling is finished
6206 Grid.prototype.scrollTo = function (rowEntity, colDef) {
6207 var gridRow = null, gridCol = null;
6209 if (rowEntity !== null && typeof(rowEntity) !== 'undefined' ) {
6210 gridRow = this.getRow(rowEntity);
6213 if (colDef !== null && typeof(colDef) !== 'undefined' ) {
6214 gridCol = this.getColumn(colDef.name ? colDef.name : colDef.field);
6216 return this.scrollToIfNecessary(gridRow, gridCol);
6221 * @name clearAllFilters
6222 * @methodOf ui.grid.class:Grid
6223 * @description Clears all filters and optionally refreshes the visible rows.
6224 * @param {object} refreshRows Defaults to true.
6225 * @param {object} clearConditions Defaults to false.
6226 * @param {object} clearFlags Defaults to false.
6227 * @returns {promise} If `refreshRows` is true, returns a promise of the rows refreshing.
6229 Grid.prototype.clearAllFilters = function clearAllFilters(refreshRows, clearConditions, clearFlags) {
6230 // Default `refreshRows` to true because it will be the most commonly desired behaviour.
6231 if (refreshRows === undefined) {
6234 if (clearConditions === undefined) {
6235 clearConditions = false;
6237 if (clearFlags === undefined) {
6241 this.columns.forEach(function(column) {
6242 column.filters.forEach(function(filter) {
6243 filter.term = undefined;
6245 if (clearConditions) {
6246 filter.condition = undefined;
6250 filter.flags = undefined;
6256 return this.refreshRows();
6261 // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
6262 function RowHashMap() {}
6264 RowHashMap.prototype = {
6266 * Store key value pair
6267 * @param key key to store can be any type
6268 * @param value value to store can be any type
6270 put: function(key, value) {
6271 this[this.grid.options.rowIdentity(key)] = value;
6276 * @returns {Object} the value for the key
6278 get: function(key) {
6279 return this[this.grid.options.rowIdentity(key)];
6283 * Remove the key/value pair
6286 remove: function(key) {
6287 var value = this[key = this.grid.options.rowIdentity(key)];
6303 angular.module('ui.grid')
6304 .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
6305 function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
6308 * @name ui.grid.class:GridApi
6309 * @description GridApi provides the ability to register public methods events inside the grid and allow
6310 * for other components to use the api via featureName.raise.methodName and featureName.on.eventName(function(args){}.
6312 * To listen to events, you must add a callback to gridOptions.onRegisterApi
6314 * $scope.gridOptions.onRegisterApi = function(gridApi){
6315 * gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){
6316 * $log.log('navigation event');
6320 * @param {object} grid grid that owns api
6322 var GridApi = function GridApi(grid) {
6324 this.listeners = [];
6328 * @name renderingComplete
6329 * @methodOf ui.grid.core.api:PublicApi
6330 * @description Rendering is complete, called at the same
6331 * time as `onRegisterApi`, but provides a way to obtain
6332 * that same event within features without stopping end
6333 * users from getting at the onRegisterApi method.
6335 * Included in gridApi so that it's always there - otherwise
6336 * there is still a timing problem with when a feature can
6339 * @param {GridApi} gridApi the grid api, as normally
6340 * returned in the onRegisterApi method
6344 * gridApi.core.on.renderingComplete( grid );
6347 this.registerEvent( 'core', 'renderingComplete' );
6351 * @name filterChanged
6352 * @eventOf ui.grid.core.api:PublicApi
6353 * @description is raised after the filter is changed. The nature
6354 * of the watch expression doesn't allow notification of what changed,
6355 * so the receiver of this event will need to re-extract the filter
6356 * conditions from the columns.
6359 this.registerEvent( 'core', 'filterChanged' );
6363 * @name setRowInvisible
6364 * @methodOf ui.grid.core.api:PublicApi
6365 * @description Sets an override on the row to make it always invisible,
6366 * which will override any filtering or other visibility calculations.
6367 * If the row is currently visible then sets it to invisible and calls
6368 * both grid refresh and emits the rowsVisibleChanged event
6369 * @param {GridRow} row the row we want to make invisible
6371 this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
6375 * @name clearRowInvisible
6376 * @methodOf ui.grid.core.api:PublicApi
6377 * @description Clears any override on visibility for the row so that it returns to
6378 * using normal filtering and other visibility calculations.
6379 * If the row is currently invisible then sets it to visible and calls
6380 * both grid refresh and emits the rowsVisibleChanged event
6381 * TODO: if a filter is active then we can't just set it to visible?
6382 * @param {GridRow} row the row we want to make visible
6384 this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
6388 * @name getVisibleRows
6389 * @methodOf ui.grid.core.api:PublicApi
6390 * @description Returns all visible rows
6391 * @param {Grid} grid the grid you want to get visible rows from
6392 * @returns {array} an array of gridRow
6394 this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
6398 * @name rowsVisibleChanged
6399 * @eventOf ui.grid.core.api:PublicApi
6400 * @description is raised after the rows that are visible
6401 * change. The filtering is zero-based, so it isn't possible
6402 * to say which rows changed (unlike in the selection feature).
6403 * We can plausibly know which row was changed when setRowInvisible
6404 * is called, but in that situation the user already knows which row
6405 * they changed. When a filter runs we don't know what changed,
6406 * and that is the one that would have been useful.
6409 this.registerEvent( 'core', 'rowsVisibleChanged' );
6413 * @name rowsRendered
6414 * @eventOf ui.grid.core.api:PublicApi
6415 * @description is raised after the cache of visible rows is changed.
6417 this.registerEvent( 'core', 'rowsRendered' );
6423 * @eventOf ui.grid.core.api:PublicApi
6424 * @description is raised when scroll begins. Is throttled, so won't be raised too frequently
6426 this.registerEvent( 'core', 'scrollBegin' );
6431 * @eventOf ui.grid.core.api:PublicApi
6432 * @description is raised when scroll has finished. Is throttled, so won't be raised too frequently
6434 this.registerEvent( 'core', 'scrollEnd' );
6438 * @name canvasHeightChanged
6439 * @eventOf ui.grid.core.api:PublicApi
6440 * @description is raised when the canvas height has changed
6442 * arguments: oldHeight, newHeight
6444 this.registerEvent( 'core', 'canvasHeightChanged');
6448 * @name gridDimensionChanged
6449 * @eventOf ui.grid.core.api:PublicApi
6450 * @description is raised when the grid dimensions have changed (when autoResize is on)
6452 * arguments: oldGridHeight, oldGridWidth, newGridHeight, newGridWidth
6454 this.registerEvent( 'core', 'gridDimensionChanged');
6459 * @name ui.grid.class:suppressEvents
6460 * @methodOf ui.grid.class:GridApi
6461 * @description Used to execute a function while disabling the specified event listeners.
6462 * Disables the listenerFunctions, executes the callbackFn, and then enables
6463 * the listenerFunctions again
6464 * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
6465 * functions that were used in the .on.eventName method
6466 * @param {object} callBackFn function to execute
6469 * var navigate = function (newRowCol, oldRowCol){
6470 * //do something on navigate
6473 * gridApi.cellNav.on.navigate(scope,navigate);
6476 * //call the scrollTo event and suppress our navigate listener
6477 * //scrollTo will still raise the event for other listeners
6478 * gridApi.suppressEvents(navigate, function(){
6479 * gridApi.cellNav.scrollTo(aRow, aCol);
6484 GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
6486 var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
6488 //find all registered listeners
6489 var foundListeners = self.listeners.filter(function(listener) {
6490 return listeners.some(function(l) {
6491 return listener.handler === l;
6495 //deregister all the listeners
6496 foundListeners.forEach(function(l){
6502 //reregister all the listeners
6503 foundListeners.forEach(function(l){
6504 l.dereg = registerEventWithAngular(l.eventId, l.handler, self.grid, l._this);
6511 * @name registerEvent
6512 * @methodOf ui.grid.class:GridApi
6513 * @description Registers a new event for the given feature. The event will get a
6514 * .raise and .on prepended to it
6516 * .raise.eventName() - takes no arguments
6519 * .on.eventName(scope, callBackFn, _this)
6521 * scope - a scope reference to add a deregister call to the scopes .$on('destroy'). Scope is optional and can be a null value,
6522 * but in this case you must deregister it yourself via the returned deregister function
6524 * callBackFn - The function to call
6526 * _this - optional this context variable for callbackFn. If omitted, grid.api will be used for the context
6528 * .on.eventName returns a dereg funtion that will remove the listener. It's not necessary to use it as the listener
6529 * will be removed when the scope is destroyed.
6530 * @param {string} featureName name of the feature that raises the event
6531 * @param {string} eventName name of the event
6533 GridApi.prototype.registerEvent = function (featureName, eventName) {
6535 if (!self[featureName]) {
6536 self[featureName] = {};
6539 var feature = self[featureName];
6545 var eventId = self.grid.id + featureName + eventName;
6547 // gridUtil.logDebug('Creating raise event method ' + featureName + '.raise.' + eventName);
6548 feature.raise[eventName] = function () {
6549 $rootScope.$emit.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
6552 // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
6553 feature.on[eventName] = function (scope, handler, _this) {
6554 if ( scope !== null && typeof(scope.$on) === 'undefined' ){
6555 gridUtil.logError('asked to listen on ' + featureName + '.on.' + eventName + ' but scope wasn\'t passed in the input parameters. It is legitimate to pass null, but you\'ve passed something else, so you probably forgot to provide scope rather than did it deliberately, not registering');
6558 var deregAngularOn = registerEventWithAngular(eventId, handler, self.grid, _this);
6560 //track our listener so we can turn off and on
6561 var listener = {handler: handler, dereg: deregAngularOn, eventId: eventId, scope: scope, _this:_this};
6562 self.listeners.push(listener);
6564 var removeListener = function(){
6566 var index = self.listeners.indexOf(listener);
6567 self.listeners.splice(index,1);
6570 //destroy tracking when scope is destroyed
6572 scope.$on('$destroy', function() {
6578 return removeListener;
6582 function registerEventWithAngular(eventId, handler, grid, _this) {
6583 return $rootScope.$on(eventId, function (event) {
6584 var args = Array.prototype.slice.call(arguments);
6585 args.splice(0, 1); //remove evt argument
6586 handler.apply(_this ? _this : grid.api, args);
6592 * @name registerEventsFromObject
6593 * @methodOf ui.grid.class:GridApi
6594 * @description Registers features and events from a simple objectMap.
6595 * eventObjectMap must be in this format (multiple features allowed)
6599 * eventNameOne:function(args){},
6600 * eventNameTwo:function(args){}
6604 * @param {object} eventObjectMap map of feature/event names
6606 GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
6609 angular.forEach(eventObjectMap, function (featProp, featPropName) {
6610 var feature = {name: featPropName, events: []};
6611 angular.forEach(featProp, function (prop, propName) {
6612 feature.events.push(propName);
6614 features.push(feature);
6617 features.forEach(function (feature) {
6618 feature.events.forEach(function (event) {
6619 self.registerEvent(feature.name, event);
6627 * @name registerMethod
6628 * @methodOf ui.grid.class:GridApi
6629 * @description Registers a new event for the given feature
6630 * @param {string} featureName name of the feature
6631 * @param {string} methodName name of the method
6632 * @param {object} callBackFn function to execute
6633 * @param {object} _this binds callBackFn 'this' to _this. Defaults to gridApi.grid
6635 GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, _this) {
6636 if (!this[featureName]) {
6637 this[featureName] = {};
6640 var feature = this[featureName];
6642 feature[methodName] = gridUtil.createBoundedWrapper(_this || this.grid, callBackFn);
6647 * @name registerMethodsFromObject
6648 * @methodOf ui.grid.class:GridApi
6649 * @description Registers features and methods from a simple objectMap.
6650 * eventObjectMap must be in this format (multiple features allowed)
6654 * methodNameOne:function(args){},
6655 * methodNameTwo:function(args){}
6657 * @param {object} eventObjectMap map of feature/event names
6658 * @param {object} _this binds this to _this for all functions. Defaults to gridApi.grid
6660 GridApi.prototype.registerMethodsFromObject = function (methodMap, _this) {
6663 angular.forEach(methodMap, function (featProp, featPropName) {
6664 var feature = {name: featPropName, methods: []};
6665 angular.forEach(featProp, function (prop, propName) {
6666 feature.methods.push({name: propName, fn: prop});
6668 features.push(feature);
6671 features.forEach(function (feature) {
6672 feature.methods.forEach(function (method) {
6673 self.registerMethod(feature.name, method.name, method.fn, _this);
6687 angular.module('ui.grid')
6688 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
6691 * ******************************************************************************************
6692 * PaulL1: Ugly hack here in documentation. These properties are clearly properties of GridColumn,
6693 * and need to be noted as such for those extending and building ui-grid itself.
6694 * However, from an end-developer perspective, they interact with all these through columnDefs,
6695 * and they really need to be documented there. I feel like they're relatively static, and
6696 * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
6697 * comment block. Ugh.
6704 * @propertyOf ui.grid.class:GridColumn
6705 * @description (mandatory) each column should have a name, although for backward
6706 * compatibility with 2.x name can be omitted if field is present
6713 * @propertyOf ui.grid.class:GridOptions.columnDef
6714 * @description (mandatory) each column should have a name, although for backward
6715 * compatibility with 2.x name can be omitted if field is present
6722 * @propertyOf ui.grid.class:GridColumn
6723 * @description Column name that will be shown in the header. If displayName is not
6724 * provided then one is generated using the name.
6731 * @propertyOf ui.grid.class:GridOptions.columnDef
6732 * @description Column name that will be shown in the header. If displayName is not
6733 * provided then one is generated using the name.
6740 * @propertyOf ui.grid.class:GridColumn
6741 * @description field must be provided if you wish to bind to a
6742 * property in the data source. Should be an angular expression that evaluates against grid.options.data
6743 * array element. Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
6744 * See the angular docs on binding expressions.
6751 * @propertyOf ui.grid.class:GridOptions.columnDef
6752 * @description field must be provided if you wish to bind to a
6753 * property in the data source. Should be an angular expression that evaluates against grid.options.data
6754 * array element. Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>. * See the angular docs on binding expressions. *
6760 * @propertyOf ui.grid.class:GridColumn
6761 * @description Filter on this column.
6763 * Available built-in conditions and types are listed under {@link jui.grid.service:uiGridConstants#properties_filter uiGridOptions.filter}
6765 * <pre>{ term: 'text', condition: uiGridConstants.filter.STARTS_WITH, placeholder: 'type to filter...', ariaLabel: 'Filter for text', flags: { caseSensitive: false }, type: uiGridConstants.filter.SELECT, [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ] }</pre>
6772 * @propertyOf ui.grid.class:GridColumn
6773 * @description additional on this column.
6775 * <pre>{extraStyle: {display:'table-cell'}}</pre>
6781 * @name ui.grid.class:GridColumn
6782 * @description Represents the viewModel for each column. Any state or methods needed for a Grid Column
6783 * are defined on this prototype
6784 * @param {ColumnDef} colDef the column def to associate with this column
6785 * @param {number} uid the unique and immutable uid we'd like to allocate to this column
6786 * @param {Grid} grid the grid we'd like to create this column in
6788 function GridColumn(colDef, uid, grid) {
6794 self.updateColumnDef(colDef, true);
6796 self.aggregationValue = undefined;
6798 // The footer cell registers to listen for the rowsRendered event, and calls this. Needed to be
6799 // in something with a scope so that the dereg would get called
6800 self.updateAggregationValue = function() {
6802 // gridUtil.logDebug('getAggregationValue for Column ' + self.colDef.name);
6806 * @name aggregationType
6807 * @propertyOf ui.grid.class:GridOptions.columnDef
6808 * @description The aggregation that you'd like to show in the columnFooter for this
6809 * column. Valid values are in
6810 * {@link ui.grid.service:uiGridConstants#properties_aggregationTypes uiGridConstants.aggregationTypes},
6811 * and currently include `uiGridConstants.aggregationTypes.count`,
6812 * `uiGridConstants.aggregationTypes.sum`, `uiGridConstants.aggregationTypes.avg`, `uiGridConstants.aggregationTypes.min`,
6813 * `uiGridConstants.aggregationTypes.max`.
6815 * You can also provide a function as the aggregation type, in this case your function needs to accept the full
6816 * set of visible rows, and return a value that should be shown
6818 if (!self.aggregationType) {
6819 self.aggregationValue = undefined;
6824 var visibleRows = self.grid.getVisibleRows();
6826 var cellValues = function(){
6828 visibleRows.forEach(function (row) {
6829 var cellValue = self.grid.getCellValue(row, self);
6830 var cellNumber = Number(cellValue);
6831 if (!isNaN(cellNumber)) {
6832 values.push(cellNumber);
6838 if (angular.isFunction(self.aggregationType)) {
6839 self.aggregationValue = self.aggregationType(visibleRows, self);
6841 else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
6842 self.aggregationValue = self.grid.getVisibleRowCount();
6844 else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
6845 cellValues().forEach(function (value) {
6848 self.aggregationValue = result;
6850 else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
6851 cellValues().forEach(function (value) {
6854 result = result / cellValues().length;
6855 self.aggregationValue = result;
6857 else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
6858 self.aggregationValue = Math.min.apply(null, cellValues());
6860 else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
6861 self.aggregationValue = Math.max.apply(null, cellValues());
6864 self.aggregationValue = '\u00A0';
6868 // var throttledUpdateAggregationValue = gridUtil.throttle(updateAggregationValue, self.grid.options.aggregationCalcThrottle, { trailing: true, context: self.name });
6872 * @name getAggregationValue
6873 * @methodOf ui.grid.class:GridColumn
6874 * @description gets the aggregation value based on the aggregation type for this column.
6875 * Debounced using scrollDebounce option setting
6877 this.getAggregationValue = function() {
6878 // if (!self.grid.isScrollingVertically && !self.grid.isScrollingHorizontally) {
6879 // throttledUpdateAggregationValue();
6882 return self.aggregationValue;
6889 * @methodOf ui.grid.class:GridColumn
6890 * @description Hides the column by setting colDef.visible = false
6892 GridColumn.prototype.hideColumn = function() {
6893 this.colDef.visible = false;
6899 * @methodOf ui.grid.class:GridColumn
6900 * @name setPropertyOrDefault
6901 * @description Sets a property on the column using the passed in columnDef, and
6902 * setting the defaultValue if the value cannot be found on the colDef
6903 * @param {ColumnDef} colDef the column def to look in for the property value
6904 * @param {string} propName the property name we'd like to set
6905 * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
6907 GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
6910 // Use the column definition filter if we were passed it
6911 if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
6912 self[propName] = colDef[propName];
6914 // Otherwise use our own if it's set
6915 else if (typeof(self[propName]) !== 'undefined') {
6916 self[propName] = self[propName];
6918 // Default to empty object for the filter
6920 self[propName] = defaultValue ? defaultValue : {};
6929 * @propertyOf ui.grid.class:GridOptions.columnDef
6930 * @description sets the column width. Can be either
6931 * a number or a percentage, or an * for auto.
6933 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
6934 * { field: 'field2', width: '20%'},
6935 * { field: 'field3', width: '*' }]; </pre>
6942 * @propertyOf ui.grid.class:GridOptions.columnDef
6943 * @description sets the minimum column width. Should be a number.
6945 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
6952 * @propertyOf ui.grid.class:GridOptions.columnDef
6953 * @description sets the maximum column width. Should be a number.
6955 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
6962 * @propertyOf ui.grid.class:GridOptions.columnDef
6963 * @description sets whether or not the column is visible
6964 * </br>Default is true
6966 * <pre> $scope.gridOptions.columnDefs = [
6967 * { field: 'field1', visible: true},
6968 * { field: 'field2', visible: false }
6976 * @propertyOf ui.grid.class:GridOptions.columnDef
6977 * @description An object of sort information, attributes are:
6979 * - direction: values are {@link ui.grid.service:uiGridConstants#properties_ASC uiGridConstants.ASC}
6980 * or {@link ui.grid.service:uiGridConstants#properties_DESC uiGridConstants.DESC}
6981 * - ignoreSort: if set to true this sort is ignored (used by tree to manipulate the sort functionality)
6982 * - priority: says what order to sort the columns in (lower priority gets sorted first).
6985 * $scope.gridOptions.columnDefs = [{
6988 * direction: uiGridConstants.ASC,
6999 * @name sortingAlgorithm
7000 * @propertyOf ui.grid.class:GridOptions.columnDef
7001 * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
7002 * like any normal sorting function with additional 'rowA', 'rowB', and 'direction' parameters
7003 * that are the row objects and the current direction of the sort respectively.
7010 * @propertyOf ui.grid.class:GridOptions.columnDef
7011 * @description Specify multiple filter fields.
7013 * <pre>$scope.gridOptions.columnDefs = [
7015 * field: 'field1', filters: [
7018 * condition: uiGridConstants.filter.STARTS_WITH,
7019 * placeholder: 'starts with...',
7020 * ariaLabel: 'Filter for field1',
7021 * flags: { caseSensitive: false },
7022 * type: uiGridConstants.filter.SELECT,
7023 * selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
7026 * condition: uiGridConstants.filter.ENDS_WITH,
7027 * placeholder: 'ends with...'
7039 * @propertyOf ui.grid.class:GridColumn
7040 * @description Filters for this column. Includes 'term' property bound to filter input elements.
7044 * term: 'foo', // ngModel for <input>
7045 * condition: uiGridConstants.filter.STARTS_WITH,
7046 * placeholder: 'starts with...',
7047 * ariaLabel: 'Filter for foo',
7048 * flags: { caseSensitive: false },
7049 * type: uiGridConstants.filter.SELECT,
7050 * selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ]
7054 * condition: uiGridConstants.filter.ENDS_WITH,
7055 * placeholder: 'ends with...'
7065 * @propertyOf ui.grid.class:GridOptions.columnDef
7066 * @description used to add menu items to a column. Refer to the tutorial on this
7067 * functionality. A number of settings are supported:
7069 * - title: controls the title that is displayed in the menu
7070 * - icon: the icon shown alongside that title
7071 * - action: the method to call when the menu is clicked
7072 * - shown: a function to evaluate to determine whether or not to show the item
7073 * - active: a function to evaluate to determine whether or not the item is currently selected
7074 * - context: context to pass to the action function, available in this.context in your handler
7075 * - leaveOpen: if set to true, the menu should stay open after the action, defaults to false
7077 * <pre> $scope.gridOptions.columnDefs = [
7078 * { field: 'field1', menuItems: [
7080 * title: 'Outer Scope Alert',
7081 * icon: 'ui-grid-icon-info-circled',
7082 * action: function($event) {
7083 * this.context.blargh(); // $scope.blargh() would work too, this is just an example
7085 * shown: function() { return true; },
7086 * active: function() { return true; },
7091 * action: function() {
7092 * alert('Grid ID: ' + this.grid.id);
7101 * @methodOf ui.grid.class:GridColumn
7102 * @name updateColumnDef
7103 * @description Moves settings from the columnDef down onto the column,
7104 * and sets properties as appropriate
7105 * @param {ColumnDef} colDef the column def to look in for the property value
7106 * @param {boolean} isNew whether the column is being newly created, if not
7107 * we're updating an existing column, and some items such as the sort shouldn't
7110 GridColumn.prototype.updateColumnDef = function(colDef, isNew) {
7113 self.colDef = colDef;
7115 if (colDef.name === undefined) {
7116 throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
7119 self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
7121 if (!angular.isNumber(self.width) || !self.hasCustomWidth || colDef.allowCustomWidthOverride) {
7122 var colDefWidth = colDef.width;
7123 var parseErrorMsg = "Cannot parse column width '" + colDefWidth + "' for column named '" + colDef.name + "'";
7124 self.hasCustomWidth = false;
7126 if (!angular.isString(colDefWidth) && !angular.isNumber(colDefWidth)) {
7128 } else if (angular.isString(colDefWidth)) {
7129 // See if it ends with a percent
7130 if (gridUtil.endsWith(colDefWidth, '%')) {
7131 // If so we should be able to parse the non-percent-sign part to a number
7132 var percentStr = colDefWidth.replace(/%/g, '');
7133 var percent = parseInt(percentStr, 10);
7134 if (isNaN(percent)) {
7135 throw new Error(parseErrorMsg);
7137 self.width = colDefWidth;
7139 // And see if it's a number string
7140 else if (colDefWidth.match(/^(\d+)$/)) {
7141 self.width = parseInt(colDefWidth.match(/^(\d+)$/)[1], 10);
7143 // Otherwise it should be a string of asterisks
7144 else if (colDefWidth.match(/^\*+$/)) {
7145 self.width = colDefWidth;
7147 // No idea, throw an Error
7149 throw new Error(parseErrorMsg);
7152 // Is a number, use it as the width
7154 self.width = colDefWidth;
7158 ['minWidth', 'maxWidth'].forEach(function (name) {
7159 var minOrMaxWidth = colDef[name];
7160 var parseErrorMsg = "Cannot parse column " + name + " '" + minOrMaxWidth + "' for column named '" + colDef.name + "'";
7162 if (!angular.isString(minOrMaxWidth) && !angular.isNumber(minOrMaxWidth)) {
7163 //Sets default minWidth and maxWidth values
7164 self[name] = ((name === 'minWidth') ? 30 : 9000);
7165 } else if (angular.isString(minOrMaxWidth)) {
7166 if (minOrMaxWidth.match(/^(\d+)$/)) {
7167 self[name] = parseInt(minOrMaxWidth.match(/^(\d+)$/)[1], 10);
7169 throw new Error(parseErrorMsg);
7172 self[name] = minOrMaxWidth;
7176 //use field if it is defined; name if it is not
7177 self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
7179 if ( typeof( self.field ) !== 'string' ){
7180 gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
7183 self.name = colDef.name;
7185 // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
7186 self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
7188 //self.originalIndex = index;
7190 self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
7191 self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
7196 * @propertyOf ui.grid.class:GridOptions.columnDef
7197 * @description Whether or not to show a tooltip when a user hovers over the cell.
7198 * If set to false, no tooltip. If true, the cell value is shown in the tooltip (useful
7199 * if you have long values in your cells), if a function then that function is called
7200 * passing in the row and the col `cellTooltip( row, col )`, and the return value is shown in the tooltip,
7201 * if it is a static string then displays that static string.
7206 if ( typeof(colDef.cellTooltip) === 'undefined' || colDef.cellTooltip === false ) {
7207 self.cellTooltip = false;
7208 } else if ( colDef.cellTooltip === true ){
7209 self.cellTooltip = function(row, col) {
7210 return self.grid.getCellValue( row, col );
7212 } else if (typeof(colDef.cellTooltip) === 'function' ){
7213 self.cellTooltip = colDef.cellTooltip;
7215 self.cellTooltip = function ( row, col ){
7216 return col.colDef.cellTooltip;
7222 * @name headerTooltip
7223 * @propertyOf ui.grid.class:GridOptions.columnDef
7224 * @description Whether or not to show a tooltip when a user hovers over the header cell.
7225 * If set to false, no tooltip. If true, the displayName is shown in the tooltip (useful
7226 * if you have long values in your headers), if a function then that function is called
7227 * passing in the row and the col `headerTooltip( col )`, and the return value is shown in the tooltip,
7228 * if a static string then shows that static string.
7233 if ( typeof(colDef.headerTooltip) === 'undefined' || colDef.headerTooltip === false ) {
7234 self.headerTooltip = false;
7235 } else if ( colDef.headerTooltip === true ){
7236 self.headerTooltip = function(col) {
7237 return col.displayName;
7239 } else if (typeof(colDef.headerTooltip) === 'function' ){
7240 self.headerTooltip = colDef.headerTooltip;
7242 self.headerTooltip = function ( col ) {
7243 return col.colDef.headerTooltip;
7250 * @name footerCellClass
7251 * @propertyOf ui.grid.class:GridOptions.columnDef
7252 * @description footerCellClass can be a string specifying the class to append to a cell
7253 * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7256 self.footerCellClass = colDef.footerCellClass;
7261 * @propertyOf ui.grid.class:GridOptions.columnDef
7262 * @description cellClass can be a string specifying the class to append to a cell
7263 * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7266 self.cellClass = colDef.cellClass;
7270 * @name headerCellClass
7271 * @propertyOf ui.grid.class:GridOptions.columnDef
7272 * @description headerCellClass can be a string specifying the class to append to a cell
7273 * or it can be a function(grid, row, col, rowRenderIndex, colRenderIndex) that returns a class name
7276 self.headerCellClass = colDef.headerCellClass;
7281 * @propertyOf ui.grid.class:GridOptions.columnDef
7282 * @description cellFilter is a filter to apply to the content of each cell
7285 * gridOptions.columnDefs[0].cellFilter = 'date'
7288 self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
7292 * @name sortCellFiltered
7293 * @propertyOf ui.grid.class:GridOptions.columnDef
7294 * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7295 * sorting the data. Note that when using this option uiGrid will assume that the displayed value is
7296 * a string, and use the {@link ui.grid.class:RowSorter#sortAlpha sortAlpha} `sortFn`. It is possible
7297 * to return a non-string value from an angularjs filter, in which case you should define a {@link ui.grid.class:GridOptions.columnDef#sortingAlgorithm sortingAlgorithm}
7298 * for the column which hanldes the returned type. You may specify one of the `sortingAlgorithms`
7299 * found in the {@link ui.grid.RowSorter rowSorter} service.
7301 self.sortCellFiltered = colDef.sortCellFiltered ? true : false;
7305 * @name filterCellFiltered
7306 * @propertyOf ui.grid.class:GridOptions.columnDef
7307 * @description (optional) False by default. When `true` uiGrid will apply the cellFilter before
7308 * applying "search" `filters`.
7310 self.filterCellFiltered = colDef.filterCellFiltered ? true : false;
7314 * @name headerCellFilter
7315 * @propertyOf ui.grid.class:GridOptions.columnDef
7316 * @description headerCellFilter is a filter to apply to the content of the column header
7319 * gridOptions.columnDefs[0].headerCellFilter = 'translate'
7322 self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
7326 * @name footerCellFilter
7327 * @propertyOf ui.grid.class:GridOptions.columnDef
7328 * @description footerCellFilter is a filter to apply to the content of the column footer
7331 * gridOptions.columnDefs[0].footerCellFilter = 'date'
7334 self.footerCellFilter = colDef.footerCellFilter ? colDef.footerCellFilter : "";
7336 self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
7338 self.headerClass = colDef.headerClass;
7339 //self.cursor = self.sortable ? 'pointer' : 'default';
7341 // Turn on sorting by default
7342 self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : self.grid.options.enableSorting;
7343 self.sortingAlgorithm = colDef.sortingAlgorithm;
7347 * @name sortDirectionCycle
7348 * @propertyOf ui.grid.class:GridOptions.columnDef
7349 * @description (optional) An array of {@link ui.grid.service:uiGridConstants#properties_ASC sort directions},
7350 * specifying the order that they should cycle through as the user repeatedly clicks on the column heading.
7351 * The default is `[null, uiGridConstants.ASC, uiGridConstants.DESC]`. Null
7352 * refers to the unsorted state. This does not affect the initial sort
7353 * direction; use the {@link ui.grid.class:GridOptions.columnDef#sort sort}
7354 * property for that. If
7355 * {@link ui.grid.class:GridOptions.columnDef#suppressRemoveSort suppressRemoveSort}
7356 * is also set, the unsorted state will be skipped even if it is listed here.
7357 * Each direction may not appear in the list more than once (e.g. `[ASC,
7358 * DESC, DESC]` is not allowed), and the list may not be empty.
7360 self.sortDirectionCycle = typeof(colDef.sortDirectionCycle) !== 'undefined' ?
7361 colDef.sortDirectionCycle :
7362 [null, uiGridConstants.ASC, uiGridConstants.DESC];
7366 * @name suppressRemoveSort
7367 * @propertyOf ui.grid.class:GridOptions.columnDef
7368 * @description (optional) False by default. When enabled, this setting hides the removeSort option
7369 * in the menu, and prevents users from manually removing the sort
7371 if ( typeof(self.suppressRemoveSort) === 'undefined'){
7372 self.suppressRemoveSort = typeof(colDef.suppressRemoveSort) !== 'undefined' ? colDef.suppressRemoveSort : false;
7377 * @name enableFiltering
7378 * @propertyOf ui.grid.class:GridOptions.columnDef
7379 * @description turn off filtering for an individual column, where
7380 * you've turned on filtering for the overall grid
7383 * gridOptions.columnDefs[0].enableFiltering = false;
7386 // Turn on filtering by default (it's disabled by default at the Grid level)
7387 self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
7389 // self.menuItems = colDef.menuItems;
7390 self.setPropertyOrDefault(colDef, 'menuItems', []);
7392 // Use the column definition sort if we were passed it, but only if this is a newly added column
7394 self.setPropertyOrDefault(colDef, 'sort');
7397 // Set up default filters array for when one is not provided.
7398 // In other words, this (in column def):
7400 // filter: { term: 'something', flags: {}, condition: [CONDITION] }
7402 // is just shorthand for this:
7404 // filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
7406 var defaultFilters = [];
7407 if (colDef.filter) {
7408 defaultFilters.push(colDef.filter);
7410 else if ( colDef.filters ){
7411 defaultFilters = colDef.filters;
7413 // Add an empty filter definition object, which will
7414 // translate to a guessed condition and no pre-populated
7415 // value for the filter <input>.
7416 defaultFilters.push({});
7422 * @propertyOf ui.grid.class:GridOptions.columnDef
7423 * @description Specify a single filter field on this column.
7425 * A filter consists of a condition, a term, and a placeholder:
7427 * - condition defines how rows are chosen as matching the filter term. This can be set to
7428 * one of the constants in {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter},
7429 * or you can supply a custom filter function
7430 * that gets passed the following arguments: [searchTerm, cellValue, row, column].
7431 * - term: If set, the filter field will be pre-populated
7433 * - placeholder: String that will be set to the `<input>.placeholder` attribute.
7434 * - ariaLabel: String that will be set to the `<input>.ariaLabel` attribute. This is what is read as a label to screen reader users.
7435 * - noTerm: set this to true if you have defined a custom function in condition, and
7436 * your custom function doesn't require a term (so it can run even when the term is null)
7437 * - rawTerm: set this to true if you have defined a custom function in condition, and
7438 * your custom function requires access to the raw unmodified search term that was entered
7439 * - flags: only flag currently available is `caseSensitive`, set to false if you don't want
7440 * case sensitive matching
7441 * - type: defaults to {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter.INPUT},
7442 * which gives a text box. If set to {@link ui.grid.service:uiGridConstants#properties_filter uiGridConstants.filter.SELECT}
7443 * then a select box will be shown with options selectOptions
7444 * - selectOptions: options in the format `[ { value: 1, label: 'male' }]`. No i18n filter is provided, you need
7445 * to perform the i18n on the values before you provide them
7446 * - disableCancelFilterButton: defaults to false. If set to true then the 'x' button that cancels/clears the filter
7447 * will not be shown.
7449 * <pre>$scope.gridOptions.columnDefs = [
7454 * condition: uiGridConstants.filter.STARTS_WITH,
7455 * placeholder: 'starts with...',
7456 * ariaLabel: 'Starts with filter for field1',
7457 * flags: { caseSensitive: false },
7458 * type: uiGridConstants.filter.SELECT,
7459 * selectOptions: [ { value: 1, label: 'male' }, { value: 2, label: 'female' } ],
7460 * disableCancelFilterButton: true
7475 condition: uiGridConstants.filter.CONTAINS,
7476 placeholder: 'my placeholder',
7477 ariaLabel: 'Starts with filter for field1',
7486 // Only set filter if this is a newly added column, if we're updating an existing
7487 // column then we don't want to put the default filter back if the user may have already
7489 // However, we do want to keep the settings if they change, just not the term
7491 self.setPropertyOrDefault(colDef, 'filter');
7492 self.setPropertyOrDefault(colDef, 'extraStyle');
7493 self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
7494 } else if ( self.filters.length === defaultFilters.length ) {
7495 self.filters.forEach( function( filter, index ){
7496 if (typeof(defaultFilters[index].placeholder) !== 'undefined') {
7497 filter.placeholder = defaultFilters[index].placeholder;
7499 if (typeof(defaultFilters[index].ariaLabel) !== 'undefined') {
7500 filter.ariaLabel = defaultFilters[index].ariaLabel;
7502 if (typeof(defaultFilters[index].flags) !== 'undefined') {
7503 filter.flags = defaultFilters[index].flags;
7505 if (typeof(defaultFilters[index].type) !== 'undefined') {
7506 filter.type = defaultFilters[index].type;
7508 if (typeof(defaultFilters[index].selectOptions) !== 'undefined') {
7509 filter.selectOptions = defaultFilters[index].selectOptions;
7518 * @methodOf ui.grid.class:GridColumn
7519 * @description Removes column from the grid sorting
7521 GridColumn.prototype.unsort = function () {
7523 this.grid.api.core.raise.sortChanged( this.grid, this.grid.getColumnSorting() );
7530 * @methodOf ui.grid.class:GridColumn
7531 * @description Returns the class name for the column
7532 * @param {bool} prefixDot if true, will return .className instead of className
7534 GridColumn.prototype.getColClass = function (prefixDot) {
7535 var cls = uiGridConstants.COL_CLASS_PREFIX + this.uid;
7537 return prefixDot ? '.' + cls : cls;
7542 * @name isPinnedLeft
7543 * @methodOf ui.grid.class:GridColumn
7544 * @description Returns true if column is in the left render container
7546 GridColumn.prototype.isPinnedLeft = function () {
7547 return this.renderContainer === 'left';
7552 * @name isPinnedRight
7553 * @methodOf ui.grid.class:GridColumn
7554 * @description Returns true if column is in the right render container
7556 GridColumn.prototype.isPinnedRight = function () {
7557 return this.renderContainer === 'right';
7563 * @name getColClassDefinition
7564 * @methodOf ui.grid.class:GridColumn
7565 * @description Returns the class definition for th column
7567 GridColumn.prototype.getColClassDefinition = function () {
7568 return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { min-width: ' + this.drawnWidth + 'px; max-width: ' + this.drawnWidth + 'px; }';
7573 * @name getRenderContainer
7574 * @methodOf ui.grid.class:GridColumn
7575 * @description Returns the render container object that this column belongs to.
7577 * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
7579 GridColumn.prototype.getRenderContainer = function getRenderContainer() {
7582 var containerId = self.renderContainer;
7584 if (containerId === null || containerId === '' || containerId === undefined) {
7585 containerId = 'body';
7588 return self.grid.renderContainers[containerId];
7594 * @methodOf ui.grid.class:GridColumn
7595 * @description Makes the column visible by setting colDef.visible = true
7597 GridColumn.prototype.showColumn = function() {
7598 this.colDef.visible = true;
7604 * @name aggregationHideLabel
7605 * @propertyOf ui.grid.class:GridOptions.columnDef
7606 * @description defaults to false, if set to true hides the label text
7607 * in the aggregation footer, so only the value is displayed.
7612 * @name getAggregationText
7613 * @methodOf ui.grid.class:GridColumn
7614 * @description Gets the aggregation label from colDef.aggregationLabel if
7615 * specified or by using i18n, including deciding whether or not to display
7616 * based on colDef.aggregationHideLabel.
7618 * @param {string} label the i18n lookup value to use for the column label
7621 GridColumn.prototype.getAggregationText = function () {
7623 if ( self.colDef.aggregationHideLabel ){
7626 else if ( self.colDef.aggregationLabel ) {
7627 return self.colDef.aggregationLabel;
7630 switch ( self.colDef.aggregationType ){
7631 case uiGridConstants.aggregationTypes.count:
7632 return i18nService.getSafeText('aggregation.count');
7633 case uiGridConstants.aggregationTypes.sum:
7634 return i18nService.getSafeText('aggregation.sum');
7635 case uiGridConstants.aggregationTypes.avg:
7636 return i18nService.getSafeText('aggregation.avg');
7637 case uiGridConstants.aggregationTypes.min:
7638 return i18nService.getSafeText('aggregation.min');
7639 case uiGridConstants.aggregationTypes.max:
7640 return i18nService.getSafeText('aggregation.max');
7647 GridColumn.prototype.getCellTemplate = function () {
7650 return self.cellTemplatePromise;
7653 GridColumn.prototype.getCompiledElementFn = function () {
7656 return self.compiledElementFnDefer.promise;
7666 angular.module('ui.grid')
7667 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
7671 * @name ui.grid.class:GridOptions
7672 * @description Default GridOptions class. GridOptions are defined by the application developer and overlaid
7673 * over this object. Setting gridOptions within your controller is the most common method for an application
7674 * developer to configure the behaviour of their ui-grid
7676 * @example To define your gridOptions within your controller:
7677 * <pre>$scope.gridOptions = {
7678 * data: $scope.myData,
7680 * { name: 'field1', displayName: 'pretty display name' },
7681 * { name: 'field2', visible: false }
7685 * You can then use this within your html template, when you define your grid:
7686 * <pre><div ui-grid="gridOptions"></div></pre>
7688 * To provide default options for all of the grids within your application, use an angular
7689 * decorator to modify the GridOptions factory.
7691 * app.config(function($provide){
7692 * $provide.decorator('GridOptions',function($delegate){
7694 * gridOptions = angular.copy($delegate);
7695 * gridOptions.initialize = function(options) {
7697 * initOptions = $delegate.initialize(options);
7698 * initOptions.enableColumnMenus = false;
7699 * return initOptions;
7701 * return gridOptions;
7707 initialize: function( baseOptions ){
7710 * @name onRegisterApi
7711 * @propertyOf ui.grid.class:GridOptions
7712 * @description A callback that returns the gridApi once the grid is instantiated, which is
7713 * then used to interact with the grid programatically.
7715 * Note that the gridApi.core.renderingComplete event is identical to this
7716 * callback, but has the advantage that it can be called from multiple places
7721 * $scope.gridOptions.onRegisterApi = function ( gridApi ) {
7722 * $scope.gridApi = gridApi;
7723 * $scope.gridApi.selection.selectAllRows( $scope.gridApi.grid );
7728 baseOptions.onRegisterApi = baseOptions.onRegisterApi || angular.noop();
7733 * @propertyOf ui.grid.class:GridOptions
7734 * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for
7737 * Most commonly the data is an array of objects, where each object has a number of attributes.
7738 * Each attribute automatically becomes a column in your grid. This array could, for example, be sourced from
7739 * an angularJS $resource query request. The array can also contain complex objects, refer the binding tutorial
7740 * for examples of that.
7742 * The most flexible usage is to set your data on $scope:
7744 * `$scope.data = data;`
7746 * And then direct the grid to resolve whatever is in $scope.data:
7748 * `$scope.gridOptions.data = 'data';`
7750 * This is the most flexible approach as it allows you to replace $scope.data whenever you feel like it without
7751 * getting pointer issues.
7753 * Alternatively you can directly set the data array:
7755 * `$scope.gridOptions.data = [ ];`
7758 * `$http.get('/data/100.json')
7759 * .success(function(data) {
7760 * $scope.myData = data;
7761 * $scope.gridOptions.data = $scope.myData;
7764 * Where you do this, you need to take care in updating the data - you can't just update `$scope.myData` to some other
7765 * array, you need to update $scope.gridOptions.data to point to that new array as well.
7768 baseOptions.data = baseOptions.data || [];
7773 * @propertyOf ui.grid.class:GridOptions
7774 * @description Array of columnDef objects. Only required property is name.
7775 * The individual options available in columnDefs are documented in the
7776 * {@link ui.grid.class:GridOptions.columnDef columnDef} section
7777 * </br>_field property can be used in place of name for backwards compatibility with 2.x_
7780 * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
7783 baseOptions.columnDefs = baseOptions.columnDefs || [];
7787 * @name ui.grid.class:GridOptions.columnDef
7788 * @description Definition / configuration of an individual column, which would typically be
7789 * one of many column definitions within the gridOptions.columnDefs array
7791 * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
7798 * @name excludeProperties
7799 * @propertyOf ui.grid.class:GridOptions
7800 * @description Array of property names in data to ignore when auto-generating column names. Provides the
7801 * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
7804 * If columnDefs is defined, this will be ignored.
7806 * Defaults to ['$$hashKey']
7809 baseOptions.excludeProperties = baseOptions.excludeProperties || ['$$hashKey'];
7813 * @name enableRowHashing
7814 * @propertyOf ui.grid.class:GridOptions
7815 * @description True by default. When enabled, this setting allows uiGrid to add
7816 * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
7817 * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
7819 * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
7820 * you do not want this at all you can disable this setting but you will take a performance hit if you are using large numbers of rows
7821 * and are altering the data set often.
7823 baseOptions.enableRowHashing = baseOptions.enableRowHashing !== false;
7828 * @methodOf ui.grid.class:GridOptions
7829 * @description This function is used to get and, if necessary, set the value uniquely identifying this row (i.e. if an identity is not present it will set one).
7831 * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
7833 baseOptions.rowIdentity = baseOptions.rowIdentity || function rowIdentity(row) {
7834 return gridUtil.hashKey(row);
7839 * @name getRowIdentity
7840 * @methodOf ui.grid.class:GridOptions
7841 * @description This function returns the identity value uniquely identifying this row, if one is not present it does not set it.
7843 * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
7845 baseOptions.getRowIdentity = baseOptions.getRowIdentity || function getRowIdentity(row) {
7846 return row.$$hashKey;
7851 * @name flatEntityAccess
7852 * @propertyOf ui.grid.class:GridOptions
7853 * @description Set to true if your columns are all related directly to fields in a flat object structure - i.e.
7854 * each of your columns associate directly with a property on each of the entities in your data array.
7856 * In that situation we can avoid all the logic associated with complex binding to functions or to properties of sub-objects,
7857 * which can provide a significant speed improvement with large data sets when filtering or sorting.
7861 baseOptions.flatEntityAccess = baseOptions.flatEntityAccess === true;
7866 * @propertyOf ui.grid.class:GridOptions
7867 * @description True by default. When set to false, this setting will replace the
7868 * standard header template with '<div></div>', resulting in no header being shown.
7870 baseOptions.showHeader = typeof(baseOptions.showHeader) !== "undefined" ? baseOptions.showHeader : true;
7872 /* (NOTE): Don't show this in the docs. We only use it internally
7874 * @name headerRowHeight
7875 * @propertyOf ui.grid.class:GridOptions
7876 * @description The height of the header in pixels, defaults to 30
7879 if (!baseOptions.showHeader) {
7880 baseOptions.headerRowHeight = 0;
7883 baseOptions.headerRowHeight = typeof(baseOptions.headerRowHeight) !== "undefined" ? baseOptions.headerRowHeight : 30;
7889 * @propertyOf ui.grid.class:GridOptions
7890 * @description The height of the row in pixels, Can be passed as integer or string. defaults to 30.
7894 if (typeof baseOptions.rowHeight === "string") {
7895 baseOptions.rowHeight = parseInt(baseOptions.rowHeight) || 30;
7899 baseOptions.rowHeight = baseOptions.rowHeight || 30;
7904 * @name minRowsToShow
7905 * @propertyOf ui.grid.class:GridOptions
7906 * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
7908 baseOptions.minRowsToShow = typeof(baseOptions.minRowsToShow) !== "undefined" ? baseOptions.minRowsToShow : 10;
7912 * @name showGridFooter
7913 * @propertyOf ui.grid.class:GridOptions
7914 * @description Whether or not to show the footer, defaults to false
7915 * The footer display Total Rows and Visible Rows (filtered rows)
7917 baseOptions.showGridFooter = baseOptions.showGridFooter === true;
7921 * @name showColumnFooter
7922 * @propertyOf ui.grid.class:GridOptions
7923 * @description Whether or not to show the column footer, defaults to false
7924 * The column footer displays column aggregates
7926 baseOptions.showColumnFooter = baseOptions.showColumnFooter === true;
7930 * @name columnFooterHeight
7931 * @propertyOf ui.grid.class:GridOptions
7932 * @description The height of the footer rows (column footer and grid footer) in pixels
7935 baseOptions.columnFooterHeight = typeof(baseOptions.columnFooterHeight) !== "undefined" ? baseOptions.columnFooterHeight : 30;
7936 baseOptions.gridFooterHeight = typeof(baseOptions.gridFooterHeight) !== "undefined" ? baseOptions.gridFooterHeight : 30;
7938 baseOptions.columnWidth = typeof(baseOptions.columnWidth) !== "undefined" ? baseOptions.columnWidth : 50;
7942 * @name maxVisibleColumnCount
7943 * @propertyOf ui.grid.class:GridOptions
7944 * @description Defaults to 200
7947 baseOptions.maxVisibleColumnCount = typeof(baseOptions.maxVisibleColumnCount) !== "undefined" ? baseOptions.maxVisibleColumnCount : 200;
7951 * @name virtualizationThreshold
7952 * @propertyOf ui.grid.class:GridOptions
7953 * @description Turn virtualization on when number of data elements goes over this number, defaults to 20
7955 baseOptions.virtualizationThreshold = typeof(baseOptions.virtualizationThreshold) !== "undefined" ? baseOptions.virtualizationThreshold : 20;
7959 * @name columnVirtualizationThreshold
7960 * @propertyOf ui.grid.class:GridOptions
7961 * @description Turn virtualization on when number of columns goes over this number, defaults to 10
7963 baseOptions.columnVirtualizationThreshold = typeof(baseOptions.columnVirtualizationThreshold) !== "undefined" ? baseOptions.columnVirtualizationThreshold : 10;
7968 * @propertyOf ui.grid.class:GridOptions
7969 * @description Extra rows to to render outside of the viewport, which helps with smoothness of scrolling.
7972 baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
7975 * @name scrollThreshold
7976 * @propertyOf ui.grid.class:GridOptions
7977 * @description Defaults to 4
7979 baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
7983 * @name excessColumns
7984 * @propertyOf ui.grid.class:GridOptions
7985 * @description Extra columns to to render outside of the viewport, which helps with smoothness of scrolling.
7988 baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
7991 * @name horizontalScrollThreshold
7992 * @propertyOf ui.grid.class:GridOptions
7993 * @description Defaults to 4
7995 baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
8000 * @name aggregationCalcThrottle
8001 * @propertyOf ui.grid.class:GridOptions
8002 * @description Default time in milliseconds to throttle aggregation calcuations, defaults to 500ms
8004 baseOptions.aggregationCalcThrottle = typeof(baseOptions.aggregationCalcThrottle) !== "undefined" ? baseOptions.aggregationCalcThrottle : 500;
8008 * @name wheelScrollThrottle
8009 * @propertyOf ui.grid.class:GridOptions
8010 * @description Default time in milliseconds to throttle scroll events to, defaults to 70ms
8012 baseOptions.wheelScrollThrottle = typeof(baseOptions.wheelScrollThrottle) !== "undefined" ? baseOptions.wheelScrollThrottle : 70;
8017 * @name scrollDebounce
8018 * @propertyOf ui.grid.class:GridOptions
8019 * @description Default time in milliseconds to debounce scroll events, defaults to 300ms
8021 baseOptions.scrollDebounce = typeof(baseOptions.scrollDebounce) !== "undefined" ? baseOptions.scrollDebounce : 300;
8025 * @name enableSorting
8026 * @propertyOf ui.grid.class:GridOptions
8027 * @description True by default. When enabled, this setting adds sort
8028 * widgets to the column headers, allowing sorting of the data for the entire grid.
8029 * Sorting can then be disabled / enabled on individual columns using the columnDefs,
8030 * if it set, it will override GridOptions enableSorting setting.
8032 baseOptions.enableSorting = baseOptions.enableSorting !== false;
8036 * @name enableFiltering
8037 * @propertyOf ui.grid.class:GridOptions
8038 * @description False by default. When enabled, this setting adds filter
8039 * boxes to each column header, allowing filtering within the column for the entire grid.
8040 * Filtering can then be disabled on individual columns using the columnDefs.
8042 baseOptions.enableFiltering = baseOptions.enableFiltering === true;
8046 * @name enableColumnMenus
8047 * @propertyOf ui.grid.class:GridOptions
8048 * @description True by default. When enabled, this setting displays a column
8049 * menu within each column.
8051 baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
8055 * @name enableVerticalScrollbar
8056 * @propertyOf ui.grid.class:GridOptions
8057 * @description {@link ui.grid.service:uiGridConstants#properties_scrollbars uiGridConstants.scrollbars.ALWAYS} by default.
8058 * This settings controls the vertical scrollbar for the grid.
8059 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
8061 baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
8065 * @name enableHorizontalScrollbar
8066 * @propertyOf ui.grid.class:GridOptions
8067 * @description {@link ui.grid.service:uiGridConstants#properties_scrollbars uiGridConstants.scrollbars.ALWAYS} by default.
8068 * This settings controls the horizontal scrollbar for the grid.
8069 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER
8071 baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
8075 * @name enableMinHeightCheck
8076 * @propertyOf ui.grid.class:GridOptions
8077 * @description True by default. When enabled, a newly initialized grid will check to see if it is tall enough to display
8078 * at least one row of data. If the grid is not tall enough, it will resize the DOM element to display minRowsToShow number
8081 baseOptions.enableMinHeightCheck = baseOptions.enableMinHeightCheck !== false;
8085 * @name minimumColumnSize
8086 * @propertyOf ui.grid.class:GridOptions
8087 * @description Columns can't be smaller than this, defaults to 10 pixels
8089 baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
8094 * @methodOf ui.grid.class:GridOptions
8095 * @description By default, rows are compared using object equality. This option can be overridden
8096 * to compare on any data item property or function
8097 * @param {object} entityA First Data Item to compare
8098 * @param {object} entityB Second Data Item to compare
8100 baseOptions.rowEquality = baseOptions.rowEquality || function(entityA, entityB) {
8101 return entityA === entityB;
8106 * @name headerTemplate
8107 * @propertyOf ui.grid.class:GridOptions
8108 * @description Null by default. When provided, this setting uses a custom header
8109 * template, rather than the default template. Can be set to either the name of a template file:
8110 * <pre> $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
8112 * <pre> $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
8113 * or the id of a pronapiled template (TBD how to use this).
8114 * </br>Refer to the custom header tutorial for more information.
8115 * If you want no header at all, you can set to an empty div:
8116 * <pre> $scope.gridOptions.headerTemplate = '<div></div>';</pre>
8118 * If you want to only have a static header, then you can set to static content. If
8119 * you want to tailor the existing column headers, then you should look at the
8120 * current 'ui-grid-header.html' template in github as your starting point.
8123 baseOptions.headerTemplate = baseOptions.headerTemplate || null;
8127 * @name footerTemplate
8128 * @propertyOf ui.grid.class:GridOptions
8129 * @description (optional) ui-grid/ui-grid-footer by default. This footer shows the per-column
8130 * aggregation totals.
8131 * When provided, this setting uses a custom footer template. Can be set to either the name of a template file 'footer_template.html', inline html
8132 * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
8133 * of a pronapiled template (TBD how to use this). Refer to the custom footer tutorial for more information.
8135 baseOptions.footerTemplate = baseOptions.footerTemplate || 'ui-grid/ui-grid-footer';
8139 * @name gridFooterTemplate
8140 * @propertyOf ui.grid.class:GridOptions
8141 * @description (optional) ui-grid/ui-grid-grid-footer by default. This template by default shows the
8142 * total items at the bottom of the grid, and the selected items if selection is enabled.
8144 baseOptions.gridFooterTemplate = baseOptions.gridFooterTemplate || 'ui-grid/ui-grid-grid-footer';
8149 * @propertyOf ui.grid.class:GridOptions
8150 * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a
8151 * custom row template. Can be set to either the name of a template file:
8152 * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
8154 * <pre> $scope.gridOptions.rowTemplate = '<div style="background-color: aquamarine" ng-click="grid.appScope.fnOne(row)" ng-repeat="col in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ui-grid-cell></div>';</pre>
8155 * or the id of a pronapiled template (TBD how to use this) can be provided.
8156 * </br>Refer to the custom row template tutorial for more information.
8158 baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
8162 * @name gridMenuTemplate
8163 * @propertyOf ui.grid.class:GridOptions
8164 * @description 'ui-grid/uiGridMenu' by default. When provided, this setting uses a
8165 * custom grid menu template.
8167 baseOptions.gridMenuTemplate = baseOptions.gridMenuTemplate || 'ui-grid/uiGridMenu';
8171 * @name appScopeProvider
8172 * @propertyOf ui.grid.class:GridOptions
8173 * @description by default, the parent scope of the ui-grid element will be assigned to grid.appScope
8174 * this property allows you to assign any reference you want to grid.appScope
8176 baseOptions.appScopeProvider = baseOptions.appScopeProvider || null;
8189 angular.module('ui.grid')
8193 * @name ui.grid.class:GridRenderContainer
8194 * @description The grid has render containers, allowing the ability to have pinned columns. If the grid
8195 * is right-to-left then there may be a right render container, if left-to-right then there may
8196 * be a left render container. There is always a body render container.
8197 * @param {string} name The name of the render container ('body', 'left', or 'right')
8198 * @param {Grid} grid the grid the render container is in
8199 * @param {object} options the render container options
8201 .factory('GridRenderContainer', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
8202 function GridRenderContainer(name, grid, options) {
8205 // if (gridUtil.type(grid) !== 'Grid') {
8206 // throw new Error('Grid argument is not a Grid object');
8213 // self.rowCache = [];
8214 // self.columnCache = [];
8216 self.visibleRowCache = [];
8217 self.visibleColumnCache = [];
8219 self.renderedRows = [];
8220 self.renderedColumns = [];
8222 self.prevScrollTop = 0;
8223 self.prevScrolltopPercentage = 0;
8224 self.prevRowScrollIndex = 0;
8226 self.prevScrollLeft = 0;
8227 self.prevScrollleftPercentage = 0;
8228 self.prevColumnScrollIndex = 0;
8230 self.columnStyles = "";
8232 self.viewportAdjusters = [];
8236 * @name hasHScrollbar
8237 * @propertyOf ui.grid.class:GridRenderContainer
8238 * @description flag to signal that container has a horizontal scrollbar
8240 self.hasHScrollbar = false;
8244 * @name hasVScrollbar
8245 * @propertyOf ui.grid.class:GridRenderContainer
8246 * @description flag to signal that container has a vertical scrollbar
8248 self.hasVScrollbar = false;
8252 * @name canvasHeightShouldUpdate
8253 * @propertyOf ui.grid.class:GridRenderContainer
8254 * @description flag to signal that container should recalculate the canvas size
8256 self.canvasHeightShouldUpdate = true;
8260 * @name canvasHeight
8261 * @propertyOf ui.grid.class:GridRenderContainer
8262 * @description last calculated canvas height value
8264 self.$$canvasHeight = 0;
8266 if (options && angular.isObject(options)) {
8267 angular.extend(self, options);
8270 grid.registerStyleComputation({
8273 self.updateColumnWidths();
8274 return self.columnStyles;
8280 GridRenderContainer.prototype.reset = function reset() {
8281 // this.rowCache.length = 0;
8282 // this.columnCache.length = 0;
8284 this.visibleColumnCache.length = 0;
8285 this.visibleRowCache.length = 0;
8287 this.renderedRows.length = 0;
8288 this.renderedColumns.length = 0;
8291 // TODO(c0bra): calculate size?? Should this be in a stackable directive?
8294 GridRenderContainer.prototype.containsColumn = function (col) {
8295 return this.visibleColumnCache.indexOf(col) !== -1;
8298 GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
8301 var rowAddedHeight = 0;
8302 var viewPortHeight = self.getViewportHeight();
8303 for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
8304 rowAddedHeight += self.visibleRowCache[i].height;
8310 GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
8312 var viewportWidth = this.getViewportWidth();
8316 // self.columns.forEach(function(col, i) {
8317 for (var i = 0; i < self.visibleColumnCache.length; i++) {
8318 var col = self.visibleColumnCache[i];
8320 if (totalWidth < viewportWidth) {
8321 totalWidth += col.drawnWidth ? col.drawnWidth : 0;
8326 for (var j = i; j >= i - min; j--) {
8327 currWidth += self.visibleColumnCache[j].drawnWidth ? self.visibleColumnCache[j].drawnWidth : 0;
8329 if (currWidth < viewportWidth) {
8338 GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
8339 return this.visibleRowCache.length;
8344 * @name registerViewportAdjuster
8345 * @methodOf ui.grid.class:GridRenderContainer
8346 * @description Registers an adjuster to the render container's available width or height. Adjusters are used
8347 * to tell the render container that there is something else consuming space, and to adjust it's size
8349 * @param {function} func the adjuster function we want to register
8352 GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
8353 this.viewportAdjusters.push(func);
8358 * @name removeViewportAdjuster
8359 * @methodOf ui.grid.class:GridRenderContainer
8360 * @description Removes an adjuster, should be used when your element is destroyed
8361 * @param {function} func the adjuster function we want to remove
8363 GridRenderContainer.prototype.removeViewportAdjuster = function removeViewportAdjuster(func) {
8364 var idx = this.viewportAdjusters.indexOf(func);
8367 this.viewportAdjusters.splice(idx, 1);
8373 * @name getViewportAdjustment
8374 * @methodOf ui.grid.class:GridRenderContainer
8375 * @description Gets the adjustment based on the viewportAdjusters.
8376 * @returns {object} a hash of { height: x, width: y }. Usually the values will be negative
8378 GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
8381 var adjustment = { height: 0, width: 0 };
8383 self.viewportAdjusters.forEach(function (func) {
8384 adjustment = func.call(this, adjustment);
8390 GridRenderContainer.prototype.getMargin = function getMargin(side) {
8395 self.viewportAdjusters.forEach(function (func) {
8396 var adjustment = func.call(this, { height: 0, width: 0 });
8398 if (adjustment.side && adjustment.side === side) {
8399 amount += adjustment.width * -1;
8406 GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
8409 var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
8411 var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
8414 var adjustment = self.getViewportAdjustment();
8416 viewPortHeight = viewPortHeight + adjustment.height;
8418 return viewPortHeight;
8421 GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
8424 var viewportWidth = self.grid.gridWidth;
8426 //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8427 // viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
8430 // var viewportWidth = 0;\
8431 // self.visibleColumnCache.forEach(function (column) {
8432 // viewportWidth += column.drawnWidth;
8435 var adjustment = self.getViewportAdjustment();
8437 viewportWidth = viewportWidth + adjustment.width;
8439 return viewportWidth;
8442 GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
8445 var viewportWidth = this.getViewportWidth();
8447 //if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
8448 // viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
8451 // var adjustment = self.getViewportAdjustment();
8452 // viewPortWidth = viewPortWidth + adjustment.width;
8454 return viewportWidth;
8460 * @name getCanvasHeight
8461 * @methodOf ui.grid.class:GridRenderContainer
8462 * @description Returns the total canvas height. Only recalculates if canvasHeightShouldUpdate = false
8463 * @returns {number} total height of all the visible rows in the container
8465 GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
8468 if (!self.canvasHeightShouldUpdate) {
8469 return self.$$canvasHeight;
8472 var oldCanvasHeight = self.$$canvasHeight;
8474 self.$$canvasHeight = 0;
8476 self.visibleRowCache.forEach(function(row){
8477 self.$$canvasHeight += row.height;
8481 self.canvasHeightShouldUpdate = false;
8483 self.grid.api.core.raise.canvasHeightChanged(oldCanvasHeight, self.$$canvasHeight);
8485 return self.$$canvasHeight;
8488 GridRenderContainer.prototype.getVerticalScrollLength = function getVerticalScrollLength() {
8489 return this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight !== 0 ? this.getCanvasHeight() - this.getViewportHeight() + this.grid.scrollbarHeight : -1;
8492 GridRenderContainer.prototype.getHorizontalScrollLength = function getHorizontalScrollLength() {
8493 return this.getCanvasWidth() - this.getViewportWidth() + this.grid.scrollbarWidth !== 0 ? this.getCanvasWidth() - this.getViewportWidth() + this.grid.scrollbarWidth : -1;
8496 GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
8499 var ret = self.canvasWidth;
8504 GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
8505 this.renderedRows.length = newRows.length;
8506 for (var i = 0; i < newRows.length; i++) {
8507 this.renderedRows[i] = newRows[i];
8511 GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
8515 this.renderedColumns.length = newColumns.length;
8516 for (var i = 0; i < newColumns.length; i++) {
8517 this.renderedColumns[i] = newColumns[i];
8520 this.updateColumnOffset();
8523 GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
8524 // Calculate the width of the columns on the left side that are no longer rendered.
8525 // That will be the offset for the columns as we scroll horizontally.
8526 var hiddenColumnsWidth = 0;
8527 for (var i = 0; i < this.currentFirstColumn; i++) {
8528 hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
8531 this.columnOffset = hiddenColumnsWidth;
8534 GridRenderContainer.prototype.scrollVertical = function (newScrollTop) {
8535 var vertScrollPercentage = -1;
8537 if (newScrollTop !== this.prevScrollTop) {
8538 var yDiff = newScrollTop - this.prevScrollTop;
8540 if (yDiff > 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; }
8541 if (yDiff < 0 ) { this.grid.scrollDirection = uiGridConstants.scrollDirection.UP; }
8543 var vertScrollLength = this.getVerticalScrollLength();
8545 vertScrollPercentage = newScrollTop / vertScrollLength;
8547 // console.log('vert', vertScrollPercentage, newScrollTop, vertScrollLength);
8549 if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
8550 if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
8552 this.adjustScrollVertical(newScrollTop, vertScrollPercentage);
8553 return vertScrollPercentage;
8557 GridRenderContainer.prototype.scrollHorizontal = function(newScrollLeft){
8558 var horizScrollPercentage = -1;
8562 if (newScrollLeft !== this.prevScrollLeft) {
8563 var xDiff = newScrollLeft - this.prevScrollLeft;
8565 if (xDiff > 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.RIGHT; }
8566 if (xDiff < 0) { this.grid.scrollDirection = uiGridConstants.scrollDirection.LEFT; }
8568 var horizScrollLength = this.getHorizontalScrollLength();
8569 if (horizScrollLength !== 0) {
8570 horizScrollPercentage = newScrollLeft / horizScrollLength;
8573 horizScrollPercentage = 0;
8576 this.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
8577 return horizScrollPercentage;
8581 GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
8582 if (this.prevScrollTop === scrollTop && !force) {
8586 if (typeof(scrollTop) === 'undefined' || scrollTop === undefined || scrollTop === null) {
8587 scrollTop = (this.getCanvasHeight() - this.getViewportHeight()) * scrollPercentage;
8590 this.adjustRows(scrollTop, scrollPercentage, false);
8592 this.prevScrollTop = scrollTop;
8593 this.prevScrolltopPercentage = scrollPercentage;
8595 this.grid.queueRefresh();
8598 GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
8599 if (this.prevScrollLeft === scrollLeft && !force) {
8603 if (typeof(scrollLeft) === 'undefined' || scrollLeft === undefined || scrollLeft === null) {
8604 scrollLeft = (this.getCanvasWidth() - this.getViewportWidth()) * scrollPercentage;
8607 this.adjustColumns(scrollLeft, scrollPercentage);
8609 this.prevScrollLeft = scrollLeft;
8610 this.prevScrollleftPercentage = scrollPercentage;
8612 this.grid.queueRefresh();
8615 GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage, postDataLoaded) {
8618 var minRows = self.minRowsToRender();
8620 var rowCache = self.visibleRowCache;
8622 var maxRowIndex = rowCache.length - minRows;
8624 // console.log('scroll%1', scrollPercentage);
8626 // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
8627 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
8628 scrollPercentage = scrollTop / self.getVerticalScrollLength();
8631 var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
8633 // console.log('maxRowIndex / scroll%', maxRowIndex, scrollPercentage, rowIndex);
8635 // Define a max row index that we can't scroll past
8636 if (rowIndex > maxRowIndex) {
8637 rowIndex = maxRowIndex;
8641 if (rowCache.length > self.grid.options.virtualizationThreshold) {
8642 if (!(typeof(scrollTop) === 'undefined' || scrollTop === null)) {
8643 // Have we hit the threshold going down?
8644 if ( !self.grid.suppressParentScrollDown && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8647 //Have we hit the threshold going up?
8648 if ( !self.grid.suppressParentScrollUp && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
8652 var rangeStart = {};
8655 rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
8656 rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
8658 newRange = [rangeStart, rangeEnd];
8661 var maxLen = self.visibleRowCache.length;
8662 newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
8665 self.updateViewableRowRange(newRange);
8667 self.prevRowScrollIndex = rowIndex;
8670 GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
8673 var minCols = self.minColumnsToRender();
8675 var columnCache = self.visibleColumnCache;
8676 var maxColumnIndex = columnCache.length - minCols;
8678 // Calculate the scroll percentage according to the scrollLeft location, if no percentage was provided
8679 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
8680 scrollPercentage = scrollLeft / self.getHorizontalScrollLength();
8683 var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
8685 // Define a max row index that we can't scroll past
8686 if (colIndex > maxColumnIndex) {
8687 colIndex = maxColumnIndex;
8691 if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
8692 /* Commented the following lines because otherwise the moved column wasn't visible immediately on the new position
8693 * in the case of many columns with horizontal scroll, one had to scroll left or right and then return in order to see it
8694 // Have we hit the threshold going down?
8695 if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8698 //Have we hit the threshold going up?
8699 if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
8703 var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
8704 var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
8706 newRange = [rangeStart, rangeEnd];
8709 var maxLen = self.visibleColumnCache.length;
8711 newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
8714 self.updateViewableColumnRange(newRange);
8716 self.prevColumnScrollIndex = colIndex;
8719 // Method for updating the visible rows
8720 GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
8721 // Slice out the range of rows from the data
8722 // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
8723 var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
8725 // Define the top-most rendered row
8726 this.currentTopRow = renderedRange[0];
8728 this.setRenderedRows(rowArr);
8731 // Method for updating the visible columns
8732 GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
8733 // Slice out the range of rows from the data
8734 // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
8735 var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
8737 // Define the left-most rendered columns
8738 this.currentFirstColumn = renderedRange[0];
8740 this.setRenderedColumns(columnArr);
8743 GridRenderContainer.prototype.headerCellWrapperStyle = function () {
8746 if (self.currentFirstColumn !== 0) {
8747 var offset = self.columnOffset;
8749 if (self.grid.isRTL()) {
8750 return { 'margin-right': offset + 'px' };
8753 return { 'margin-left': offset + 'px' };
8762 * @name updateColumnWidths
8763 * @propertyOf ui.grid.class:GridRenderContainer
8764 * @description Determine the appropriate column width of each column across all render containers.
8766 * Column width is easy when each column has a specified width. When columns are variable width (i.e.
8767 * have an * or % of the viewport) then we try to calculate so that things fit in. The problem is that
8768 * we have multiple render containers, and we don't want one render container to just take the whole viewport
8769 * when it doesn't need to - we want things to balance out across the render containers.
8771 * To do this, we use this method to calculate all the renderContainers, recognising that in a given render
8772 * cycle it'll get called once per render container, so it needs to return the same values each time.
8774 * The constraints on this method are therefore:
8775 * - must return the same value when called multiple times, to do this it needs to rely on properties of the
8776 * columns, but not properties that change when this is called (so it shouldn't rely on drawnWidth)
8778 * The general logic of this method is:
8779 * - calculate our total available width
8780 * - look at all the columns across all render containers, and work out which have widths and which have
8781 * constraints such as % or * or something else
8782 * - for those with *, count the total number of * we see and add it onto a running total, add this column to an * array
8783 * - for those with a %, allocate the % as a percentage of the viewport, having consideration of min and max
8784 * - for those with manual width (in pixels) we set the drawnWidth to the specified width
8785 * - we end up with an asterisks array still to process
8786 * - we look at our remaining width. If it's greater than zero, we divide it up among the asterisk columns, then process
8787 * them for min and max width constraints
8788 * - if it's zero or less, we set the asterisk columns to their minimum widths
8789 * - we use parseInt quite a bit, as we try to make all our column widths integers
8791 GridRenderContainer.prototype.updateColumnWidths = function () {
8794 var asterisksArray = [],
8799 // Get the width of the viewport
8800 var availableWidth = self.grid.getViewportWidth() - self.grid.scrollbarWidth;
8802 // get all the columns across all render containers, we have to calculate them all or one render container
8803 // could consume the whole viewport
8804 var columnCache = [];
8805 angular.forEach(self.grid.renderContainers, function( container, name){
8806 columnCache = columnCache.concat(container.visibleColumnCache);
8809 // look at each column, process any manual values or %, put the * into an array to look at later
8810 columnCache.forEach(function(column, i) {
8812 // Skip hidden columns
8813 if (!column.visible) { return; }
8815 if (angular.isNumber(column.width)) {
8816 // pixel width, set to this value
8817 width = parseInt(column.width, 10);
8818 usedWidthSum = usedWidthSum + width;
8819 column.drawnWidth = width;
8821 } else if (gridUtil.endsWith(column.width, "%")) {
8822 // percentage width, set to percentage of the viewport
8823 width = parseFloat(parseInt(column.width.replace(/%/g, ''), 10) / 100 * availableWidth);
8825 if ( width > column.maxWidth ){
8826 width = column.maxWidth;
8829 if ( width < column.minWidth ){
8830 width = column.minWidth;
8833 usedWidthSum = usedWidthSum + width;
8834 column.drawnWidth = width;
8835 } else if (angular.isString(column.width) && column.width.indexOf('*') !== -1) {
8836 // is an asterisk column, the gridColumn already checked the string consists only of '****'
8837 asteriskNum = asteriskNum + column.width.length;
8838 asterisksArray.push(column);
8842 // Get the remaining width (available width subtracted by the used widths sum)
8843 var remainingWidth = availableWidth - usedWidthSum;
8845 var i, column, colWidth;
8847 if (asterisksArray.length > 0) {
8848 // the width that each asterisk value would be assigned (this can be negative)
8849 var asteriskVal = remainingWidth / asteriskNum;
8851 asterisksArray.forEach(function( column ){
8852 var width = parseInt(column.width.length * asteriskVal, 10);
8854 if ( width > column.maxWidth ){
8855 width = column.maxWidth;
8858 if ( width < column.minWidth ){
8859 width = column.minWidth;
8862 usedWidthSum = usedWidthSum + width;
8863 column.drawnWidth = width;
8867 // If the grid width didn't divide evenly into the column widths and we have pixels left over, or our
8868 // calculated widths would have the grid narrower than the available space,
8869 // dole the remainder out one by one to make everything fit
8870 var processColumnUpwards = function(column){
8871 if ( column.drawnWidth < column.maxWidth && leftoverWidth > 0) {
8872 column.drawnWidth++;
8875 columnsToChange = true;
8879 var leftoverWidth = availableWidth - usedWidthSum;
8880 var columnsToChange = true;
8882 while (leftoverWidth > 0 && columnsToChange) {
8883 columnsToChange = false;
8884 asterisksArray.forEach(processColumnUpwards);
8887 // We can end up with too much width even though some columns aren't at their max width, in this situation
8888 // we can trim the columns a little
8889 var processColumnDownwards = function(column){
8890 if ( column.drawnWidth > column.minWidth && excessWidth > 0) {
8891 column.drawnWidth--;
8894 columnsToChange = true;
8898 var excessWidth = usedWidthSum - availableWidth;
8899 columnsToChange = true;
8901 while (excessWidth > 0 && columnsToChange) {
8902 columnsToChange = false;
8903 asterisksArray.forEach(processColumnDownwards);
8907 // all that was across all the renderContainers, now we need to work out what that calculation decided for
8908 // our renderContainer
8909 var canvasWidth = 0;
8910 self.visibleColumnCache.forEach(function(column){
8911 if ( column.visible ){
8912 canvasWidth = canvasWidth + column.drawnWidth;
8917 columnCache.forEach(function (column) {
8918 ret = ret + column.getColClassDefinition();
8921 self.canvasWidth = canvasWidth;
8923 // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
8926 // Set this render container's column styles so they can be used in style computation
8927 this.columnStyles = ret;
8930 GridRenderContainer.prototype.needsHScrollbarPlaceholder = function () {
8931 return this.grid.options.enableHorizontalScrollbar && !this.hasHScrollbar && !this.grid.disableScrolling;
8934 GridRenderContainer.prototype.getViewportStyle = function () {
8938 self.hasHScrollbar = false;
8939 self.hasVScrollbar = false;
8941 if (self.grid.disableScrolling) {
8942 styles['overflow-x'] = 'hidden';
8943 styles['overflow-y'] = 'hidden';
8947 if (self.name === 'body') {
8948 self.hasHScrollbar = self.grid.options.enableHorizontalScrollbar !== uiGridConstants.scrollbars.NEVER;
8949 if (!self.grid.isRTL()) {
8950 if (!self.grid.hasRightContainerColumns()) {
8951 self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8955 if (!self.grid.hasLeftContainerColumns()) {
8956 self.hasVScrollbar = self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER;
8960 else if (self.name === 'left') {
8961 self.hasVScrollbar = self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8964 self.hasVScrollbar = !self.grid.isRTL() ? self.grid.options.enableVerticalScrollbar !== uiGridConstants.scrollbars.NEVER : false;
8967 styles['overflow-x'] = self.hasHScrollbar ? 'scroll' : 'hidden';
8968 styles['overflow-y'] = self.hasVScrollbar ? 'scroll' : 'hidden';
8976 return GridRenderContainer;
8983 angular.module('ui.grid')
8984 .factory('GridRow', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
8988 * @name ui.grid.class:GridRow
8989 * @description GridRow is the viewModel for one logical row on the grid. A grid Row is not necessarily a one-to-one
8990 * relation to gridOptions.data.
8991 * @param {object} entity the array item from GridOptions.data
8992 * @param {number} index the current position of the row in the array
8993 * @param {Grid} reference to the parent grid
8995 function GridRow(entity, index, grid) {
9000 * @propertyOf ui.grid.class:GridRow
9001 * @description A reference back to the grid
9008 * @propertyOf ui.grid.class:GridRow
9009 * @description A reference to an item in gridOptions.data[]
9011 this.entity = entity;
9016 * @propertyOf ui.grid.class:GridRow
9017 * @description UniqueId of row
9019 this.uid = gridUtil.nextUid();
9024 * @propertyOf ui.grid.class:GridRow
9025 * @description If true, the row will be rendered
9028 this.visible = true;
9031 this.$$height = grid.options.rowHeight;
9038 * @propertyOf ui.grid.class:GridRow
9039 * @description height of each individual row. changing the height will flag all
9040 * row renderContainers to recalculate their canvas height
9042 Object.defineProperty(GridRow.prototype, 'height', {
9044 return this.$$height;
9046 set: function(height) {
9047 if (height !== this.$$height) {
9048 this.grid.updateCanvasHeight();
9049 this.$$height = height;
9056 * @name getQualifiedColField
9057 * @methodOf ui.grid.class:GridRow
9058 * @description returns the qualified field name as it exists on scope
9059 * ie: row.entity.fieldA
9060 * @param {GridCol} col column instance
9061 * @returns {string} resulting name that can be evaluated on scope
9063 GridRow.prototype.getQualifiedColField = function(col) {
9064 return 'row.' + this.getEntityQualifiedColField(col);
9069 * @name getEntityQualifiedColField
9070 * @methodOf ui.grid.class:GridRow
9071 * @description returns the qualified field name minus the row path
9073 * @param {GridCol} col column instance
9074 * @returns {string} resulting name that can be evaluated against a row
9076 GridRow.prototype.getEntityQualifiedColField = function(col) {
9077 var base = 'entity';
9078 if ( col.field === uiGridConstants.ENTITY_BINDING ) {
9081 return gridUtil.preEval(base + '.' + col.field);
9087 * @name setRowInvisible
9088 * @methodOf ui.grid.class:GridRow
9089 * @description Sets an override on the row that forces it to always
9090 * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
9092 * This method can be called from the api, passing in the gridRow we want
9093 * altered. It should really work by calling gridRow.setRowInvisible, but that's
9094 * not the way I coded it, and too late to change now. Changed to just call
9095 * the internal function row.setThisRowInvisible().
9097 * @param {GridRow} row the row we want to set to invisible
9100 GridRow.prototype.setRowInvisible = function ( row ) {
9101 if (row && row.setThisRowInvisible){
9102 row.setThisRowInvisible( 'user' );
9109 * @name clearRowInvisible
9110 * @methodOf ui.grid.class:GridRow
9111 * @description Clears an override on the row that forces it to always
9112 * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility.
9114 * This method can be called from the api, passing in the gridRow we want
9115 * altered. It should really work by calling gridRow.clearRowInvisible, but that's
9116 * not the way I coded it, and too late to change now. Changed to just call
9117 * the internal function row.clearThisRowInvisible().
9119 * @param {GridRow} row the row we want to clear the invisible flag
9122 GridRow.prototype.clearRowInvisible = function ( row ) {
9123 if (row && row.clearThisRowInvisible){
9124 row.clearThisRowInvisible( 'user' );
9131 * @name setThisRowInvisible
9132 * @methodOf ui.grid.class:GridRow
9133 * @description Sets an override on the row that forces it to always
9134 * be invisible. Emits the rowsVisibleChanged event if it changed the row visibility
9136 * @param {string} reason the reason (usually the module) for the row to be invisible.
9137 * E.g. grouping, user, filter
9138 * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
9140 GridRow.prototype.setThisRowInvisible = function ( reason, fromRowsProcessor ) {
9141 if ( !this.invisibleReason ){
9142 this.invisibleReason = {};
9144 this.invisibleReason[reason] = true;
9145 this.evaluateRowVisibility( fromRowsProcessor);
9151 * @name clearRowInvisible
9152 * @methodOf ui.grid.class:GridRow
9153 * @description Clears any override on the row visibility, returning it
9154 * to normal visibility calculations. Emits the rowsVisibleChanged
9157 * @param {string} reason the reason (usually the module) for the row to be invisible.
9158 * E.g. grouping, user, filter
9159 * @param {boolean} fromRowsProcessor whether we were called from a rowsProcessor, passed through to evaluateRowVisibility
9161 GridRow.prototype.clearThisRowInvisible = function ( reason, fromRowsProcessor ) {
9162 if (typeof(this.invisibleReason) !== 'undefined' ) {
9163 delete this.invisibleReason[reason];
9165 this.evaluateRowVisibility( fromRowsProcessor );
9171 * @name evaluateRowVisibility
9172 * @methodOf ui.grid.class:GridRow
9173 * @description Determines whether the row should be visible based on invisibleReason,
9174 * and if it changes the row visibility, then emits the rowsVisibleChanged event.
9176 * Queues a grid refresh, but doesn't call it directly to avoid hitting lots of grid refreshes.
9177 * @param {boolean} fromRowProcessor if true, then it won't raise events or queue the refresh, the
9178 * row processor does that already
9180 GridRow.prototype.evaluateRowVisibility = function ( fromRowProcessor ) {
9181 var newVisibility = true;
9182 if ( typeof(this.invisibleReason) !== 'undefined' ){
9183 angular.forEach(this.invisibleReason, function( value, key ){
9185 newVisibility = false;
9190 if ( typeof(this.visible) === 'undefined' || this.visible !== newVisibility ){
9191 this.visible = newVisibility;
9192 if ( !fromRowProcessor ){
9193 this.grid.queueGridRefresh();
9194 this.grid.api.core.raise.rowsVisibleChanged(this);
9209 * @name ui.grid.class:GridRowColumn
9210 * @param {GridRow} row The row for this pair
9211 * @param {GridColumn} column The column for this pair
9212 * @description A row and column pair that represents the intersection of these two entities.
9213 * Must be instantiated as a constructor using the `new` keyword.
9215 angular.module('ui.grid')
9216 .factory('GridRowColumn', ['$parse', '$filter',
9217 function GridRowColumnFactory($parse, $filter){
9218 var GridRowColumn = function GridRowColumn(row, col) {
9219 if ( !(this instanceof GridRowColumn)){
9220 throw "Using GridRowColumn as a function insead of as a constructor. Must be called with `new` keyword";
9226 * @propertyOf ui.grid.class:GridRowColumn
9227 * @description {@link ui.grid.class:GridRow }
9233 * @propertyOf ui.grid.class:GridRowColumn
9234 * @description {@link ui.grid.class:GridColumn }
9241 * @name getIntersectionValueRaw
9242 * @methodOf ui.grid.class:GridRowColumn
9243 * @description Gets the intersection of where the row and column meet.
9244 * @returns {String|Number|Object} The value from the grid data that this GridRowColumn points too.
9245 * If the column has a cellFilter this will NOT return the filtered value.
9247 GridRowColumn.prototype.getIntersectionValueRaw = function(){
9248 var getter = $parse(this.row.getEntityQualifiedColField(this.col));
9249 var context = this.row;
9250 return getter(context);
9252 return GridRowColumn;
9258 angular.module('ui.grid')
9259 .factory('ScrollEvent', ['gridUtil', function (gridUtil) {
9263 * @name ui.grid.class:ScrollEvent
9264 * @description Model for all scrollEvents
9265 * @param {Grid} grid that owns the scroll event
9266 * @param {GridRenderContainer} sourceRowContainer that owns the scroll event. Can be null
9267 * @param {GridRenderContainer} sourceColContainer that owns the scroll event. Can be null
9268 * @param {string} source the source of the event - from uiGridConstants.scrollEventSources or a string value of directive/service/factory.functionName
9270 function ScrollEvent(grid, sourceRowContainer, sourceColContainer, source) {
9273 throw new Error("grid argument is required");
9279 * @propertyOf ui.grid.class:ScrollEvent
9280 * @description A reference back to the grid
9289 * @propertyOf ui.grid.class:ScrollEvent
9290 * @description the source of the scroll event. limited to values from uiGridConstants.scrollEventSources
9292 self.source = source;
9298 * @propertyOf ui.grid.class:ScrollEvent
9299 * @description most scroll events from the mouse or trackpad require delay to operate properly
9300 * set to false to eliminate delay. Useful for scroll events that the grid causes, such as scrolling to make a row visible.
9302 self.withDelay = true;
9304 self.sourceRowContainer = sourceRowContainer;
9305 self.sourceColContainer = sourceColContainer;
9307 self.newScrollLeft = null;
9308 self.newScrollTop = null;
9312 self.verticalScrollLength = -9999999;
9313 self.horizontalScrollLength = -999999;
9318 * @name fireThrottledScrollingEvent
9319 * @methodOf ui.grid.class:ScrollEvent
9320 * @description fires a throttled event using grid.api.core.raise.scrollEvent
9322 self.fireThrottledScrollingEvent = gridUtil.throttle(function(sourceContainerId) {
9323 self.grid.scrollContainers(sourceContainerId, self);
9324 }, self.grid.options.wheelScrollThrottle, {trailing: true});
9331 * @name getNewScrollLeft
9332 * @methodOf ui.grid.class:ScrollEvent
9333 * @description returns newScrollLeft property if available; calculates a new value if it isn't
9335 ScrollEvent.prototype.getNewScrollLeft = function(colContainer, viewport){
9338 if (!self.newScrollLeft){
9339 var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
9341 var oldScrollLeft = gridUtil.normalizeScrollLeft(viewport, self.grid);
9343 var scrollXPercentage;
9344 if (typeof(self.x.percentage) !== 'undefined' && self.x.percentage !== undefined) {
9345 scrollXPercentage = self.x.percentage;
9347 else if (typeof(self.x.pixels) !== 'undefined' && self.x.pixels !== undefined) {
9348 scrollXPercentage = self.x.percentage = (oldScrollLeft + self.x.pixels) / scrollWidth;
9351 throw new Error("No percentage or pixel value provided for scroll event X axis");
9354 return Math.max(0, scrollXPercentage * scrollWidth);
9357 return self.newScrollLeft;
9363 * @name getNewScrollTop
9364 * @methodOf ui.grid.class:ScrollEvent
9365 * @description returns newScrollTop property if available; calculates a new value if it isn't
9367 ScrollEvent.prototype.getNewScrollTop = function(rowContainer, viewport){
9371 if (!self.newScrollTop){
9372 var scrollLength = rowContainer.getVerticalScrollLength();
9374 var oldScrollTop = viewport[0].scrollTop;
9376 var scrollYPercentage;
9377 if (typeof(self.y.percentage) !== 'undefined' && self.y.percentage !== undefined) {
9378 scrollYPercentage = self.y.percentage;
9380 else if (typeof(self.y.pixels) !== 'undefined' && self.y.pixels !== undefined) {
9381 scrollYPercentage = self.y.percentage = (oldScrollTop + self.y.pixels) / scrollLength;
9384 throw new Error("No percentage or pixel value provided for scroll event Y axis");
9387 return Math.max(0, scrollYPercentage * scrollLength);
9390 return self.newScrollTop;
9393 ScrollEvent.prototype.atTop = function(scrollTop) {
9394 return (this.y && (this.y.percentage === 0 || this.verticalScrollLength < 0) && scrollTop === 0);
9397 ScrollEvent.prototype.atBottom = function(scrollTop) {
9398 return (this.y && (this.y.percentage === 1 || this.verticalScrollLength === 0) && scrollTop > 0);
9401 ScrollEvent.prototype.atLeft = function(scrollLeft) {
9402 return (this.x && (this.x.percentage === 0 || this.horizontalScrollLength < 0) && scrollLeft === 0);
9405 ScrollEvent.prototype.atRight = function(scrollLeft) {
9406 return (this.x && (this.x.percentage === 1 || this.horizontalScrollLength ===0) && scrollLeft > 0);
9410 ScrollEvent.Sources = {
9411 ViewPortScroll: 'ViewPortScroll',
9412 RenderContainerMouseWheel: 'RenderContainerMouseWheel',
9413 RenderContainerTouchMove: 'RenderContainerTouchMove',
9428 * @name ui.grid.service:gridClassFactory
9430 * @description factory to return dom specific instances of a grid
9433 angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', 'Grid', 'GridColumn', 'GridRow',
9434 function (gridUtil, $q, $compile, $templateCache, uiGridConstants, Grid, GridColumn, GridRow) {
9440 * @methodOf ui.grid.service:gridClassFactory
9441 * @description Creates a new grid instance. Each instance will have a unique id
9442 * @param {object} options An object map of options to pass into the created grid instance.
9443 * @returns {Grid} grid
9445 createGrid : function(options) {
9446 options = (typeof(options) !== 'undefined') ? options : {};
9447 options.id = gridUtil.newId();
9448 var grid = new Grid(options);
9450 // NOTE/TODO: rowTemplate should always be defined...
9451 if (grid.options.rowTemplate) {
9452 var rowTemplateFnPromise = $q.defer();
9453 grid.getRowTemplateFn = rowTemplateFnPromise.promise;
9455 gridUtil.getTemplate(grid.options.rowTemplate)
9457 function (template) {
9458 var rowTemplateFn = $compile(template);
9459 rowTemplateFnPromise.resolve(rowTemplateFn);
9462 // Todo handle response error here?
9463 throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
9467 grid.registerColumnBuilder(service.defaultColumnBuilder);
9469 // Row builder for custom row templates
9470 grid.registerRowBuilder(service.rowTemplateAssigner);
9472 // Reset all rows to visible initially
9473 grid.registerRowsProcessor(function allRowsVisible(rows) {
9474 rows.forEach(function (row) {
9475 row.evaluateRowVisibility( true );
9481 grid.registerColumnsProcessor(function applyColumnVisibility(columns) {
9482 columns.forEach(function (column) {
9483 column.visible = angular.isDefined(column.colDef.visible) ? column.colDef.visible : true;
9489 grid.registerRowsProcessor(grid.searchRows, 100);
9491 // Register the default row processor, it sorts rows by selected columns
9492 if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
9493 grid.registerRowsProcessor(grid.options.externalSort, 200);
9496 grid.registerRowsProcessor(grid.sortByColumn, 200);
9504 * @name defaultColumnBuilder
9505 * @methodOf ui.grid.service:gridClassFactory
9506 * @description Processes designTime column definitions and applies them to col for the
9507 * core grid features
9508 * @param {object} colDef reference to column definition
9509 * @param {GridColumn} col reference to gridCol
9510 * @param {object} gridOptions reference to grid options
9512 defaultColumnBuilder: function (colDef, col, gridOptions) {
9514 var templateGetPromises = [];
9516 // Abstracts the standard template processing we do for every template type.
9517 var processTemplate = function( templateType, providedType, defaultTemplate, filterType, tooltipType ) {
9518 if ( !colDef[templateType] ){
9519 col[providedType] = defaultTemplate;
9521 col[providedType] = colDef[templateType];
9524 templateGetPromises.push(gridUtil.getTemplate(col[providedType])
9526 function (template) {
9527 if ( angular.isFunction(template) ) { template = template(); }
9528 var tooltipCall = ( tooltipType === 'cellTooltip' ) ? 'col.cellTooltip(row,col)' : 'col.headerTooltip(col)';
9529 if ( tooltipType && col[tooltipType] === false ){
9530 template = template.replace(uiGridConstants.TOOLTIP, '');
9531 } else if ( tooltipType && col[tooltipType] ){
9532 template = template.replace(uiGridConstants.TOOLTIP, 'title="{{' + tooltipCall + ' CUSTOM_FILTERS }}"');
9536 col[templateType] = template.replace(uiGridConstants.CUSTOM_FILTERS, function() {
9537 return col[filterType] ? "|" + col[filterType] : "";
9540 col[templateType] = template;
9544 throw new Error("Couldn't fetch/use colDef." + templateType + " '" + colDef[templateType] + "'");
9553 * @name cellTemplate
9554 * @propertyOf ui.grid.class:GridOptions.columnDef
9555 * @description a custom template for each cell in this column. The default
9556 * is ui-grid/uiGridCell. If you are using the cellNav feature, this template
9557 * must contain a div that can receive focus.
9560 processTemplate( 'cellTemplate', 'providedCellTemplate', 'ui-grid/uiGridCell', 'cellFilter', 'cellTooltip' );
9561 col.cellTemplatePromise = templateGetPromises[0];
9565 * @name headerCellTemplate
9566 * @propertyOf ui.grid.class:GridOptions.columnDef
9567 * @description a custom template for the header for this column. The default
9568 * is ui-grid/uiGridHeaderCell
9571 processTemplate( 'headerCellTemplate', 'providedHeaderCellTemplate', 'ui-grid/uiGridHeaderCell', 'headerCellFilter', 'headerTooltip' );
9575 * @name footerCellTemplate
9576 * @propertyOf ui.grid.class:GridOptions.columnDef
9577 * @description a custom template for the footer for this column. The default
9578 * is ui-grid/uiGridFooterCell
9581 processTemplate( 'footerCellTemplate', 'providedFooterCellTemplate', 'ui-grid/uiGridFooterCell', 'footerCellFilter' );
9585 * @name filterHeaderTemplate
9586 * @propertyOf ui.grid.class:GridOptions.columnDef
9587 * @description a custom template for the filter input. The default is ui-grid/ui-grid-filter
9590 processTemplate( 'filterHeaderTemplate', 'providedFilterHeaderTemplate', 'ui-grid/ui-grid-filter' );
9592 // Create a promise for the compiled element function
9593 col.compiledElementFnDefer = $q.defer();
9595 return $q.all(templateGetPromises);
9599 rowTemplateAssigner: function rowTemplateAssigner(row) {
9602 // Row has no template assigned to it
9603 if (!row.rowTemplate) {
9604 // Use the default row template from the grid
9605 row.rowTemplate = grid.options.rowTemplate;
9607 // Use the grid's function for fetching the compiled row template function
9608 row.getRowTemplateFn = grid.getRowTemplateFn;
9610 // Row has its own template assigned
9612 // Create a promise for the compiled row template function
9613 var perRowTemplateFnPromise = $q.defer();
9614 row.getRowTemplateFn = perRowTemplateFnPromise.promise;
9616 // Get the row template
9617 gridUtil.getTemplate(row.rowTemplate)
9618 .then(function (template) {
9619 // Compile the template
9620 var rowTemplateFn = $compile(template);
9622 // Resolve the compiled template function promise
9623 perRowTemplateFnPromise.resolve(rowTemplateFn);
9626 // Todo handle response error here?
9627 throw new Error("Couldn't fetch/use row template '" + row.rowTemplate + "'");
9631 return row.getRowTemplateFn;
9635 //class definitions (moved to separate factories)
9644 var module = angular.module('ui.grid');
9646 function escapeRegExp(str) {
9647 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
9653 * @name ui.grid.service:rowSearcher
9655 * @description Service for searching/filtering rows based on column value conditions.
9657 module.service('rowSearcher', ['gridUtil', 'uiGridConstants', function (gridUtil, uiGridConstants) {
9658 var defaultCondition = uiGridConstants.filter.CONTAINS;
9660 var rowSearcher = {};
9665 * @methodOf ui.grid.service:rowSearcher
9666 * @description Get the term from a filter
9667 * Trims leading and trailing whitespace
9668 * @param {object} filter object to use
9669 * @returns {object} Parsed term
9671 rowSearcher.getTerm = function getTerm(filter) {
9672 if (typeof(filter.term) === 'undefined') { return filter.term; }
9674 var term = filter.term;
9676 // Strip leading and trailing whitespace if the term is a string
9677 if (typeof(term) === 'string') {
9687 * @methodOf ui.grid.service:rowSearcher
9688 * @description Remove leading and trailing asterisk (*) from the filter's term
9689 * @param {object} filter object to use
9690 * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9692 rowSearcher.stripTerm = function stripTerm(filter) {
9693 var term = rowSearcher.getTerm(filter);
9695 if (typeof(term) === 'string') {
9696 return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
9706 * @name guessCondition
9707 * @methodOf ui.grid.service:rowSearcher
9708 * @description Guess the condition for a filter based on its term
9710 * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
9711 * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
9712 * @param {object} filter object to use
9713 * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
9715 rowSearcher.guessCondition = function guessCondition(filter) {
9716 if (typeof(filter.term) === 'undefined' || !filter.term) {
9717 return defaultCondition;
9720 var term = rowSearcher.getTerm(filter);
9722 if (/\*/.test(term)) {
9723 var regexpFlags = '';
9724 if (!filter.flags || !filter.flags.caseSensitive) {
9728 var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
9729 return new RegExp('^' + reText + '$', regexpFlags);
9731 // Otherwise default to default condition
9733 return defaultCondition;
9740 * @name setupFilters
9741 * @methodOf ui.grid.service:rowSearcher
9742 * @description For a given columns filters (either col.filters, or [col.filter] can be passed in),
9743 * do all the parsing and pre-processing and store that data into a new filters object. The object
9744 * has the condition, the flags, the stripped term, and a parsed reg exp if there was one.
9746 * We could use a forEach in here, since it's much less performance sensitive, but since we're using
9747 * for loops everywhere else in this module...
9749 * @param {array} filters the filters from the column (col.filters or [col.filter])
9750 * @returns {array} An array of parsed/preprocessed filters
9752 rowSearcher.setupFilters = function setupFilters( filters ){
9753 var newFilters = [];
9755 var filtersLength = filters.length;
9756 for ( var i = 0; i < filtersLength; i++ ){
9757 var filter = filters[i];
9759 if ( filter.noTerm || !gridUtil.isNullOrUndefined(filter.term) ){
9762 var regexpFlags = '';
9763 if (!filter.flags || !filter.flags.caseSensitive) {
9767 if ( !gridUtil.isNullOrUndefined(filter.term) ){
9768 // it is possible to have noTerm.
9769 if ( filter.rawTerm ){
9770 newFilter.term = filter.term;
9772 newFilter.term = rowSearcher.stripTerm(filter);
9775 newFilter.noTerm = filter.noTerm;
9777 if ( filter.condition ){
9778 newFilter.condition = filter.condition;
9780 newFilter.condition = rowSearcher.guessCondition(filter);
9783 newFilter.flags = angular.extend( { caseSensitive: false, date: false }, filter.flags );
9785 if (newFilter.condition === uiGridConstants.filter.STARTS_WITH) {
9786 newFilter.startswithRE = new RegExp('^' + newFilter.term, regexpFlags);
9789 if (newFilter.condition === uiGridConstants.filter.ENDS_WITH) {
9790 newFilter.endswithRE = new RegExp(newFilter.term + '$', regexpFlags);
9793 if (newFilter.condition === uiGridConstants.filter.CONTAINS) {
9794 newFilter.containsRE = new RegExp(newFilter.term, regexpFlags);
9797 if (newFilter.condition === uiGridConstants.filter.EXACT) {
9798 newFilter.exactRE = new RegExp('^' + newFilter.term + '$', regexpFlags);
9801 newFilters.push(newFilter);
9810 * @name runColumnFilter
9811 * @methodOf ui.grid.service:rowSearcher
9812 * @description Runs a single pre-parsed filter against a cell, returning true
9813 * if the cell matches that one filter.
9815 * @param {Grid} grid the grid we're working against
9816 * @param {GridRow} row the row we're matching against
9817 * @param {GridCol} column the column that we're working against
9818 * @param {object} filter the specific, preparsed, filter that we want to test
9819 * @returns {boolean} true if we match (row stays visible)
9821 rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, filter) {
9822 // Cache typeof condition
9823 var conditionType = typeof(filter.condition);
9825 // Term to search for.
9826 var term = filter.term;
9828 // Get the column value for this row
9830 if ( column.filterCellFiltered ){
9831 value = grid.getCellDisplayValue(row, column);
9833 value = grid.getCellValue(row, column);
9837 // If the filter's condition is a RegExp, then use it
9838 if (filter.condition instanceof RegExp) {
9839 return filter.condition.test(value);
9842 // If the filter's condition is a function, run it
9843 if (conditionType === 'function') {
9844 return filter.condition(term, value, row, column);
9847 if (filter.startswithRE) {
9848 return filter.startswithRE.test(value);
9851 if (filter.endswithRE) {
9852 return filter.endswithRE.test(value);
9855 if (filter.containsRE) {
9856 return filter.containsRE.test(value);
9859 if (filter.exactRE) {
9860 return filter.exactRE.test(value);
9863 if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
9864 var regex = new RegExp('^' + term + '$');
9865 return !regex.exec(value);
9868 if (typeof(value) === 'number' && typeof(term) === 'string' ){
9869 // if the term has a decimal in it, it comes through as '9\.4', we need to take out the \
9870 // the same for negative numbers
9871 // TODO: I suspect the right answer is to look at escapeRegExp at the top of this code file, maybe it's not needed?
9872 var tempFloat = parseFloat(term.replace(/\\\./,'.').replace(/\\\-/,'-'));
9873 if (!isNaN(tempFloat)) {
9878 if (filter.flags.date === true) {
9879 value = new Date(value);
9880 // If the term has a dash in it, it comes through as '\-' -- we need to take out the '\'.
9881 term = new Date(term.replace(/\\/g, ''));
9884 if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
9885 return (value > term);
9888 if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
9889 return (value >= term);
9892 if (filter.condition === uiGridConstants.filter.LESS_THAN) {
9893 return (value < term);
9896 if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
9897 return (value <= term);
9906 * @name useExternalFiltering
9907 * @propertyOf ui.grid.class:GridOptions
9908 * @description False by default. When enabled, this setting suppresses the internal filtering.
9909 * All UI logic will still operate, allowing filter conditions to be set and modified.
9911 * The external filter logic can listen for the `filterChange` event, which fires whenever
9912 * a filter has been adjusted.
9916 * @name searchColumn
9917 * @methodOf ui.grid.service:rowSearcher
9918 * @description Process provided filters on provided column against a given row. If the row meets
9919 * the conditions on all the filters, return true.
9920 * @param {Grid} grid Grid to search in
9921 * @param {GridRow} row Row to search on
9922 * @param {GridCol} column Column with the filters to use
9923 * @param {array} filters array of pre-parsed/preprocessed filters to apply
9924 * @returns {boolean} Whether the column matches or not.
9926 rowSearcher.searchColumn = function searchColumn(grid, row, column, filters) {
9927 if (grid.options.useExternalFiltering) {
9931 var filtersLength = filters.length;
9932 for (var i = 0; i < filtersLength; i++) {
9933 var filter = filters[i];
9935 if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){
9936 var ret = rowSearcher.runColumnFilter(grid, row, column, filter);
9950 * @methodOf ui.grid.service:rowSearcher
9951 * @description Run a search across the given rows and columns, marking any rows that don't
9952 * match the stored col.filters or col.filter as invisible.
9953 * @param {Grid} grid Grid instance to search inside
9954 * @param {Array[GridRow]} rows GridRows to filter
9955 * @param {Array[GridColumn]} columns GridColumns with filters to process
9957 rowSearcher.search = function search(grid, rows, columns) {
9959 * Added performance optimisations into this code base, as this logic creates deeply nested
9960 * loops and is therefore very performance sensitive. In particular, avoiding forEach as
9961 * this impacts some browser optimisers (particularly Chrome), using iterators instead
9964 // Don't do anything if we weren't passed any rows
9969 // don't filter if filtering currently disabled
9970 if (!grid.options.enableFiltering){
9974 // Build list of filters to apply
9975 var filterData = [];
9977 var colsLength = columns.length;
9979 var hasTerm = function( filters ) {
9980 var hasTerm = false;
9982 filters.forEach( function (filter) {
9983 if ( !gridUtil.isNullOrUndefined(filter.term) && filter.term !== '' || filter.noTerm ){
9991 for (var i = 0; i < colsLength; i++) {
9992 var col = columns[i];
9994 if (typeof(col.filters) !== 'undefined' && hasTerm(col.filters) ) {
9995 filterData.push( { col: col, filters: rowSearcher.setupFilters(col.filters) } );
9999 if (filterData.length > 0) {
10000 // define functions outside the loop, performance optimisation
10001 var foreachRow = function(grid, row, col, filters){
10002 if ( row.visible && !rowSearcher.searchColumn(grid, row, col, filters) ) {
10003 row.visible = false;
10007 var foreachFilterCol = function(grid, filterData){
10008 var rowsLength = rows.length;
10009 for ( var i = 0; i < rowsLength; i++){
10010 foreachRow(grid, rows[i], filterData.col, filterData.filters);
10014 // nested loop itself - foreachFilterCol, which in turn calls foreachRow
10015 var filterDataLength = filterData.length;
10016 for ( var j = 0; j < filterDataLength; j++){
10017 foreachFilterCol( grid, filterData[j] );
10020 if (grid.api.core.raise.rowsVisibleChanged) {
10021 grid.api.core.raise.rowsVisibleChanged();
10024 // drop any invisible rows
10025 // keeping these, as needed with filtering for trees - we have to come back and make parent nodes visible if child nodes are selected in the filter
10026 // rows = rows.filter(function(row){ return row.visible; });
10033 return rowSearcher;
10040 var module = angular.module('ui.grid');
10044 * @name ui.grid.class:rowSorter
10045 * @description rowSorter provides the default sorting mechanisms,
10046 * including guessing column types and applying appropriate sort
10051 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
10052 var currencyRegexStr =
10054 uiGridConstants.CURRENCY_SYMBOLS
10055 .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
10056 .join('|') + // Join all the symbols together with |s
10059 // /^[-+]?[£$¤¥]?[\d,.]+%?$/
10060 var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
10063 // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
10064 // this takes a piece of data from the cell and tries to determine its type and what sorting
10065 // function to use for it
10072 * @methodOf ui.grid.class:rowSorter
10073 * @name guessSortFn
10074 * @description Assigns a sort function to use based on the itemType in the column
10075 * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'. And
10076 * error will be thrown for any other type.
10077 * @returns {function} a sort function that will sort that type
10079 rowSorter.guessSortFn = function guessSortFn(itemType) {
10080 switch (itemType) {
10082 return rowSorter.sortNumber;
10084 return rowSorter.sortNumberStr;
10086 return rowSorter.sortBool;
10088 return rowSorter.sortAlpha;
10090 return rowSorter.sortDate;
10092 return rowSorter.basicSort;
10094 throw new Error('No sorting function found for type:' + itemType);
10101 * @methodOf ui.grid.class:rowSorter
10102 * @name handleNulls
10103 * @description Sorts nulls and undefined to the bottom (top when
10104 * descending). Called by each of the internal sorters before
10105 * attempting to sort. Note that this method is available on the core api
10106 * via gridApi.core.sortHandleNulls
10107 * @param {object} a sort value a
10108 * @param {object} b sort value b
10109 * @returns {number} null if there were no nulls/undefineds, otherwise returns
10110 * a sort value that should be passed back from the sort function
10112 rowSorter.handleNulls = function handleNulls(a, b) {
10113 // We want to allow zero values and false values to be evaluated in the sort function
10114 if ((!a && a !== 0 && a !== false) || (!b && b !== 0 && b !== false)) {
10115 // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
10116 if ((!a && a !== 0 && a !== false) && (!b && b !== 0 && b !== false)) {
10119 else if (!a && a !== 0 && a !== false) {
10122 else if (!b && b !== 0 && b !== false) {
10132 * @methodOf ui.grid.class:rowSorter
10134 * @description Sorts any values that provide the < method, including strings
10135 * or numbers. Handles nulls and undefined through calling handleNulls
10136 * @param {object} a sort value a
10137 * @param {object} b sort value b
10138 * @returns {number} normal sort function, returns -ve, 0, +ve
10140 rowSorter.basicSort = function basicSort(a, b) {
10141 var nulls = rowSorter.handleNulls(a, b);
10142 if ( nulls !== null ){
10158 * @methodOf ui.grid.class:rowSorter
10160 * @description Sorts numerical values. Handles nulls and undefined through calling handleNulls
10161 * @param {object} a sort value a
10162 * @param {object} b sort value b
10163 * @returns {number} normal sort function, returns -ve, 0, +ve
10165 rowSorter.sortNumber = function sortNumber(a, b) {
10166 var nulls = rowSorter.handleNulls(a, b);
10167 if ( nulls !== null ){
10177 * @methodOf ui.grid.class:rowSorter
10178 * @name sortNumberStr
10179 * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).
10180 * Handles nulls and undefined through calling handleNulls
10181 * @param {object} a sort value a
10182 * @param {object} b sort value b
10183 * @returns {number} normal sort function, returns -ve, 0, +ve
10185 rowSorter.sortNumberStr = function sortNumberStr(a, b) {
10186 var nulls = rowSorter.handleNulls(a, b);
10187 if ( nulls !== null ){
10190 var numA, // The parsed number form of 'a'
10191 numB, // The parsed number form of 'b'
10195 // Try to parse 'a' to a float
10196 numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
10198 // If 'a' couldn't be parsed to float, flag it as bad
10203 // Try to parse 'b' to a float
10204 numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
10206 // If 'b' couldn't be parsed to float, flag it as bad
10211 // We want bad ones to get pushed to the bottom... which effectively is "greater than"
10212 if (badA && badB) {
10224 return numA - numB;
10231 * @methodOf ui.grid.class:rowSorter
10233 * @description Sorts string values. Handles nulls and undefined through calling handleNulls
10234 * @param {object} a sort value a
10235 * @param {object} b sort value b
10236 * @returns {number} normal sort function, returns -ve, 0, +ve
10238 rowSorter.sortAlpha = function sortAlpha(a, b) {
10239 var nulls = rowSorter.handleNulls(a, b);
10240 if ( nulls !== null ){
10243 var strA = a.toString().toLowerCase(),
10244 strB = b.toString().toLowerCase();
10246 return strA === strB ? 0 : strA.localeCompare(strB);
10253 * @methodOf ui.grid.class:rowSorter
10255 * @description Sorts date values. Handles nulls and undefined through calling handleNulls.
10256 * Handles date strings by converting to Date object if not already an instance of Date
10257 * @param {object} a sort value a
10258 * @param {object} b sort value b
10259 * @returns {number} normal sort function, returns -ve, 0, +ve
10261 rowSorter.sortDate = function sortDate(a, b) {
10262 var nulls = rowSorter.handleNulls(a, b);
10263 if ( nulls !== null ){
10266 if (!(a instanceof Date)) {
10269 if (!(b instanceof Date)){
10272 var timeA = a.getTime(),
10273 timeB = b.getTime();
10275 return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
10282 * @methodOf ui.grid.class:rowSorter
10284 * @description Sorts boolean values, true is considered larger than false.
10285 * Handles nulls and undefined through calling handleNulls
10286 * @param {object} a sort value a
10287 * @param {object} b sort value b
10288 * @returns {number} normal sort function, returns -ve, 0, +ve
10290 rowSorter.sortBool = function sortBool(a, b) {
10291 var nulls = rowSorter.handleNulls(a, b);
10292 if ( nulls !== null ){
10311 * @methodOf ui.grid.class:rowSorter
10313 * @description Get the sort function for the column. Looks first in
10314 * rowSorter.colSortFnCache using the column name, failing that it
10315 * looks at col.sortingAlgorithm (and puts it in the cache), failing that
10316 * it guesses the sort algorithm based on the data type.
10318 * The cache currently seems a bit pointless, as none of the work we do is
10319 * processor intensive enough to need caching. Presumably in future we might
10320 * inspect the row data itself to guess the sort function, and in that case
10321 * it would make sense to have a cache, the infrastructure is in place to allow
10324 * @param {Grid} grid the grid to consider
10325 * @param {GridCol} col the column to find a function for
10326 * @param {array} rows an array of grid rows. Currently unused, but presumably in future
10327 * we might inspect the rows themselves to decide what sort of data might be there
10328 * @returns {function} the sort function chosen for the column
10330 rowSorter.getSortFn = function getSortFn(grid, col, rows) {
10333 // See if we already figured out what to use to sort the column and have it in the cache
10334 if (rowSorter.colSortFnCache[col.colDef.name]) {
10335 sortFn = rowSorter.colSortFnCache[col.colDef.name];
10337 // If the column has its OWN sorting algorithm, use that
10338 else if (col.sortingAlgorithm !== undefined) {
10339 sortFn = col.sortingAlgorithm;
10340 rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
10342 // Always default to sortAlpha when sorting after a cellFilter
10343 else if ( col.sortCellFiltered && col.cellFilter ){
10344 sortFn = rowSorter.sortAlpha;
10345 rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10347 // Try and guess what sort function to use
10349 // Guess the sort function
10350 sortFn = rowSorter.guessSortFn(col.colDef.type);
10352 // If we found a sort function, cache it
10354 rowSorter.colSortFnCache[col.colDef.name] = sortFn;
10357 // We assign the alpha sort because anything that is null/undefined will never get passed to
10358 // the actual sorting function. It will get caught in our null check and returned to be sorted
10359 // down to the bottom
10360 sortFn = rowSorter.sortAlpha;
10371 * @methodOf ui.grid.class:rowSorter
10372 * @name prioritySort
10373 * @description Used where multiple columns are present in the sort criteria,
10374 * we determine which column should take precedence in the sort by sorting
10375 * the columns based on their sort.priority
10377 * @param {gridColumn} a column a
10378 * @param {gridColumn} b column b
10379 * @returns {number} normal sort function, returns -ve, 0, +ve
10381 rowSorter.prioritySort = function (a, b) {
10382 // Both columns have a sort priority
10383 if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
10384 // A is higher priority
10385 if (a.sort.priority < b.sort.priority) {
10389 else if (a.sort.priority === b.sort.priority) {
10397 // Only A has a priority
10398 else if (a.sort.priority !== undefined) {
10401 // Only B has a priority
10402 else if (b.sort.priority !== undefined) {
10405 // Neither has a priority
10414 * @name useExternalSorting
10415 * @propertyOf ui.grid.class:GridOptions
10416 * @description Prevents the internal sorting from executing. Events will
10417 * still be fired when the sort changes, and the sort information on
10418 * the columns will be updated, allowing an external sorter (for example,
10419 * server sorting) to be implemented. Defaults to false.
10424 * @methodOf ui.grid.class:rowSorter
10426 * @description sorts the grid
10427 * @param {Object} grid the grid itself
10428 * @param {array} rows the rows to be sorted
10429 * @param {array} columns the columns in which to look
10430 * for sort criteria
10431 * @returns {array} sorted rows
10433 rowSorter.sort = function rowSorterSort(grid, rows, columns) {
10434 // first make sure we are even supposed to do work
10439 if (grid.options.useExternalSorting){
10443 // Build the list of columns to sort by
10445 columns.forEach(function (col) {
10446 if (col.sort && !col.sort.ignoreSort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
10447 sortCols.push(col);
10451 // Sort the "sort columns" by their sort priority
10452 sortCols = sortCols.sort(rowSorter.prioritySort);
10454 // Now rows to sort by, maintain original order
10455 if (sortCols.length === 0) {
10459 // Re-usable variables
10460 var col, direction;
10462 // put a custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10463 var setIndex = function( row, idx ){
10464 row.entity.$$uiGridIndex = idx;
10466 rows.forEach(setIndex);
10468 // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
10469 // var d = data.slice(0);
10470 var r = rows.slice(0);
10472 // Now actually sort the data
10473 var rowSortFn = function (rowA, rowB) {
10478 while (tem === 0 && idx < sortCols.length) {
10479 // grab the metadata for the rest of the logic
10480 col = sortCols[idx];
10481 direction = sortCols[idx].sort.direction;
10483 sortFn = rowSorter.getSortFn(grid, col, r);
10487 if ( col.sortCellFiltered ){
10488 propA = grid.getCellDisplayValue(rowA, col);
10489 propB = grid.getCellDisplayValue(rowB, col);
10491 propA = grid.getCellValue(rowA, col);
10492 propB = grid.getCellValue(rowB, col);
10495 tem = sortFn(propA, propB, rowA, rowB, direction, col);
10500 // Chrome doesn't implement a stable sort function. If our sort returns 0
10501 // (i.e. the items are equal), and we're at the last sort column in the list,
10502 // then return the previous order using our custom
10505 return rowA.entity.$$uiGridIndex - rowB.entity.$$uiGridIndex;
10508 // Made it this far, we don't have to worry about null & undefined
10509 if (direction === uiGridConstants.ASC) {
10516 var newRows = rows.sort(rowSortFn);
10518 // remove the custom index field on each row, used to make a stable sort out of unstable sorts (e.g. Chrome)
10519 var clearIndex = function( row, idx ){
10520 delete row.entity.$$uiGridIndex;
10522 rows.forEach(clearIndex);
10534 var module = angular.module('ui.grid');
10537 if (typeof Function.prototype.bind !== "function") {
10538 bindPolyfill = function() {
10539 var slice = Array.prototype.slice;
10540 return function(context) {
10542 args = slice.call(arguments, 1);
10544 return function() {
10545 return arguments.length ? fn.apply(context, args.concat(slice.call(arguments))) : fn.apply(context, args);
10548 return function() {
10549 return arguments.length ? fn.apply(context, arguments) : fn.call(context);
10555 function getStyles (elem) {
10557 if (typeof(e.length) !== 'undefined' && e.length) {
10561 return e.ownerDocument.defaultView.getComputedStyle(e, null);
10564 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
10565 // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
10566 // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
10567 rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
10568 cssShow = { position: "absolute", visibility: "hidden", display: "block" };
10570 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
10571 var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
10572 // If we already have the right measurement, avoid augmentation
10574 // Otherwise initialize for horizontal or vertical properties
10575 name === 'width' ? 1 : 0,
10579 var sides = ['Top', 'Right', 'Bottom', 'Left'];
10581 for ( ; i < 4; i += 2 ) {
10582 var side = sides[i];
10583 // dump('side', side);
10585 // both box models exclude margin, so add it if we want it
10586 if ( extra === 'margin' ) {
10587 var marg = parseFloat(styles[extra + side]);
10588 if (!isNaN(marg)) {
10592 // dump('val1', val);
10594 if ( isBorderBox ) {
10595 // border-box includes padding, so remove it if we want content
10596 if ( extra === 'content' ) {
10597 var padd = parseFloat(styles['padding' + side]);
10598 if (!isNaN(padd)) {
10600 // dump('val2', val);
10604 // at this point, extra isn't border nor margin, so remove border
10605 if ( extra !== 'margin' ) {
10606 var bordermarg = parseFloat(styles['border' + side + 'Width']);
10607 if (!isNaN(bordermarg)) {
10609 // dump('val3', val);
10614 // at this point, extra isn't content, so add padding
10615 var nocontentPad = parseFloat(styles['padding' + side]);
10616 if (!isNaN(nocontentPad)) {
10617 val += nocontentPad;
10618 // dump('val4', val);
10621 // at this point, extra isn't content nor padding, so add border
10622 if ( extra !== 'padding') {
10623 var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
10624 if (!isNaN(nocontentnopad)) {
10625 val += nocontentnopad;
10626 // dump('val5', val);
10632 // dump('augVal', val);
10637 function getWidthOrHeight( elem, name, extra ) {
10638 // Start with offset property, which is equivalent to the border-box value
10639 var valueIsBorderBox = true,
10640 val, // = name === 'width' ? elem.offsetWidth : elem.offsetHeight,
10641 styles = getStyles(elem),
10642 isBorderBox = styles['boxSizing'] === 'border-box';
10644 // some non-html elements return undefined for offsetWidth, so check for null/undefined
10645 // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
10646 // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
10647 if ( val <= 0 || val == null ) {
10648 // Fall back to computed then uncomputed css if necessary
10649 val = styles[name];
10650 if ( val < 0 || val == null ) {
10651 val = elem.style[ name ];
10654 // Computed unit is not pixels. Stop here and return.
10655 if ( rnumnonpx.test(val) ) {
10659 // we need the check for style in case a browser which returns unreliable values
10660 // for getComputedStyle silently falls back to the reliable elem.style
10661 valueIsBorderBox = isBorderBox &&
10662 ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
10664 // Normalize "", auto, and prepare for extra
10665 val = parseFloat( val ) || 0;
10668 // use the active box-sizing model to add/subtract irrelevant styles
10670 augmentWidthOrHeight(
10673 extra || ( isBorderBox ? "border" : "content" ),
10679 // dump('ret', ret, val);
10683 function getLineHeight(elm) {
10684 elm = angular.element(elm)[0];
10685 var parent = elm.parentElement;
10688 parent = document.getElementsByTagName('body')[0];
10691 return parseInt( getStyles(parent).fontSize ) || parseInt( getStyles(elm).fontSize ) || 16;
10694 var uid = ['0', '0', '0', '0'];
10695 var uidPrefix = 'uiGrid-';
10699 * @name ui.grid.service:GridUtil
10701 * @description Grid utility functions
10703 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$interval', '$injector', '$q', '$interpolate', 'uiGridConstants',
10704 function ($log, $window, $document, $http, $templateCache, $timeout, $interval, $injector, $q, $interpolate, uiGridConstants) {
10707 augmentWidthOrHeight: augmentWidthOrHeight,
10709 getStyles: getStyles,
10713 * @name createBoundedWrapper
10714 * @methodOf ui.grid.service:GridUtil
10716 * @param {object} Object to bind 'this' to
10717 * @param {method} Method to bind
10718 * @returns {Function} The wrapper that performs the binding
10721 * Binds given method to given object.
10723 * By means of a wrapper, ensures that ``method`` is always bound to
10724 * ``object`` regardless of its calling environment.
10725 * Iow, inside ``method``, ``this`` always points to ``object``.
10727 * See http://alistapart.com/article/getoutbindingsituations
10730 createBoundedWrapper: function(object, method) {
10731 return function() {
10732 return method.apply(object, arguments);
10739 * @name readableColumnName
10740 * @methodOf ui.grid.service:GridUtil
10742 * @param {string} columnName Column name as a string
10743 * @returns {string} Column name appropriately capitalized and split apart
10746 <example module="app">
10747 <file name="app.js">
10748 var app = angular.module('app', ['ui.grid']);
10750 app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
10751 $scope.name = 'firstName';
10752 $scope.columnName = function(name) {
10753 return gridUtil.readableColumnName(name);
10757 <file name="index.html">
10758 <div ng-controller="MainCtrl">
10759 <strong>Column name:</strong> <input ng-model="name" />
10761 <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
10766 readableColumnName: function (columnName) {
10767 // Convert underscores to spaces
10768 if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
10770 if (typeof(columnName) !== 'string') {
10771 columnName = String(columnName);
10774 return columnName.replace(/_+/g, ' ')
10775 // Replace a completely all-capsed word with a first-letter-capitalized version
10776 .replace(/^[A-Z]+$/, function (match) {
10777 return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
10779 // Capitalize the first letter of words
10780 .replace(/([\w\u00C0-\u017F]+)/g, function (match) {
10781 return angular.uppercase(match.charAt(0)) + match.slice(1);
10783 // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
10784 // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
10785 // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
10786 .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
10791 * @name getColumnsFromData
10792 * @methodOf ui.grid.service:GridUtil
10793 * @description Return a list of column names, given a data set
10795 * @param {string} data Data array for grid
10796 * @returns {Object} Column definitions with field accessor and column name
10801 { firstName: 'Bob', lastName: 'Jones' },
10802 { firstName: 'Frank', lastName: 'Smith' }
10805 var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
10809 field: 'firstName',
10819 getColumnsFromData: function (data, excludeProperties) {
10820 var columnDefs = [];
10822 if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
10823 if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
10825 var item = data[0];
10827 angular.forEach(item,function (prop, propName) {
10828 if ( excludeProperties.indexOf(propName) === -1){
10841 * @methodOf ui.grid.service:GridUtil
10842 * @description Return a unique ID string
10844 * @returns {string} Unique string
10848 var id = GridUtil.newId();
10853 newId: (function() {
10854 var seedId = new Date().getTime();
10855 return function() {
10856 return seedId += 1;
10863 * @name getTemplate
10864 * @methodOf ui.grid.service:GridUtil
10865 * @description Get's template from cache / element / url
10867 * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
10868 * an jQuery/Angualr element, or a promise that returns the template contents to use.
10869 * @returns {object} a promise resolving to template contents
10873 GridUtil.getTemplate(url).then(function (contents) {
10878 getTemplate: function (template) {
10879 // Try to fetch the template out of the templateCache
10880 if ($templateCache.get(template)) {
10881 return s.postProcessTemplate($templateCache.get(template));
10884 // See if the template is itself a promise
10885 if (angular.isFunction(template.then)) {
10886 return template.then(s.postProcessTemplate);
10889 // If the template is an element, return the element
10891 if (angular.element(template).length > 0) {
10892 return $q.when(template).then(s.postProcessTemplate);
10896 //do nothing; not valid html
10899 s.logDebug('fetching url', template);
10901 // Default to trying to fetch the template as a url with $http
10902 return $http({ method: 'GET', url: template})
10904 function (result) {
10905 var templateHtml = result.data.trim();
10906 //put in templateCache for next call
10907 $templateCache.put(template, templateHtml);
10908 return templateHtml;
10911 throw new Error("Could not get template " + template + ": " + err);
10914 .then(s.postProcessTemplate);
10918 postProcessTemplate: function (template) {
10919 var startSym = $interpolate.startSymbol(),
10920 endSym = $interpolate.endSymbol();
10922 // If either of the interpolation symbols have been changed, we need to alter this template
10923 if (startSym !== '{{' || endSym !== '}}') {
10924 template = template.replace(/\{\{/g, startSym);
10925 template = template.replace(/\}\}/g, endSym);
10928 return $q.when(template);
10934 * @methodOf ui.grid.service:GridUtil
10935 * @description guesses the type of an argument
10937 * @param {string/number/bool/object} item variable to examine
10938 * @returns {string} one of the following
10945 guessType : function (item) {
10946 var itemType = typeof(item);
10948 // Check for numbers and booleans
10949 switch (itemType) {
10955 if (angular.isDate(item)) {
10965 * @name elementWidth
10966 * @methodOf ui.grid.service:GridUtil
10968 * @param {element} element DOM element
10969 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10971 * @returns {number} Element width in pixels, accounting for any borders, etc.
10973 elementWidth: function (elem) {
10979 * @name elementHeight
10980 * @methodOf ui.grid.service:GridUtil
10982 * @param {element} element DOM element
10983 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
10985 * @returns {number} Element height in pixels, accounting for any borders, etc.
10987 elementHeight: function (elem) {
10991 // Thanks to http://stackoverflow.com/a/13382873/888165
10992 getScrollbarWidth: function() {
10993 var outer = document.createElement("div");
10994 outer.style.visibility = "hidden";
10995 outer.style.width = "100px";
10996 outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
10998 document.body.appendChild(outer);
11000 var widthNoScroll = outer.offsetWidth;
11001 // force scrollbars
11002 outer.style.overflow = "scroll";
11005 var inner = document.createElement("div");
11006 inner.style.width = "100%";
11007 outer.appendChild(inner);
11009 var widthWithScroll = inner.offsetWidth;
11012 outer.parentNode.removeChild(outer);
11014 return widthNoScroll - widthWithScroll;
11017 swap: function( elem, options, callback, args ) {
11021 // Remember the old values, and insert the new ones
11022 for ( name in options ) {
11023 old[ name ] = elem.style[ name ];
11024 elem.style[ name ] = options[ name ];
11027 ret = callback.apply( elem, args || [] );
11029 // Revert the old values
11030 for ( name in options ) {
11031 elem.style[ name ] = old[ name ];
11037 fakeElement: function( elem, options, callback, args ) {
11039 newElement = angular.element(elem).clone()[0];
11041 for ( name in options ) {
11042 newElement.style[ name ] = options[ name ];
11045 angular.element(document.body).append(newElement);
11047 ret = callback.call( newElement, newElement );
11049 angular.element(newElement).remove();
11056 * @name normalizeWheelEvent
11057 * @methodOf ui.grid.service:GridUtil
11059 * @param {event} event A mouse wheel event
11061 * @returns {event} A normalized event
11064 * Given an event from this list:
11066 * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
11069 * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
11071 normalizeWheelEvent: function (event) {
11072 // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
11073 // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
11074 var lowestDelta, lowestDeltaXY;
11076 var orgEvent = event || window.event,
11077 args = [].slice.call(arguments, 1),
11085 // event = $.event.fix(orgEvent);
11086 // event.type = 'mousewheel';
11088 // NOTE: jQuery masks the event and stores it in the event as originalEvent
11089 if (orgEvent.originalEvent) {
11090 orgEvent = orgEvent.originalEvent;
11093 // Old school scrollwheel delta
11094 if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
11095 if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
11097 // At a minimum, setup the deltaY to be delta
11100 // Firefox < 17 related to DOMMouseScroll event
11101 if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
11103 deltaX = delta * -1;
11106 // New school wheel delta (wheel event)
11107 if ( orgEvent.deltaY ) {
11108 deltaY = orgEvent.deltaY * -1;
11111 if ( orgEvent.deltaX ) {
11112 deltaX = orgEvent.deltaX;
11113 delta = deltaX * -1;
11117 if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
11118 if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
11120 // Look for lowest delta to normalize the delta values
11121 absDelta = Math.abs(delta);
11122 if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
11123 absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
11124 if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
11126 // Get a whole value for the deltas
11127 fn = delta > 0 ? 'floor' : 'ceil';
11128 delta = Math[fn](delta / lowestDelta);
11129 deltaX = Math[fn](deltaX / lowestDeltaXY);
11130 deltaY = Math[fn](deltaY / lowestDeltaXY);
11139 // Stolen from Modernizr
11140 // TODO: make this, and everythign that flows from it, robust
11141 //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
11142 isTouchEnabled: function() {
11145 if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
11152 isNullOrUndefined: function(obj) {
11153 if (obj === undefined || obj === null) {
11159 endsWith: function(str, suffix) {
11160 if (!str || !suffix || typeof str !== "string") {
11163 return str.indexOf(suffix, str.length - suffix.length) !== -1;
11166 arrayContainsObjectWithProperty: function(array, propertyName, propertyValue) {
11168 angular.forEach(array, function (object) {
11169 if (object[propertyName] === propertyValue) {
11176 //// Shim requestAnimationFrame
11177 //requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
11178 // $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
11180 // return $timeout(fn, 10, false);
11183 numericAndNullSort: function (a, b) {
11184 if (a === null) { return 1; }
11185 if (b === null) { return -1; }
11186 if (a === null && b === null) { return 0; }
11190 // Disable ngAnimate animations on an element
11191 disableAnimations: function (element) {
11194 $animate = $injector.get('$animate');
11195 // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
11196 if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
11197 $animate.enabled(element, false);
11199 $animate.enabled(false, element);
11205 enableAnimations: function (element) {
11208 $animate = $injector.get('$animate');
11209 // See: http://brianhann.com/angular-1-4-breaking-changes-to-be-aware-of/#animate
11210 if (angular.version.major > 1 || (angular.version.major === 1 && angular.version.minor >= 4)) {
11211 $animate.enabled(element, true);
11213 $animate.enabled(true, element);
11220 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11221 nextUid: function nextUid() {
11222 var index = uid.length;
11227 digit = uid[index].charCodeAt(0);
11228 if (digit === 57 /*'9'*/) {
11230 return uidPrefix + uid.join('');
11232 if (digit === 90 /*'Z'*/) {
11235 uid[index] = String.fromCharCode(digit + 1);
11236 return uidPrefix + uid.join('');
11241 return uidPrefix + uid.join('');
11244 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
11245 hashKey: function hashKey(obj) {
11246 var objType = typeof obj,
11249 if (objType === 'object' && obj !== null) {
11250 if (typeof (key = obj.$$hashKey) === 'function') {
11251 // must invoke on object to keep the right this
11252 key = obj.$$hashKey();
11254 else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
11255 key = obj.$$hashKey;
11257 else if (key === undefined) {
11258 key = obj.$$hashKey = s.nextUid();
11265 return objType + ':' + key;
11268 resetUids: function () {
11269 uid = ['0', '0', '0'];
11274 * @methodOf ui.grid.service:GridUtil
11276 * @description wraps the $log method, allowing us to choose different
11277 * treatment within ui-grid if we so desired. At present we only log
11278 * error messages if uiGridConstants.LOG_ERROR_MESSAGES is set to true
11279 * @param {string} logMessage message to be logged to the console
11282 logError: function( logMessage ){
11283 if ( uiGridConstants.LOG_ERROR_MESSAGES ){
11284 $log.error( logMessage );
11290 * @methodOf ui.grid.service:GridUtil
11292 * @description wraps the $log method, allowing us to choose different
11293 * treatment within ui-grid if we so desired. At present we only log
11294 * warning messages if uiGridConstants.LOG_WARN_MESSAGES is set to true
11295 * @param {string} logMessage message to be logged to the console
11298 logWarn: function( logMessage ){
11299 if ( uiGridConstants.LOG_WARN_MESSAGES ){
11300 $log.warn( logMessage );
11306 * @methodOf ui.grid.service:GridUtil
11308 * @description wraps the $log method, allowing us to choose different
11309 * treatment within ui-grid if we so desired. At present we only log
11310 * debug messages if uiGridConstants.LOG_DEBUG_MESSAGES is set to true
11313 logDebug: function() {
11314 if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
11315 $log.debug.apply($log, arguments);
11324 * @propertyOf ui.grid.service:GridUtil
11325 * @description Provies a set of methods to set the document focus inside the grid.
11326 * See {@link ui.grid.service:GridUtil.focus} for more information.
11331 * @name ui.grid.service:GridUtil.focus
11332 * @description Provies a set of methods to set the document focus inside the grid.
11333 * Timeouts are utilized to ensure that the focus is invoked after any other event has been triggered.
11334 * e.g. click events that need to run before the focus or
11335 * inputs elements that are in a disabled state but are enabled when those events
11340 //http://stackoverflow.com/questions/25596399/set-element-focus-in-angular-way
11343 * @methodOf ui.grid.service:GridUtil.focus
11345 * @description Sets the focus of the document to the given id value.
11346 * If provided with the grid object it will automatically append the grid id.
11347 * This is done to encourage unique dom id's as it allows for multiple grids on a
11349 * @param {String} id the id of the dom element to set the focus on
11350 * @param {Object=} Grid the grid object for this grid instance. See: {@link ui.grid.class:Grid}
11351 * @param {Number} Grid.id the unique id for this grid. Already set on an initialized grid object.
11352 * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11353 * then the promise will fail with the `'canceled'` reason.
11355 byId: function (id, Grid) {
11356 this._purgeQueue();
11357 var promise = $timeout(function() {
11358 var elementID = (Grid && Grid.id ? Grid.id + '-' : '') + id;
11359 var element = $window.document.getElementById(elementID);
11363 s.logWarn('[focus.byId] Element id ' + elementID + ' was not found.');
11366 this.queue.push(promise);
11372 * @methodOf ui.grid.service:GridUtil.focus
11374 * @description Sets the focus of the document to the given dom element.
11375 * @param {(element|angular.element)} element the DOM element to set the focus on
11376 * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11377 * then the promise will fail with the `'canceled'` reason.
11379 byElement: function(element){
11380 if (!angular.isElement(element)){
11381 s.logWarn("Trying to focus on an element that isn\'t an element.");
11382 return $q.reject('not-element');
11384 element = angular.element(element);
11385 this._purgeQueue();
11386 var promise = $timeout(function(){
11388 element[0].focus();
11391 this.queue.push(promise);
11396 * @methodOf ui.grid.service:GridUtil.focus
11398 * @description Sets the focus of the document to the given dom element.
11399 * @param {(element|angular.element)} parentElement the parent/ancestor of the dom element that you are selecting using the query selector
11400 * @param {String} querySelector finds the dom element using the {@link http://www.w3schools.com/jsref/met_document_queryselector.asp querySelector}
11401 * @param {boolean} [aSync=false] If true then the selector will be querried inside of a timeout. Otherwise the selector will be querried imidately
11402 * then the focus will be called.
11403 * @returns {Promise} The `$timeout` promise that will be resolved once focus is set. If another focus is requested before this request is evaluated.
11404 * then the promise will fail with the `'canceled'` reason.
11406 bySelector: function(parentElement, querySelector, aSync){
11408 if (!angular.isElement(parentElement)){
11409 throw new Error("The parent element is not an element.");
11411 // Ensure that this is an angular element.
11412 // It is fine if this is already an angular element.
11413 parentElement = angular.element(parentElement);
11414 var focusBySelector = function(){
11415 var element = parentElement[0].querySelector(querySelector);
11416 return self.byElement(element);
11418 this._purgeQueue();
11419 if (aSync){ //Do this asynchronysly
11420 var promise = $timeout(focusBySelector);
11421 this.queue.push($timeout(focusBySelector));
11424 return focusBySelector();
11427 _purgeQueue: function(){
11428 this.queue.forEach(function(element){
11429 $timeout.cancel(element);
11436 ['width', 'height'].forEach(function (name) {
11437 var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
11438 s['element' + capsName] = function (elem, extra) {
11440 if (e && typeof(e.length) !== 'undefined' && e.length) {
11444 if (e && e !== null) {
11445 var styles = getStyles(e);
11446 return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
11447 s.swap(e, cssShow, function() {
11448 return getWidthOrHeight(e, name, extra );
11450 getWidthOrHeight( e, name, extra );
11457 s['outerElement' + capsName] = function (elem, margin) {
11458 return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
11462 // http://stackoverflow.com/a/24107550/888165
11463 s.closestElm = function closestElm(el, selector) {
11464 if (typeof(el.length) !== 'undefined' && el.length) {
11470 // find vendor prefix
11471 ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
11472 if (typeof document.body[fn] === 'function') {
11479 // traverse parents
11481 while (el !== null) {
11482 parent = el.parentElement;
11483 if (parent !== null && parent[matchesFn](selector)) {
11492 s.type = function (obj) {
11493 var text = Function.prototype.toString.call(obj.constructor);
11494 return text.match(/function (.*?)\(/)[1];
11497 s.getBorderSize = function getBorderSize(elem, borderType) {
11498 if (typeof(elem.length) !== 'undefined' && elem.length) {
11502 var styles = getStyles(elem);
11504 // If a specific border is supplied, like 'top', read the 'borderTop' style property
11506 borderType = 'border' + borderType.charAt(0).toUpperCase() + borderType.slice(1);
11509 borderType = 'border';
11512 borderType += 'Width';
11514 var val = parseInt(styles[borderType], 10);
11524 // http://stackoverflow.com/a/22948274/888165
11525 // TODO: Opera? Mobile?
11526 s.detectBrowser = function detectBrowser() {
11527 var userAgent = $window.navigator.userAgent;
11529 var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
11531 for (var key in browsers) {
11532 if (browsers[key].test(userAgent)) {
11540 // Borrowed from https://github.com/othree/jquery.rtl-scroll-type
11541 // Determine the scroll "type" this browser is using for RTL
11542 s.rtlScrollType = function rtlScrollType() {
11543 if (rtlScrollType.type) {
11544 return rtlScrollType.type;
11547 var definer = angular.element('<div dir="rtl" style="font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll">A</div>')[0],
11550 document.body.appendChild(definer);
11552 if (definer.scrollLeft > 0) {
11556 definer.scrollLeft = 1;
11557 if (definer.scrollLeft === 0) {
11562 angular.element(definer).remove();
11563 rtlScrollType.type = type;
11570 * @name normalizeScrollLeft
11571 * @methodOf ui.grid.service:GridUtil
11573 * @param {element} element The element to get the `scrollLeft` from.
11574 * @param {grid} grid - grid used to normalize (uses the rtl property)
11576 * @returns {number} A normalized scrollLeft value for the current browser.
11579 * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
11581 s.normalizeScrollLeft = function normalizeScrollLeft(element, grid) {
11582 if (typeof(element.length) !== 'undefined' && element.length) {
11583 element = element[0];
11586 var scrollLeft = element.scrollLeft;
11588 if (grid.isRTL()) {
11589 switch (s.rtlScrollType()) {
11591 return element.scrollWidth - scrollLeft - element.clientWidth;
11593 return Math.abs(scrollLeft);
11604 * @name denormalizeScrollLeft
11605 * @methodOf ui.grid.service:GridUtil
11607 * @param {element} element The element to normalize the `scrollLeft` value for
11608 * @param {number} scrollLeft The `scrollLeft` value to denormalize.
11609 * @param {grid} grid The grid that owns the scroll event.
11611 * @returns {number} A normalized scrollLeft value for the current browser.
11614 * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
11616 s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft, grid) {
11617 if (typeof(element.length) !== 'undefined' && element.length) {
11618 element = element[0];
11621 if (grid.isRTL()) {
11622 switch (s.rtlScrollType()) {
11624 // Get the max scroll for the element
11625 var maxScrollLeft = element.scrollWidth - element.clientWidth;
11627 // Subtract the current scroll amount from the max scroll
11628 return maxScrollLeft - scrollLeft;
11630 return scrollLeft * -1;
11642 * @methodOf ui.grid.service:GridUtil
11644 * @param {string} path Path to evaluate
11646 * @returns {string} A path that is normalized.
11649 * Takes a field path and converts it to bracket notation to allow for special characters in path
11652 * gridUtil.preEval('property') == 'property'
11653 * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
11656 s.preEval = function (path) {
11657 var m = uiGridConstants.BRACKET_REGEXP.exec(path);
11659 return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
11661 path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
11662 var parts = path.split(uiGridConstants.DOT_REGEXP);
11663 var preparsed = [parts.shift()]; // first item must be var notation, thus skip
11664 angular.forEach(parts, function (part) {
11665 preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
11667 return preparsed.join('[\'');
11674 * @methodOf ui.grid.service:GridUtil
11676 * @param {function} func function to debounce
11677 * @param {number} wait milliseconds to delay
11678 * @param {boolean} immediate execute before delay
11680 * @returns {function} A function that can be executed as debounced function
11683 * Copied from https://github.com/shahata/angular-debounce
11684 * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
11687 * var debouncedFunc = gridUtil.debounce(function(){alert('debounced');}, 500);
11693 s.debounce = function (func, wait, immediate) {
11694 var timeout, args, context, result;
11695 function debounce() {
11696 /* jshint validthis:true */
11699 var later = function () {
11702 result = func.apply(context, args);
11705 var callNow = immediate && !timeout;
11707 $timeout.cancel(timeout);
11709 timeout = $timeout(later, wait, false);
11711 result = func.apply(context, args);
11715 debounce.cancel = function () {
11716 $timeout.cancel(timeout);
11725 * @methodOf ui.grid.service:GridUtil
11727 * @param {function} func function to throttle
11728 * @param {number} wait milliseconds to delay after first trigger
11729 * @param {Object} params to use in throttle.
11731 * @returns {function} A function that can be executed as throttled function
11734 * Adapted from debounce function (above)
11735 * Potential keys for Params Object are:
11736 * trailing (bool) - whether to trigger after throttle time ends if called multiple times
11737 * Updated to use $interval rather than $timeout, as protractor (e2e tests) is able to work with $interval,
11738 * but not with $timeout
11740 * Note that when using throttle, you need to use throttle to create a new function upfront, then use the function
11741 * return from that call each time you need to call throttle. If you call throttle itself repeatedly, the lastCall
11742 * variable will get overwritten and the throttling won't work
11746 * var throttledFunc = gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
11747 * throttledFunc(); //=> logs throttled
11748 * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
11749 * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
11752 s.throttle = function(func, wait, options){
11753 options = options || {};
11754 var lastCall = 0, queued = null, context, args;
11756 function runFunc(endDate){
11757 lastCall = +new Date();
11758 func.apply(context, args);
11759 $interval(function(){queued = null; }, 0, 1, false);
11763 /* jshint validthis:true */
11766 if (queued === null){
11767 var sinceLast = +new Date() - lastCall;
11768 if (sinceLast > wait){
11771 else if (options.trailing){
11772 queued = $interval(runFunc, wait - sinceLast, 1, false);
11782 s.addOff = function (eventName) {
11783 s.off[eventName] = function (elm, fn) {
11784 var idx = s._events[eventName].indexOf(fn);
11786 s._events[eventName].removeAt(idx);
11791 var mouseWheeltoBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
11792 nullLowestDeltaTimeout,
11795 s.on.mousewheel = function (elm, fn) {
11796 if (!elm || !fn) { return; }
11798 var $elm = angular.element(elm);
11800 // Store the line height and page height for this particular element
11801 $elm.data('mousewheel-line-height', getLineHeight($elm));
11802 $elm.data('mousewheel-page-height', s.elementHeight($elm));
11803 if (!$elm.data('mousewheel-callbacks')) { $elm.data('mousewheel-callbacks', {}); }
11805 var cbs = $elm.data('mousewheel-callbacks');
11806 cbs[fn] = (Function.prototype.bind || bindPolyfill).call(mousewheelHandler, $elm[0], fn);
11808 // Bind all the mousew heel events
11809 for ( var i = mouseWheeltoBind.length; i; ) {
11810 $elm.on(mouseWheeltoBind[--i], cbs[fn]);
11812 $elm.on('$destroy', function unbindEvents() {
11813 for ( var i = mouseWheeltoBind.length; i; ) {
11814 $elm.off(mouseWheeltoBind[--i], cbs[fn]);
11818 s.off.mousewheel = function (elm, fn) {
11819 var $elm = angular.element(elm);
11821 var cbs = $elm.data('mousewheel-callbacks');
11822 var handler = cbs[fn];
11825 for ( var i = mouseWheeltoBind.length; i; ) {
11826 $elm.off(mouseWheeltoBind[--i], handler);
11832 if (Object.keys(cbs).length === 0) {
11833 $elm.removeData('mousewheel-line-height');
11834 $elm.removeData('mousewheel-page-height');
11835 $elm.removeData('mousewheel-callbacks');
11839 function mousewheelHandler(fn, event) {
11840 var $elm = angular.element(this);
11849 // jQuery masks events
11850 if (event.originalEvent) { event = event.originalEvent; }
11852 if ( 'detail' in event ) { deltaY = event.detail * -1; }
11853 if ( 'wheelDelta' in event ) { deltaY = event.wheelDelta; }
11854 if ( 'wheelDeltaY' in event ) { deltaY = event.wheelDeltaY; }
11855 if ( 'wheelDeltaX' in event ) { deltaX = event.wheelDeltaX * -1; }
11857 // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
11858 if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
11859 deltaX = deltaY * -1;
11863 // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
11864 delta = deltaY === 0 ? deltaX : deltaY;
11866 // New school wheel delta (wheel event)
11867 if ( 'deltaY' in event ) {
11868 deltaY = event.deltaY * -1;
11871 if ( 'deltaX' in event ) {
11872 deltaX = event.deltaX;
11873 if ( deltaY === 0 ) { delta = deltaX * -1; }
11876 // No change actually happened, no reason to go any further
11877 if ( deltaY === 0 && deltaX === 0 ) { return; }
11879 // Need to convert lines and pages to pixels if we aren't already in pixels
11880 // There are three delta modes:
11881 // * deltaMode 0 is by pixels, nothing to do
11882 // * deltaMode 1 is by lines
11883 // * deltaMode 2 is by pages
11884 if ( event.deltaMode === 1 ) {
11885 var lineHeight = $elm.data('mousewheel-line-height');
11886 delta *= lineHeight;
11887 deltaY *= lineHeight;
11888 deltaX *= lineHeight;
11890 else if ( event.deltaMode === 2 ) {
11891 var pageHeight = $elm.data('mousewheel-page-height');
11892 delta *= pageHeight;
11893 deltaY *= pageHeight;
11894 deltaX *= pageHeight;
11897 // Store lowest absolute delta to normalize the delta values
11898 absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
11900 if ( !lowestDelta || absDelta < lowestDelta ) {
11901 lowestDelta = absDelta;
11903 // Adjust older deltas if necessary
11904 if ( shouldAdjustOldDeltas(event, absDelta) ) {
11909 // Get a whole, normalized value for the deltas
11910 delta = Math[ delta >= 1 ? 'floor' : 'ceil' ](delta / lowestDelta);
11911 deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
11912 deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
11914 // Normalise offsetX and offsetY properties
11915 // if ($elm[0].getBoundingClientRect ) {
11916 // var boundingRect = $(elm)[0].getBoundingClientRect();
11917 // offsetX = event.clientX - boundingRect.left;
11918 // offsetY = event.clientY - boundingRect.top;
11921 // event.deltaX = deltaX;
11922 // event.deltaY = deltaY;
11923 // event.deltaFactor = lowestDelta;
11926 originalEvent: event,
11929 deltaFactor: lowestDelta,
11930 preventDefault: function () { event.preventDefault(); },
11931 stopPropagation: function () { event.stopPropagation(); }
11934 // Clearout lowestDelta after sometime to better
11935 // handle multiple device types that give
11936 // a different lowestDelta
11937 // Ex: trackpad = 3 and mouse wheel = 120
11938 if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
11939 nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
11941 fn.call($elm[0], newEvent);
11944 function nullLowestDelta() {
11945 lowestDelta = null;
11948 function shouldAdjustOldDeltas(orgEvent, absDelta) {
11949 // If this is an older event and the delta is divisable by 120,
11950 // then we are assuming that the browser is treating this as an
11951 // older mouse wheel event and that we should divide the deltas
11952 // by 40 to try and get a more usable deltaFactor.
11953 // Side note, this actually impacts the reported scroll distance
11954 // in older browsers and can cause scrolling to be slower than native.
11955 // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
11956 return orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
11962 // Add 'px' to the end of a number string if it doesn't have it already
11963 module.filter('px', function() {
11964 return function(str) {
11965 if (str.match(/^[\d\.]+$/)) {
11977 angular.module('ui.grid').config(['$provide', function($provide) {
11978 $provide.decorator('i18nService', ['$delegate', function($delegate) {
11984 description: 'Přesuňte záhlaví zde pro vytvoření skupiny dle sloupce.'
11987 placeholder: 'Hledat...',
11988 showingItems: 'Zobrazuji položky:',
11989 selectedItems: 'Vybrané položky:',
11990 totalItems: 'Celkem položek:',
11991 size: 'Velikost strany:',
11992 first: 'První strana',
11993 next: 'Další strana',
11994 previous: 'Předchozí strana',
11995 last: 'Poslední strana'
11998 text: 'Vyberte sloupec:'
12001 ascending: 'Seřadit od A-Z',
12002 descending: 'Seřadit od Z-A',
12003 remove: 'Odebrat seřazení'
12006 hide: 'Schovat sloupec'
12009 count: 'celkem řádků: ',
12016 pinLeft: 'Zamknout vlevo',
12017 pinRight: 'Zamknout vpravo',
12021 columns: 'Sloupce:',
12022 importerTitle: 'Importovat soubor',
12023 exporterAllAsCsv: 'Exportovat všechna data do csv',
12024 exporterVisibleAsCsv: 'Exportovat viditelná data do csv',
12025 exporterSelectedAsCsv: 'Exportovat vybraná data do csv',
12026 exporterAllAsPdf: 'Exportovat všechna data do pdf',
12027 exporterVisibleAsPdf: 'Exportovat viditelná data do pdf',
12028 exporterSelectedAsPdf: 'Exportovat vybraná data do pdf',
12029 clearAllFilters: 'Odstranit všechny filtry'
12032 noHeaders: 'Názvy sloupců se nepodařilo získat, obsahuje soubor záhlaví?',
12033 noObjects: 'Data se nepodařilo zpracovat, obsahuje soubor řádky mimo záhlaví?',
12034 invalidCsv: 'Soubor nelze zpracovat, jedná se o CSV?',
12035 invalidJson: 'Soubor nelze zpracovat, je to JSON?',
12036 jsonNotArray: 'Soubor musí obsahovat json. Ukončuji..'
12039 sizes: 'položek na stránku',
12040 totalItems: 'položek'
12044 ungroup: 'Odebrat seskupení',
12045 aggregate_count: 'Agregace: Count',
12046 aggregate_sum: 'Agregace: Sum',
12047 aggregate_max: 'Agregace: Max',
12048 aggregate_min: 'Agregace: Min',
12049 aggregate_avg: 'Agregace: Avg',
12050 aggregate_remove: 'Agregace: Odebrat'
12054 // support varianty of different czech keys.
12055 $delegate.add('cs', lang);
12056 $delegate.add('cz', lang);
12057 $delegate.add('cs-cz', lang);
12058 $delegate.add('cs-CZ', lang);
12065 angular.module('ui.grid').config(['$provide', function($provide) {
12066 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12067 $delegate.add('da', {
12072 description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
12075 placeholder: 'Søg...',
12076 showingItems: 'Viste rækker:',
12077 selectedItems: 'Valgte rækker:',
12078 totalItems: 'Rækker totalt:',
12079 size: 'Side størrelse:',
12080 first: 'Første side',
12081 next: 'Næste side',
12082 previous: 'Forrige side',
12083 last: 'Sidste side'
12086 text: 'Vælg kolonner:'
12089 ascending: 'Sorter stigende',
12090 descending: 'Sorter faldende',
12091 none: 'Sorter ingen',
12092 remove: 'Fjern sortering'
12095 hide: 'Skjul kolonne'
12098 count: 'antal rækker: ',
12105 columns: 'Kolonner:',
12106 importerTitle: 'Importer fil',
12107 exporterAllAsCsv: 'Eksporter alle data som csv',
12108 exporterVisibleAsCsv: 'Eksporter synlige data som csv',
12109 exporterSelectedAsCsv: 'Eksporter markerede data som csv',
12110 exporterAllAsPdf: 'Eksporter alle data som pdf',
12111 exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
12112 exporterSelectedAsPdf: 'Eksporter markerede data som pdf',
12113 clearAllFilters: 'Clear all filters'
12116 noHeaders: 'Column names were unable to be derived, does the file have a header?',
12117 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12118 invalidCsv: 'File was unable to be processed, is it valid CSV?',
12119 invalidJson: 'File was unable to be processed, is it valid Json?',
12120 jsonNotArray: 'Imported json file must contain an array, aborting.'
12124 pageToFirst: 'Gå til første',
12125 pageBack: 'Gå tilbage',
12126 pageSelected: 'Valgte side',
12127 pageForward: 'Gå frem',
12128 pageToLast: 'Gå til sidste'
12130 sizes: 'genstande per side',
12131 totalItems: 'genstande',
12142 angular.module('ui.grid').config(['$provide', function ($provide) {
12143 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12144 $delegate.add('de', {
12147 defaultFilterLabel: 'Filter für Spalte',
12148 removeFilter: 'Filter löschen',
12149 columnMenuButtonLabel: 'Spaltenmenü'
12151 priority: 'Priorität:',
12152 filterLabel: "Filter für Spalte: "
12158 description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
12161 placeholder: 'Suche...',
12162 showingItems: 'Zeige Einträge:',
12163 selectedItems: 'Ausgewählte Einträge:',
12164 totalItems: 'Einträge gesamt:',
12165 size: 'Einträge pro Seite:',
12166 first: 'Erste Seite',
12167 next: 'Nächste Seite',
12168 previous: 'Vorherige Seite',
12169 last: 'Letzte Seite'
12172 text: 'Spalten auswählen:'
12175 ascending: 'aufsteigend sortieren',
12176 descending: 'absteigend sortieren',
12177 none: 'keine Sortierung',
12178 remove: 'Sortierung zurücksetzen'
12181 hide: 'Spalte ausblenden'
12184 count: 'Zeilen insgesamt: ',
12186 avg: 'Durchschnitt: ',
12191 pinLeft: 'Links anheften',
12192 pinRight: 'Rechts anheften',
12200 buttonLabel: 'Tabellenmenü'
12202 columns: 'Spalten:',
12203 importerTitle: 'Datei importieren',
12204 exporterAllAsCsv: 'Alle Daten als CSV exportieren',
12205 exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
12206 exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
12207 exporterAllAsPdf: 'Alle Daten als PDF exportieren',
12208 exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
12209 exporterSelectedAsPdf: 'markierte Daten als PDF exportieren',
12210 clearAllFilters: 'Alle Filter zurücksetzen'
12213 noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
12214 noObjects: 'Es konnten keine Zeileninformationen gelesen werden, Sind in der Datei außer den Spaltendefinitionen auch Daten enthalten?',
12215 invalidCsv: 'Die Datei konnte nicht eingelesen werden, ist es eine gültige CSV-Datei?',
12216 invalidJson: 'Die Datei konnte nicht eingelesen werden. Enthält sie gültiges JSON?',
12217 jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
12221 pageToFirst: 'Zum Anfang',
12222 pageBack: 'Seite zurück',
12223 pageSelected: 'Ausgwählte Seite',
12224 pageForward: 'Seite vor',
12225 pageToLast: 'Zum Ende'
12227 sizes: 'Einträge pro Seite',
12228 totalItems: 'Einträge',
12233 group: 'Gruppieren',
12234 ungroup: 'Gruppierung aufheben',
12235 aggregate_count: 'Agg: Anzahl',
12236 aggregate_sum: 'Agg: Summe',
12237 aggregate_max: 'Agg: Maximum',
12238 aggregate_min: 'Agg: Minimum',
12239 aggregate_avg: 'Agg: Mittelwert',
12240 aggregate_remove: 'Aggregation entfernen'
12249 angular.module('ui.grid').config(['$provide', function($provide) {
12250 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12251 $delegate.add('en', {
12254 defaultFilterLabel: 'Filter for column',
12255 removeFilter: 'Remove Filter',
12256 columnMenuButtonLabel: 'Column Menu'
12258 priority: 'Priority:',
12259 filterLabel: "Filter for column: "
12265 description: 'Drag a column header here and drop it to group by that column.'
12268 placeholder: 'Search...',
12269 showingItems: 'Showing Items:',
12270 selectedItems: 'Selected Items:',
12271 totalItems: 'Total Items:',
12272 size: 'Page Size:',
12273 first: 'First Page',
12275 previous: 'Previous Page',
12279 text: 'Choose Columns:'
12282 ascending: 'Sort Ascending',
12283 descending: 'Sort Descending',
12285 remove: 'Remove Sort'
12288 hide: 'Hide Column'
12291 count: 'total rows: ',
12298 pinLeft: 'Pin Left',
12299 pinRight: 'Pin Right',
12307 buttonLabel: 'Grid Menu'
12309 columns: 'Columns:',
12310 importerTitle: 'Import file',
12311 exporterAllAsCsv: 'Export all data as csv',
12312 exporterVisibleAsCsv: 'Export visible data as csv',
12313 exporterSelectedAsCsv: 'Export selected data as csv',
12314 exporterAllAsPdf: 'Export all data as pdf',
12315 exporterVisibleAsPdf: 'Export visible data as pdf',
12316 exporterSelectedAsPdf: 'Export selected data as pdf',
12317 clearAllFilters: 'Clear all filters'
12320 noHeaders: 'Column names were unable to be derived, does the file have a header?',
12321 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12322 invalidCsv: 'File was unable to be processed, is it valid CSV?',
12323 invalidJson: 'File was unable to be processed, is it valid Json?',
12324 jsonNotArray: 'Imported json file must contain an array, aborting.'
12328 pageToFirst: 'Page to first',
12329 pageBack: 'Page back',
12330 pageSelected: 'Selected page',
12331 pageForward: 'Page forward',
12332 pageToLast: 'Page to last'
12334 sizes: 'items per page',
12335 totalItems: 'items',
12336 through: 'through',
12341 ungroup: 'Ungroup',
12342 aggregate_count: 'Agg: Count',
12343 aggregate_sum: 'Agg: Sum',
12344 aggregate_max: 'Agg: Max',
12345 aggregate_min: 'Agg: Min',
12346 aggregate_avg: 'Agg: Avg',
12347 aggregate_remove: 'Agg: Remove'
12351 minLength: 'Value should be at least THRESHOLD characters long.',
12352 maxLength: 'Value should be at most THRESHOLD characters long.',
12353 required: 'A value is needed.'
12362 angular.module('ui.grid').config(['$provide', function($provide) {
12363 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12364 $delegate.add('es', {
12369 description: 'Arrastre un encabezado de columna aquí y suéltelo para agrupar por esa columna.'
12372 placeholder: 'Buscar...',
12373 showingItems: 'Artículos Mostrados:',
12374 selectedItems: 'Artículos Seleccionados:',
12375 totalItems: 'Artículos Totales:',
12376 size: 'Tamaño de Página:',
12377 first: 'Primera Página',
12378 next: 'Página Siguiente',
12379 previous: 'Página Anterior',
12380 last: 'Última Página'
12383 text: 'Elegir columnas:'
12386 ascending: 'Orden Ascendente',
12387 descending: 'Orden Descendente',
12388 remove: 'Sin Ordenar'
12391 hide: 'Ocultar la columna'
12394 count: 'filas totales: ',
12401 pinLeft: 'Fijar a la Izquierda',
12402 pinRight: 'Fijar a la Derecha',
12403 unpin: 'Quitar Fijación'
12406 columns: 'Columnas:',
12407 importerTitle: 'Importar archivo',
12408 exporterAllAsCsv: 'Exportar todo como csv',
12409 exporterVisibleAsCsv: 'Exportar vista como csv',
12410 exporterSelectedAsCsv: 'Exportar selección como csv',
12411 exporterAllAsPdf: 'Exportar todo como pdf',
12412 exporterVisibleAsPdf: 'Exportar vista como pdf',
12413 exporterSelectedAsPdf: 'Exportar selección como pdf',
12414 clearAllFilters: 'Limpiar todos los filtros'
12417 noHeaders: 'No fue posible derivar los nombres de las columnas, ¿tiene encabezados el archivo?',
12418 noObjects: 'No fue posible obtener registros, ¿contiene datos el archivo, aparte de los encabezados?',
12419 invalidCsv: 'No fue posible procesar el archivo, ¿es un CSV válido?',
12420 invalidJson: 'No fue posible procesar el archivo, ¿es un Json válido?',
12421 jsonNotArray: 'El archivo json importado debe contener un array, abortando.'
12424 sizes: 'registros por página',
12425 totalItems: 'registros',
12430 ungroup: 'Desagrupar',
12431 aggregate_count: 'Agr: Cont',
12432 aggregate_sum: 'Agr: Sum',
12433 aggregate_max: 'Agr: Máx',
12434 aggregate_min: 'Agr: Min',
12435 aggregate_avg: 'Agr: Prom',
12436 aggregate_remove: 'Agr: Quitar'
12445 * Translated by: R. Salarmehr
12447 * Using Vajje.com online dictionary.
12450 angular.module('ui.grid').config(['$provide', function ($provide) {
12451 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12452 $delegate.add('fa', {
12457 description: 'عنوان یک ستون را بگیر و به گروهی از آن ستون رها کن.'
12460 placeholder: 'جستجو...',
12461 showingItems: 'نمایش اقلام:',
12462 selectedItems: 'قلم\u200cهای انتخاب شده:',
12463 totalItems: 'مجموع اقلام:',
12464 size: 'اندازه\u200cی صفحه:',
12465 first: 'اولین صفحه',
12466 next: 'صفحه\u200cی\u200cبعدی',
12467 previous: 'صفحه\u200cی\u200c قبلی',
12471 text: 'ستون\u200cهای انتخابی:'
12474 ascending: 'ترتیب صعودی',
12475 descending: 'ترتیب نزولی',
12476 remove: 'حذف مرتب کردن'
12479 hide: 'پنهان\u200cکردن ستون'
12489 pinLeft: 'پین کردن سمت چپ',
12490 pinRight: 'پین کردن سمت راست',
12494 columns: 'ستون\u200cها:',
12495 importerTitle: 'وارد کردن فایل',
12496 exporterAllAsCsv: 'خروجی تمام داده\u200cها در فایل csv',
12497 exporterVisibleAsCsv: 'خروجی داده\u200cهای قابل مشاهده در فایل csv',
12498 exporterSelectedAsCsv: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل csv',
12499 exporterAllAsPdf: 'خروجی تمام داده\u200cها در فایل pdf',
12500 exporterVisibleAsPdf: 'خروجی داده\u200cهای قابل مشاهده در فایل pdf',
12501 exporterSelectedAsPdf: 'خروجی داده\u200cهای انتخاب\u200cشده در فایل pdf',
12502 clearAllFilters: 'پاک کردن تمام فیلتر'
12505 noHeaders: 'نام ستون قابل استخراج نیست. آیا فایل عنوان دارد؟',
12506 noObjects: 'اشیا قابل استخراج نیستند. آیا به جز عنوان\u200cها در فایل داده وجود دارد؟',
12507 invalidCsv: 'فایل قابل پردازش نیست. آیا فرمت csv معتبر است؟',
12508 invalidJson: 'فایل قابل پردازش نیست. آیا فرمت json معتبر است؟',
12509 jsonNotArray: 'فایل json وارد شده باید حاوی آرایه باشد. عملیات ساقط شد.'
12512 sizes: 'اقلام در هر صفحه',
12513 totalItems: 'اقلام',
12517 group: 'گروه\u200cبندی',
12518 ungroup: 'حذف گروه\u200cبندی',
12519 aggregate_count: 'Agg: تعداد',
12520 aggregate_sum: 'Agg: جمع',
12521 aggregate_max: 'Agg: بیشینه',
12522 aggregate_min: 'Agg: کمینه',
12523 aggregate_avg: 'Agg: میانگین',
12524 aggregate_remove: 'Agg: حذف'
12533 angular.module('ui.grid').config(['$provide', function($provide) {
12534 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12535 $delegate.add('fi', {
12540 description: 'Raahaa ja pudota otsikko tähän ryhmittääksesi sarakkeen mukaan.'
12543 placeholder: 'Hae...',
12544 showingItems: 'Näytetään rivejä:',
12545 selectedItems: 'Valitut rivit:',
12546 totalItems: 'Rivejä yht.:',
12548 first: 'Ensimmäinen sivu',
12549 next: 'Seuraava sivu',
12550 previous: 'Edellinen sivu',
12551 last: 'Viimeinen sivu'
12554 text: 'Valitse sarakkeet:'
12557 ascending: 'Järjestä nouseva',
12558 descending: 'Järjestä laskeva',
12559 remove: 'Poista järjestys'
12562 hide: 'Piilota sarake'
12565 count: 'Rivejä yht.: ',
12572 pinLeft: 'Lukitse vasemmalle',
12573 pinRight: 'Lukitse oikealle',
12574 unpin: 'Poista lukitus'
12577 columns: 'Sarakkeet:',
12578 importerTitle: 'Tuo tiedosto',
12579 exporterAllAsCsv: 'Vie tiedot csv-muodossa',
12580 exporterVisibleAsCsv: 'Vie näkyvä tieto csv-muodossa',
12581 exporterSelectedAsCsv: 'Vie valittu tieto csv-muodossa',
12582 exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
12583 exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
12584 exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa',
12585 clearAllFilters: 'Puhdista kaikki suodattimet'
12588 noHeaders: 'Sarakkeen nimiä ei voitu päätellä, onko tiedostossa otsikkoriviä?',
12589 noObjects: 'Tietoja ei voitu lukea, onko tiedostossa muuta kuin otsikkot?',
12590 invalidCsv: 'Tiedostoa ei voitu käsitellä, oliko se CSV-muodossa?',
12591 invalidJson: 'Tiedostoa ei voitu käsitellä, oliko se JSON-muodossa?',
12592 jsonNotArray: 'Tiedosto ei sisältänyt taulukkoa, lopetetaan.'
12601 angular.module('ui.grid').config(['$provide', function($provide) {
12602 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12603 $delegate.add('fr', {
12606 defaultFilterLabel: 'Filtre de la colonne',
12607 removeFilter: 'Supprimer le filtre',
12608 columnMenuButtonLabel: 'Menu de la colonne'
12610 priority: 'Priorité:',
12611 filterLabel: "Filtre de la colonne: "
12617 description: 'Faites glisser une en-tête de colonne ici pour créer un groupe de colonnes.'
12620 placeholder: 'Recherche...',
12621 showingItems: 'Affichage des éléments :',
12622 selectedItems: 'Éléments sélectionnés :',
12623 totalItems: 'Nombre total d\'éléments:',
12624 size: 'Taille de page:',
12625 first: 'Première page',
12626 next: 'Page Suivante',
12627 previous: 'Page précédente',
12628 last: 'Dernière page'
12631 text: 'Choisir des colonnes :'
12634 ascending: 'Trier par ordre croissant',
12635 descending: 'Trier par ordre décroissant',
12637 remove: 'Enlever le tri'
12640 hide: 'Cacher la colonne'
12643 count: 'lignes totales: ',
12650 pinLeft: 'Épingler à gauche',
12651 pinRight: 'Épingler à droite',
12659 buttonLabel: 'Menu du tableau'
12661 columns: 'Colonnes:',
12662 importerTitle: 'Importer un fichier',
12663 exporterAllAsCsv: 'Exporter toutes les données en CSV',
12664 exporterVisibleAsCsv: 'Exporter les données visibles en CSV',
12665 exporterSelectedAsCsv: 'Exporter les données sélectionnées en CSV',
12666 exporterAllAsPdf: 'Exporter toutes les données en PDF',
12667 exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
12668 exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF',
12669 clearAllFilters: 'Nettoyez tous les filtres'
12672 noHeaders: 'Impossible de déterminer le nom des colonnes, le fichier possède-t-il une en-tête ?',
12673 noObjects: 'Aucun objet trouvé, le fichier possède-t-il des données autres que l\'en-tête ?',
12674 invalidCsv: 'Le fichier n\'a pas pu être traité, le CSV est-il valide ?',
12675 invalidJson: 'Le fichier n\'a pas pu être traité, le JSON est-il valide ?',
12676 jsonNotArray: 'Le fichier JSON importé doit contenir un tableau, abandon.'
12680 pageToFirst: 'Aller à la première page',
12681 pageBack: 'Page précédente',
12682 pageSelected: 'Page sélectionnée',
12683 pageForward: 'Page suivante',
12684 pageToLast: 'Aller à la dernière page'
12686 sizes: 'éléments par page',
12687 totalItems: 'éléments',
12693 ungroup: 'Dégrouper',
12694 aggregate_count: 'Agg: Compter',
12695 aggregate_sum: 'Agg: Somme',
12696 aggregate_max: 'Agg: Max',
12697 aggregate_min: 'Agg: Min',
12698 aggregate_avg: 'Agg: Moy',
12699 aggregate_remove: 'Agg: Retirer'
12703 minLength: 'La valeur doit être supérieure ou égale à THRESHOLD caractères.',
12704 maxLength: 'La valeur doit être inférieure ou égale à THRESHOLD caractères.',
12705 required: 'Une valeur est nécéssaire.'
12714 angular.module('ui.grid').config(['$provide', function ($provide) {
12715 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
12716 $delegate.add('he', {
12721 description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
12724 placeholder: 'חפש...',
12725 showingItems: 'מציג:',
12726 selectedItems: 'סה"כ נבחרו:',
12727 totalItems: 'סה"כ רשומות:',
12728 size: 'תוצאות בדף:',
12731 previous: 'דף קודם',
12735 text: 'בחר עמודות:'
12738 ascending: 'סדר עולה',
12739 descending: 'סדר יורד',
12746 count: 'total rows: ',
12753 columns: 'Columns:',
12754 importerTitle: 'Import file',
12755 exporterAllAsCsv: 'Export all data as csv',
12756 exporterVisibleAsCsv: 'Export visible data as csv',
12757 exporterSelectedAsCsv: 'Export selected data as csv',
12758 exporterAllAsPdf: 'Export all data as pdf',
12759 exporterVisibleAsPdf: 'Export visible data as pdf',
12760 exporterSelectedAsPdf: 'Export selected data as pdf',
12761 clearAllFilters: 'Clean all filters'
12764 noHeaders: 'Column names were unable to be derived, does the file have a header?',
12765 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
12766 invalidCsv: 'File was unable to be processed, is it valid CSV?',
12767 invalidJson: 'File was unable to be processed, is it valid Json?',
12768 jsonNotArray: 'Imported json file must contain an array, aborting.'
12777 angular.module('ui.grid').config(['$provide', function($provide) {
12778 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12779 $delegate.add('hy', {
12784 description: 'Ըստ սյան խմբավորելու համար քաշեք և գցեք վերնագիրն այստեղ։'
12787 placeholder: 'Փնտրում...',
12788 showingItems: 'Ցուցադրված տվյալներ՝',
12789 selectedItems: 'Ընտրված:',
12790 totalItems: 'Ընդամենը՝',
12791 size: 'Տողերի քանակը էջում՝',
12792 first: 'Առաջին էջ',
12794 previous: 'Նախորդ էջ',
12798 text: 'Ընտրել սյուները:'
12801 ascending: 'Աճման կարգով',
12802 descending: 'Նվազման կարգով',
12806 hide: 'Թաքցնել սյունը'
12809 count: 'ընդամենը տող՝ ',
12816 pinLeft: 'Կպցնել ձախ կողմում',
12817 pinRight: 'Կպցնել աջ կողմում',
12821 columns: 'Սյուներ:',
12822 importerTitle: 'Ներմուծել ֆայլ',
12823 exporterAllAsCsv: 'Արտահանել ամբողջը CSV',
12824 exporterVisibleAsCsv: 'Արտահանել երևացող տվյալները CSV',
12825 exporterSelectedAsCsv: 'Արտահանել ընտրված տվյալները CSV',
12826 exporterAllAsPdf: 'Արտահանել PDF',
12827 exporterVisibleAsPdf: 'Արտահանել երևացող տվյալները PDF',
12828 exporterSelectedAsPdf: 'Արտահանել ընտրված տվյալները PDF',
12829 clearAllFilters: 'Մաքրել բոլոր ֆիլտրերը'
12832 noHeaders: 'Հնարավոր չեղավ որոշել սյան վերնագրերը։ Արդյո՞ք ֆայլը ունի վերնագրեր։',
12833 noObjects: 'Հնարավոր չեղավ կարդալ տվյալները։ Արդյո՞ք ֆայլում կան տվյալներ։',
12834 invalidCsv: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր CSV է։',
12835 invalidJson: 'Հնարավոր չեղավ մշակել ֆայլը։ Արդյո՞ք այն վավեր Json է։',
12836 jsonNotArray: 'Ներմուծված json ֆայլը պետք է պարունակի զանգված, կասեցվում է։'
12845 angular.module('ui.grid').config(['$provide', function($provide) {
12846 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12847 $delegate.add('it', {
12852 description: 'Trascina un\'intestazione all\'interno del gruppo della colonna.'
12855 placeholder: 'Ricerca...',
12856 showingItems: 'Mostra:',
12857 selectedItems: 'Selezionati:',
12858 totalItems: 'Totali:',
12859 size: 'Tot Pagine:',
12862 previous: 'Precedente',
12866 text: 'Scegli le colonne:'
12870 descending: 'Desc.',
12871 remove: 'Annulla ordinamento'
12877 count: 'righe totali: ',
12884 pinLeft: 'Blocca a sx',
12885 pinRight: 'Blocca a dx',
12886 unpin: 'Blocca in alto'
12889 columns: 'Colonne:',
12890 importerTitle: 'Importa',
12891 exporterAllAsCsv: 'Esporta tutti i dati in CSV',
12892 exporterVisibleAsCsv: 'Esporta i dati visibili in CSV',
12893 exporterSelectedAsCsv: 'Esporta i dati selezionati in CSV',
12894 exporterAllAsPdf: 'Esporta tutti i dati in PDF',
12895 exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
12896 exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF',
12897 clearAllFilters: 'Pulire tutti i filtri'
12900 noHeaders: 'Impossibile reperire i nomi delle colonne, sicuro che siano indicati all\'interno del file?',
12901 noObjects: 'Impossibile reperire gli oggetti, sicuro che siano indicati all\'interno del file?',
12902 invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
12903 invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
12904 jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
12908 pageToFirst: 'Prima',
12909 pageBack: 'Indietro',
12910 pageSelected: 'Pagina selezionata',
12911 pageForward: 'Avanti',
12912 pageToLast: 'Ultima'
12914 sizes: 'elementi per pagina',
12915 totalItems: 'elementi',
12920 group: 'Raggruppa',
12922 aggregate_count: 'Agg: N. Elem.',
12923 aggregate_sum: 'Agg: Somma',
12924 aggregate_max: 'Agg: Massimo',
12925 aggregate_min: 'Agg: Minimo',
12926 aggregate_avg: 'Agg: Media',
12927 aggregate_remove: 'Agg: Rimuovi'
12931 minLength: 'Lunghezza minima pari a THRESHOLD caratteri.',
12932 maxLength: 'Lunghezza massima pari a THRESHOLD caratteri.',
12933 required: 'Necessario inserire un valore.'
12942 angular.module('ui.grid').config(['$provide', function($provide) {
12943 $provide.decorator('i18nService', ['$delegate', function($delegate) {
12944 $delegate.add('ja', {
12949 description: 'ここに列ヘッダをドラッグアンドドロップして、その列でグループ化します。'
12952 placeholder: '検索...',
12953 showingItems: '表示中の項目:',
12954 selectedItems: '選択した項目:',
12955 totalItems: '項目の総数:',
12966 ascending: '昇順に並べ替え',
12967 descending: '降順に並べ替え',
12987 importerTitle: 'ファイルのインポート',
12988 exporterAllAsCsv: 'すべてのデータをCSV形式でエクスポート',
12989 exporterVisibleAsCsv: '表示中のデータをCSV形式でエクスポート',
12990 exporterSelectedAsCsv: '選択したデータをCSV形式でエクスポート',
12991 exporterAllAsPdf: 'すべてのデータをPDF形式でエクスポート',
12992 exporterVisibleAsPdf: '表示中のデータをPDF形式でエクスポート',
12993 exporterSelectedAsPdf: '選択したデータをPDF形式でエクスポート',
12994 clearAllFilters: 'すべてのフィルタを清掃してください'
12997 noHeaders: '列名を取得できません。ファイルにヘッダが含まれていることを確認してください。',
12998 noObjects: 'オブジェクトを取得できません。ファイルにヘッダ以外のデータが含まれていることを確認してください。',
12999 invalidCsv: 'ファイルを処理できません。ファイルが有効なCSV形式であることを確認してください。',
13000 invalidJson: 'ファイルを処理できません。ファイルが有効なJSON形式であることを確認してください。',
13001 jsonNotArray: 'インポートしたJSONファイルには配列が含まれている必要があります。処理を中止します。'
13005 pageToFirst: '最初のページ',
13007 pageSelected: '現在のページ',
13008 pageForward: '次のページ',
13009 pageToLast: '最後のページ'
13023 angular.module('ui.grid').config(['$provide', function($provide) {
13024 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13025 $delegate.add('ko', {
13030 description: '컬럼으로 그룹핑하기 위해서는 컬럼 헤더를 끌어 떨어뜨려 주세요.'
13033 placeholder: '검색...',
13034 showingItems: '항목 보여주기:',
13035 selectedItems: '선택 항목:',
13036 totalItems: '전체 항목:',
13040 previous: '이전 페이지',
13047 ascending: '오름차순 정렬',
13048 descending: '내림차순 정렬',
13068 importerTitle: '파일 가져오기',
13069 exporterAllAsCsv: 'csv로 모든 데이터 내보내기',
13070 exporterVisibleAsCsv: 'csv로 보이는 데이터 내보내기',
13071 exporterSelectedAsCsv: 'csv로 선택된 데이터 내보내기',
13072 exporterAllAsPdf: 'pdf로 모든 데이터 내보내기',
13073 exporterVisibleAsPdf: 'pdf로 보이는 데이터 내보내기',
13074 exporterSelectedAsPdf: 'pdf로 선택 데이터 내보내기',
13075 clearAllFilters: '모든 필터를 청소'
13078 noHeaders: '컬럼명이 지정되어 있지 않습니다. 파일에 헤더가 명시되어 있는지 확인해 주세요.',
13079 noObjects: '데이터가 지정되어 있지 않습니다. 데이터가 파일에 있는지 확인해 주세요.',
13080 invalidCsv: '파일을 처리할 수 없습니다. 올바른 csv인지 확인해 주세요.',
13081 invalidJson: '파일을 처리할 수 없습니다. 올바른 json인지 확인해 주세요.',
13082 jsonNotArray: 'json 파일은 배열을 포함해야 합니다.'
13086 totalItems: '전체 항목'
13095 angular.module('ui.grid').config(['$provide', function($provide) {
13096 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13097 $delegate.add('nl', {
13102 description: 'Sleep hier een kolomnaam heen om op te groeperen.'
13105 placeholder: 'Zoeken...',
13106 showingItems: 'Getoonde items:',
13107 selectedItems: 'Geselecteerde items:',
13108 totalItems: 'Totaal aantal items:',
13109 size: 'Items per pagina:',
13110 first: 'Eerste pagina',
13111 next: 'Volgende pagina',
13112 previous: 'Vorige pagina',
13113 last: 'Laatste pagina'
13116 text: 'Kies kolommen:'
13119 ascending: 'Sorteer oplopend',
13120 descending: 'Sorteer aflopend',
13121 remove: 'Verwijder sortering'
13124 hide: 'Verberg kolom'
13127 count: 'Aantal rijen: ',
13129 avg: 'Gemiddelde: ',
13134 pinLeft: 'Zet links vast',
13135 pinRight: 'Zet rechts vast',
13139 columns: 'Kolommen:',
13140 importerTitle: 'Importeer bestand',
13141 exporterAllAsCsv: 'Exporteer alle data als csv',
13142 exporterVisibleAsCsv: 'Exporteer zichtbare data als csv',
13143 exporterSelectedAsCsv: 'Exporteer geselecteerde data als csv',
13144 exporterAllAsPdf: 'Exporteer alle data als pdf',
13145 exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
13146 exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf',
13147 clearAllFilters: 'Reinig alle filters'
13150 noHeaders: 'Kolomnamen kunnen niet worden afgeleid. Heeft het bestand een header?',
13151 noObjects: 'Objecten kunnen niet worden afgeleid. Bevat het bestand data naast de headers?',
13152 invalidCsv: 'Het bestand kan niet verwerkt worden. Is het een valide csv bestand?',
13153 invalidJson: 'Het bestand kan niet verwerkt worden. Is het valide json?',
13154 jsonNotArray: 'Het json bestand moet een array bevatten. De actie wordt geannuleerd.'
13157 sizes: 'items per pagina',
13158 totalItems: 'items',
13163 ungroup: 'Groepering opheffen',
13164 aggregate_count: 'Agg: Aantal',
13165 aggregate_sum: 'Agg: Som',
13166 aggregate_max: 'Agg: Max',
13167 aggregate_min: 'Agg: Min',
13168 aggregate_avg: 'Agg: Gem',
13169 aggregate_remove: 'Agg: Verwijder'
13178 angular.module('ui.grid').config(['$provide', function($provide) {
13179 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13180 $delegate.add('no', {
13183 defaultFilterLabel: 'Filter for column',
13184 removeFilter: 'Remove Filter',
13185 columnMenuButtonLabel: 'Column Menu'
13187 priority: 'Priority:',
13188 filterLabel: "Filter for column: "
13194 description: 'Drag a column header here and drop it to group by that column.'
13197 placeholder: 'Search...',
13198 showingItems: 'Showing Items:',
13199 selectedItems: 'Selected Items:',
13200 totalItems: 'Total Items:',
13201 size: 'Page Size:',
13202 first: 'First Page',
13204 previous: 'Previous Page',
13208 text: 'Choose Columns:'
13211 ascending: 'Sort Ascending',
13212 descending: 'Sort Descending',
13214 remove: 'Remove Sort'
13217 hide: 'Hide Column'
13220 count: 'total rows: ',
13227 pinLeft: 'Pin Left',
13228 pinRight: 'Pin Right',
13236 buttonLabel: 'Grid Menu'
13238 columns: 'Kolonner:',
13239 importerTitle: 'Importer fil',
13240 exporterAllAsCsv: 'Eksporter alle data som csv',
13241 exporterVisibleAsCsv: 'Eksporter synlige data som csv',
13242 exporterSelectedAsCsv: 'Eksporter utvalgte data som csv',
13243 exporterAllAsPdf: 'Eksporter alle data som pdf',
13244 exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
13245 exporterSelectedAsPdf: 'Eksporter utvalgte data som pdf',
13246 clearAllFilters: 'Clear all filters'
13249 noHeaders: 'Column names were unable to be derived, does the file have a header?',
13250 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13251 invalidCsv: 'File was unable to be processed, is it valid CSV?',
13252 invalidJson: 'File was unable to be processed, is it valid Json?',
13253 jsonNotArray: 'Imported json file must contain an array, aborting.'
13257 pageToFirst: 'Page to first',
13258 pageBack: 'Page back',
13259 pageSelected: 'Selected page',
13260 pageForward: 'Page forward',
13261 pageToLast: 'Page to last'
13263 sizes: 'items per page',
13264 totalItems: 'items',
13265 through: 'through',
13270 ungroup: 'Ungroup',
13271 aggregate_count: 'Agg: Count',
13272 aggregate_sum: 'Agg: Sum',
13273 aggregate_max: 'Agg: Max',
13274 aggregate_min: 'Agg: Min',
13275 aggregate_avg: 'Agg: Avg',
13276 aggregate_remove: 'Agg: Remove'
13285 angular.module('ui.grid').config(['$provide', function($provide) {
13286 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13287 $delegate.add('pl', {
13290 defaultFilterLabel: 'Filtr dla kolumny',
13291 removeFilter: 'Usuń filtr',
13292 columnMenuButtonLabel: 'Menu kolumny'
13294 priority: 'Prioritet:',
13295 filterLabel: "Filtr dla kolumny: "
13301 description: 'Przeciągnij nagłówek kolumny tutaj, aby pogrupować według niej.'
13304 placeholder: 'Szukaj...',
13305 showingItems: 'Widoczne pozycje:',
13306 selectedItems: 'Zaznaczone pozycje:',
13307 totalItems: 'Wszystkich pozycji:',
13308 size: 'Rozmiar strony:',
13309 first: 'Pierwsza strona',
13310 next: 'Następna strona',
13311 previous: 'Poprzednia strona',
13312 last: 'Ostatnia strona'
13315 text: 'Wybierz kolumny:'
13318 ascending: 'Sortuj rosnąco',
13319 descending: 'Sortuj malejąco',
13320 none: 'Brak sortowania',
13321 remove: 'Wyłącz sortowanie'
13324 hide: 'Ukryj kolumne'
13327 count: 'Razem pozycji: ',
13334 pinLeft: 'Przypnij do lewej',
13335 pinRight: 'Przypnij do prawej',
13343 buttonLabel: 'Menu Grida'
13345 columns: 'Kolumny:',
13346 importerTitle: 'Importuj plik',
13347 exporterAllAsCsv: 'Eksportuj wszystkie dane do csv',
13348 exporterVisibleAsCsv: 'Eksportuj widoczne dane do csv',
13349 exporterSelectedAsCsv: 'Eksportuj zaznaczone dane do csv',
13350 exporterAllAsPdf: 'Eksportuj wszystkie dane do pdf',
13351 exporterVisibleAsPdf: 'Eksportuj widoczne dane do pdf',
13352 exporterSelectedAsPdf: 'Eksportuj zaznaczone dane do pdf',
13353 clearAllFilters: 'Wyczyść filtry'
13356 noHeaders: 'Nie udało się wczytać nazw kolumn. Czy plik posiada nagłówek?',
13357 noObjects: 'Nie udalo się wczytać pozycji. Czy plik zawiera dane??',
13358 invalidCsv: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik CSV??',
13359 invalidJson: 'Nie udało się przetworzyć pliku, jest to prawidlowy plik Json?',
13360 jsonNotArray: 'Importowany plik json musi zawierać tablicę, importowanie przerwane.'
13364 pageToFirst: 'Pierwsza strona',
13365 pageBack: 'Poprzednia strona',
13366 pageSelected: 'Wybrana strona',
13367 pageForward: 'Następna strona',
13368 pageToLast: 'Ostatnia strona'
13370 sizes: 'pozycji na stronę',
13371 totalItems: 'pozycji',
13377 ungroup: 'Rozgrupuj',
13378 aggregate_count: 'Zbiorczo: Razem',
13379 aggregate_sum: 'Zbiorczo: Suma',
13380 aggregate_max: 'Zbiorczo: Max',
13381 aggregate_min: 'Zbiorczo: Min',
13382 aggregate_avg: 'Zbiorczo: Średnia',
13383 aggregate_remove: 'Zbiorczo: Usuń'
13387 minLength: 'Wartość powinna składać się z co najmniej THRESHOLD znaków.',
13388 maxLength: 'Wartość powinna składać się z przynajmniej THRESHOLD znaków.',
13389 required: 'Wartość jest wymagana.'
13398 angular.module('ui.grid').config(['$provide', function($provide) {
13399 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13400 $delegate.add('pt-br', {
13403 defaultFilterLabel: 'Filtro por coluna',
13404 removeFilter: 'Remover filtro',
13405 columnMenuButtonLabel: 'Menu coluna'
13407 priority: 'Prioridade:',
13408 filterLabel: "Filtro por coluna: "
13414 description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13417 placeholder: 'Procurar...',
13418 showingItems: 'Mostrando os Itens:',
13419 selectedItems: 'Items Selecionados:',
13420 totalItems: 'Total de Itens:',
13421 size: 'Tamanho da Página:',
13422 first: 'Primeira Página',
13423 next: 'Próxima Página',
13424 previous: 'Página Anterior',
13425 last: 'Última Página'
13428 text: 'Selecione as colunas:'
13431 ascending: 'Ordenar Ascendente',
13432 descending: 'Ordenar Descendente',
13433 none: 'Nenhuma Ordem',
13434 remove: 'Remover Ordenação'
13437 hide: 'Esconder coluna'
13440 count: 'total de linhas: ',
13447 pinLeft: 'Fixar Esquerda',
13448 pinRight: 'Fixar Direita',
13449 unpin: 'Desprender'
13456 buttonLabel: 'Menu Grid'
13458 columns: 'Colunas:',
13459 importerTitle: 'Importar arquivo',
13460 exporterAllAsCsv: 'Exportar todos os dados como csv',
13461 exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13462 exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13463 exporterAllAsPdf: 'Exportar todos os dados como pdf',
13464 exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13465 exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13466 clearAllFilters: 'Limpar todos os filtros'
13469 noHeaders: 'Nomes de colunas não puderam ser derivados. O arquivo tem um cabeçalho?',
13470 noObjects: 'Objetos não puderam ser derivados. Havia dados no arquivo, além dos cabeçalhos?',
13471 invalidCsv: 'Arquivo não pode ser processado. É um CSV válido?',
13472 invalidJson: 'Arquivo não pode ser processado. É um Json válido?',
13473 jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
13477 pageToFirst: 'Primeira página',
13478 pageBack: 'Página anterior',
13479 pageSelected: 'Página Selecionada',
13480 pageForward: 'Proxima',
13481 pageToLast: 'Anterior'
13483 sizes: 'itens por página',
13484 totalItems: 'itens',
13485 through: 'através dos',
13490 ungroup: 'Desagrupar',
13491 aggregate_count: 'Agr: Contar',
13492 aggregate_sum: 'Agr: Soma',
13493 aggregate_max: 'Agr: Max',
13494 aggregate_min: 'Agr: Min',
13495 aggregate_avg: 'Agr: Med',
13496 aggregate_remove: 'Agr: Remover'
13505 angular.module('ui.grid').config(['$provide', function($provide) {
13506 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13507 $delegate.add('pt', {
13510 defaultFilterLabel: 'Filtro por coluna',
13511 removeFilter: 'Remover filtro',
13512 columnMenuButtonLabel: 'Menu coluna'
13514 priority: 'Prioridade:',
13515 filterLabel: "Filtro por coluna: "
13521 description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
13524 placeholder: 'Procurar...',
13525 showingItems: 'Mostrando os Itens:',
13526 selectedItems: 'Itens Selecionados:',
13527 totalItems: 'Total de Itens:',
13528 size: 'Tamanho da Página:',
13529 first: 'Primeira Página',
13530 next: 'Próxima Página',
13531 previous: 'Página Anterior',
13532 last: 'Última Página'
13535 text: 'Selecione as colunas:'
13538 ascending: 'Ordenar Ascendente',
13539 descending: 'Ordenar Descendente',
13540 none: 'Nenhuma Ordem',
13541 remove: 'Remover Ordenação'
13544 hide: 'Esconder coluna'
13547 count: 'total de linhas: ',
13554 pinLeft: 'Fixar Esquerda',
13555 pinRight: 'Fixar Direita',
13556 unpin: 'Desprender'
13563 buttonLabel: 'Menu Grid'
13565 columns: 'Colunas:',
13566 importerTitle: 'Importar ficheiro',
13567 exporterAllAsCsv: 'Exportar todos os dados como csv',
13568 exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
13569 exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
13570 exporterAllAsPdf: 'Exportar todos os dados como pdf',
13571 exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
13572 exporterSelectedAsPdf: 'Exportar dados selecionados como pdf',
13573 clearAllFilters: 'Limpar todos os filtros'
13576 noHeaders: 'Nomes de colunas não puderam ser derivados. O ficheiro tem um cabeçalho?',
13577 noObjects: 'Objetos não puderam ser derivados. Havia dados no ficheiro, além dos cabeçalhos?',
13578 invalidCsv: 'Ficheiro não pode ser processado. É um CSV válido?',
13579 invalidJson: 'Ficheiro não pode ser processado. É um Json válido?',
13580 jsonNotArray: 'Ficheiro json importado tem que conter um array. Interrompendo.'
13584 pageToFirst: 'Primeira página',
13585 pageBack: 'Página anterior',
13586 pageSelected: 'Página Selecionada',
13587 pageForward: 'Próxima',
13588 pageToLast: 'Anterior'
13590 sizes: 'itens por página',
13591 totalItems: 'itens',
13592 through: 'através dos',
13597 ungroup: 'Desagrupar',
13598 aggregate_count: 'Agr: Contar',
13599 aggregate_sum: 'Agr: Soma',
13600 aggregate_max: 'Agr: Max',
13601 aggregate_min: 'Agr: Min',
13602 aggregate_avg: 'Agr: Med',
13603 aggregate_remove: 'Agr: Remover'
13612 angular.module('ui.grid').config(['$provide', function($provide) {
13613 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13614 $delegate.add('ro', {
13617 defaultFilterLabel: 'Filtru pentru coloana',
13618 removeFilter: 'Sterge filtru',
13619 columnMenuButtonLabel: 'Column Menu'
13621 priority: 'Prioritate:',
13622 filterLabel: "Filtru pentru coloana:"
13628 description: 'Trage un cap de coloana aici pentru a grupa elementele dupa coloana respectiva'
13631 placeholder: 'Cauta...',
13632 showingItems: 'Arata elementele:',
13633 selectedItems: 'Elementele selectate:',
13634 totalItems: 'Total elemente:',
13635 size: 'Marime pagina:',
13636 first: 'Prima pagina',
13637 next: 'Pagina urmatoare',
13638 previous: 'Pagina anterioara',
13639 last: 'Ultima pagina'
13642 text: 'Alege coloane:'
13645 ascending: 'Ordoneaza crescator',
13646 descending: 'Ordoneaza descrescator',
13647 none: 'Fara ordonare',
13648 remove: 'Sterge ordonarea'
13651 hide: 'Ascunde coloana'
13654 count: 'total linii: ',
13661 pinLeft: 'Pin la stanga',
13662 pinRight: 'Pin la dreapta',
13663 unpin: 'Sterge pinul'
13670 buttonLabel: 'Grid Menu'
13672 columns: 'Coloane:',
13673 importerTitle: 'Incarca fisier',
13674 exporterAllAsCsv: 'Exporta toate datele ca csv',
13675 exporterVisibleAsCsv: 'Exporta datele vizibile ca csv',
13676 exporterSelectedAsCsv: 'Exporta datele selectate ca csv',
13677 exporterAllAsPdf: 'Exporta toate datele ca pdf',
13678 exporterVisibleAsPdf: 'Exporta datele vizibile ca pdf',
13679 exporterSelectedAsPdf: 'Exporta datele selectate ca csv pdf',
13680 clearAllFilters: 'Sterge toate filtrele'
13683 noHeaders: 'Numele coloanelor nu a putut fi incarcat, acest fisier are un header?',
13684 noObjects: 'Datele nu au putut fi incarcate, exista date in fisier in afara numelor de coloane?',
13685 invalidCsv: 'Fisierul nu a putut fi procesat, ati incarcat un CSV valid ?',
13686 invalidJson: 'Fisierul nu a putut fi procesat, ati incarcat un Json valid?',
13687 jsonNotArray: 'Json-ul incarcat trebuie sa contina un array, inchidere.'
13691 pageToFirst: 'Prima pagina',
13692 pageBack: 'O pagina inapoi',
13693 pageSelected: 'Pagina selectata',
13694 pageForward: 'O pagina inainte',
13695 pageToLast: 'Ultima pagina'
13697 sizes: 'Elemente per pagina',
13698 totalItems: 'elemente',
13704 ungroup: 'Opreste gruparea',
13705 aggregate_count: 'Agg: Count',
13706 aggregate_sum: 'Agg: Sum',
13707 aggregate_max: 'Agg: Max',
13708 aggregate_min: 'Agg: Min',
13709 aggregate_avg: 'Agg: Avg',
13710 aggregate_remove: 'Agg: Remove'
13719 angular.module('ui.grid').config(['$provide', function($provide) {
13720 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13721 $delegate.add('ru', {
13724 defaultFilterLabel: 'Фильтр столбца',
13725 removeFilter: 'Удалить фильтр',
13726 columnMenuButtonLabel: 'Меню столбца'
13728 priority: 'Приоритет:',
13729 filterLabel: "Фильтр столбца: "
13735 description: 'Для группировки по столбцу перетащите сюда его название.'
13738 placeholder: 'Поиск...',
13739 showingItems: 'Показать элементы:',
13740 selectedItems: 'Выбранные элементы:',
13741 totalItems: 'Всего элементов:',
13742 size: 'Размер страницы:',
13743 first: 'Первая страница',
13744 next: 'Следующая страница',
13745 previous: 'Предыдущая страница',
13746 last: 'Последняя страница'
13749 text: 'Выбрать столбцы:'
13752 ascending: 'По возрастанию',
13753 descending: 'По убыванию',
13754 none: 'Без сортировки',
13755 remove: 'Убрать сортировку'
13758 hide: 'Спрятать столбец'
13761 count: 'всего строк: ',
13768 pinLeft: 'Закрепить слева',
13769 pinRight: 'Закрепить справа',
13777 buttonLabel: 'Меню'
13779 columns: 'Столбцы:',
13780 importerTitle: 'Импортировать файл',
13781 exporterAllAsCsv: 'Экспортировать всё в CSV',
13782 exporterVisibleAsCsv: 'Экспортировать видимые данные в CSV',
13783 exporterSelectedAsCsv: 'Экспортировать выбранные данные в CSV',
13784 exporterAllAsPdf: 'Экспортировать всё в PDF',
13785 exporterVisibleAsPdf: 'Экспортировать видимые данные в PDF',
13786 exporterSelectedAsPdf: 'Экспортировать выбранные данные в PDF',
13787 clearAllFilters: 'Очистите все фильтры'
13790 noHeaders: 'Не удалось получить названия столбцов, есть ли в файле заголовок?',
13791 noObjects: 'Не удалось получить данные, есть ли в файле строки кроме заголовка?',
13792 invalidCsv: 'Не удалось обработать файл, это правильный CSV-файл?',
13793 invalidJson: 'Не удалось обработать файл, это правильный JSON?',
13794 jsonNotArray: 'Импортируемый JSON-файл должен содержать массив, операция отменена.'
13798 pageToFirst: 'Первая страница',
13799 pageBack: 'Предыдущая страница',
13800 pageSelected: 'Выбранная страница',
13801 pageForward: 'Следующая страница',
13802 pageToLast: 'Последняя страница'
13804 sizes: 'строк на страницу',
13805 totalItems: 'строк',
13810 group: 'Группировать',
13811 ungroup: 'Разгруппировать',
13812 aggregate_count: 'Группировать: Count',
13813 aggregate_sum: 'Для группы: Сумма',
13814 aggregate_max: 'Для группы: Максимум',
13815 aggregate_min: 'Для группы: Минимум',
13816 aggregate_avg: 'Для группы: Среднее',
13817 aggregate_remove: 'Для группы: Пусто'
13826 angular.module('ui.grid').config(['$provide', function($provide) {
13827 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13828 $delegate.add('sk', {
13833 description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
13836 placeholder: 'Hľadaj...',
13837 showingItems: 'Zobrazujem položky:',
13838 selectedItems: 'Vybraté položky:',
13839 totalItems: 'Počet položiek:',
13841 first: 'Prvá strana',
13842 next: 'Ďalšia strana',
13843 previous: 'Predchádzajúca strana',
13844 last: 'Posledná strana'
13847 text: 'Vyberte stĺpce:'
13850 ascending: 'Zotriediť vzostupne',
13851 descending: 'Zotriediť zostupne',
13852 remove: 'Vymazať triedenie'
13855 count: 'total rows: ',
13862 columns: 'Columns:',
13863 importerTitle: 'Import file',
13864 exporterAllAsCsv: 'Export all data as csv',
13865 exporterVisibleAsCsv: 'Export visible data as csv',
13866 exporterSelectedAsCsv: 'Export selected data as csv',
13867 exporterAllAsPdf: 'Export all data as pdf',
13868 exporterVisibleAsPdf: 'Export visible data as pdf',
13869 exporterSelectedAsPdf: 'Export selected data as pdf',
13870 clearAllFilters: 'Clear all filters'
13873 noHeaders: 'Column names were unable to be derived, does the file have a header?',
13874 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
13875 invalidCsv: 'File was unable to be processed, is it valid CSV?',
13876 invalidJson: 'File was unable to be processed, is it valid Json?',
13877 jsonNotArray: 'Imported json file must contain an array, aborting.'
13886 angular.module('ui.grid').config(['$provide', function($provide) {
13887 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13888 $delegate.add('sv', {
13893 description: 'Dra en kolumnrubrik hit och släpp den för att gruppera efter den kolumnen.'
13896 placeholder: 'Sök...',
13897 showingItems: 'Visar artiklar:',
13898 selectedItems: 'Valda artiklar:',
13899 totalItems: 'Antal artiklar:',
13900 size: 'Sidstorlek:',
13901 first: 'Första sidan',
13902 next: 'Nästa sida',
13903 previous: 'Föregående sida',
13904 last: 'Sista sidan'
13907 text: 'Välj kolumner:'
13910 ascending: 'Sortera stigande',
13911 descending: 'Sortera fallande',
13912 remove: 'Inaktivera sortering'
13918 count: 'Antal rader: ',
13920 avg: 'Genomsnitt: ',
13925 pinLeft: 'Fäst vänster',
13926 pinRight: 'Fäst höger',
13930 columns: 'Kolumner:',
13931 importerTitle: 'Importera fil',
13932 exporterAllAsCsv: 'Exportera all data som CSV',
13933 exporterVisibleAsCsv: 'Exportera synlig data som CSV',
13934 exporterSelectedAsCsv: 'Exportera markerad data som CSV',
13935 exporterAllAsPdf: 'Exportera all data som PDF',
13936 exporterVisibleAsPdf: 'Exportera synlig data som PDF',
13937 exporterSelectedAsPdf: 'Exportera markerad data som PDF',
13938 clearAllFilters: 'Rengör alla filter'
13941 noHeaders: 'Kolumnnamn kunde inte härledas. Har filen ett sidhuvud?',
13942 noObjects: 'Objekt kunde inte härledas. Har filen data undantaget sidhuvud?',
13943 invalidCsv: 'Filen kunde inte behandlas, är den en giltig CSV?',
13944 invalidJson: 'Filen kunde inte behandlas, är den en giltig JSON?',
13945 jsonNotArray: 'Importerad JSON-fil måste innehålla ett fält. Import avbruten.'
13948 sizes: 'Artiklar per sida',
13949 totalItems: 'Artiklar'
13958 angular.module('ui.grid').config(['$provide', function($provide) {
13959 $provide.decorator('i18nService', ['$delegate', function($delegate) {
13960 $delegate.add('ta', {
13962 label: 'உருப்படிகள்'
13965 description: 'ஒரு பத்தியை குழுவாக அமைக்க அப்பத்தியின் தலைப்பை இங்கே இழுத்து வரவும் '
13968 placeholder: 'தேடல் ...',
13969 showingItems: 'உருப்படிகளை காண்பித்தல்:',
13970 selectedItems: 'தேர்ந்தெடுக்கப்பட்ட உருப்படிகள்:',
13971 totalItems: 'மொத்த உருப்படிகள்:',
13972 size: 'பக்க அளவு: ',
13973 first: 'முதல் பக்கம்',
13974 next: 'அடுத்த பக்கம்',
13975 previous: 'முந்தைய பக்கம் ',
13976 last: 'இறுதி பக்கம்'
13979 text: 'பத்திகளை தேர்ந்தெடு:'
13982 ascending: 'மேலிருந்து கீழாக',
13983 descending: 'கீழிருந்து மேலாக',
13984 remove: 'வரிசையை நீக்கு'
13987 hide: 'பத்தியை மறைத்து வை '
13990 count: 'மொத்த வரிகள்:',
13993 min: 'குறைந்தபட்ச: ',
13997 pinLeft: 'இடதுபுறமாக தைக்க ',
13998 pinRight: 'வலதுபுறமாக தைக்க',
14002 columns: 'பத்திகள்:',
14003 importerTitle: 'கோப்பு : படித்தல்',
14004 exporterAllAsCsv: 'எல்லா தரவுகளையும் கோப்பாக்கு: csv',
14005 exporterVisibleAsCsv: 'இருக்கும் தரவுகளை கோப்பாக்கு: csv',
14006 exporterSelectedAsCsv: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: csv',
14007 exporterAllAsPdf: 'எல்லா தரவுகளையும் கோப்பாக்கு: pdf',
14008 exporterVisibleAsPdf: 'இருக்கும் தரவுகளை கோப்பாக்கு: pdf',
14009 exporterSelectedAsPdf: 'தேர்ந்தெடுத்த தரவுகளை கோப்பாக்கு: pdf',
14010 clearAllFilters: 'Clear all filters'
14013 noHeaders: 'பத்தியின் தலைப்புகளை பெற இயலவில்லை, கோப்பிற்கு தலைப்பு உள்ளதா?',
14014 noObjects: 'இலக்குகளை உருவாக்க முடியவில்லை, கோப்பில் தலைப்புகளை தவிர தரவு ஏதேனும் உள்ளதா? ',
14015 invalidCsv: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - csv',
14016 invalidJson: 'சரிவர நடைமுறை படுத்த இயலவில்லை, கோப்பு சரிதானா? - json',
14017 jsonNotArray: 'படித்த கோப்பில் வரிசைகள் உள்ளது, நடைமுறை ரத்து செய் : json'
14020 sizes : 'உருப்படிகள் / பக்கம்',
14021 totalItems : 'உருப்படிகள் '
14026 aggregate_count : 'மதிப்பீட்டு : எண்ணு',
14027 aggregate_sum : 'மதிப்பீட்டு : கூட்டல்',
14028 aggregate_max : 'மதிப்பீட்டு : அதிகபட்சம்',
14029 aggregate_min : 'மதிப்பீட்டு : குறைந்தபட்சம்',
14030 aggregate_avg : 'மதிப்பீட்டு : சராசரி',
14031 aggregate_remove : 'மதிப்பீட்டு : நீக்கு'
14040 angular.module('ui.grid').config(['$provide', function($provide) {
14041 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14042 $delegate.add('tr', {
14045 defaultFilterLabel: 'Sütun için filtre',
14046 removeFilter: 'Filtreyi Kaldır',
14047 columnMenuButtonLabel: 'Sütun Menüsü'
14049 priority: 'Öncelik:',
14050 filterLabel: "Sütun için filtre: "
14056 description: 'Sütuna göre gruplamak için sütun başlığını buraya sürükleyin ve bırakın.'
14059 placeholder: 'Arama...',
14060 showingItems: 'Gösterilen Kayıt:',
14061 selectedItems: 'Seçili Kayıt:',
14062 totalItems: 'Toplam Kayıt:',
14063 size: 'Sayfa Boyutu:',
14064 first: 'İlk Sayfa',
14065 next: 'Sonraki Sayfa',
14066 previous: 'Önceki Sayfa',
14070 text: 'Sütunları Seç:'
14073 ascending: 'Artan Sırada Sırala',
14074 descending: 'Azalan Sırada Sırala',
14075 none: 'Sıralama Yapma',
14076 remove: 'Sıralamayı Kaldır'
14079 hide: 'Sütunu Gizle'
14082 count: 'toplam satır: ',
14089 pinLeft: 'Sola Sabitle',
14090 pinRight: 'Sağa Sabitle',
14091 unpin: 'Sabitlemeyi Kaldır'
14098 buttonLabel: 'Tablo Menü'
14100 columns: 'Sütunlar:',
14101 importerTitle: 'Dosya içeri aktar',
14102 exporterAllAsCsv: 'Bütün veriyi CSV olarak dışarı aktar',
14103 exporterVisibleAsCsv: 'Görünen veriyi CSV olarak dışarı aktar',
14104 exporterSelectedAsCsv: 'Seçili veriyi CSV olarak dışarı aktar',
14105 exporterAllAsPdf: 'Bütün veriyi PDF olarak dışarı aktar',
14106 exporterVisibleAsPdf: 'Görünen veriyi PDF olarak dışarı aktar',
14107 exporterSelectedAsPdf: 'Seçili veriyi PDF olarak dışarı aktar',
14108 clearAllFilters: 'Bütün filtreleri kaldır'
14111 noHeaders: 'Sütun isimleri üretilemiyor, dosyanın bir başlığı var mı?',
14112 noObjects: 'Nesneler üretilemiyor, dosyada başlıktan başka bir veri var mı?',
14113 invalidCsv: 'Dosya işlenemedi, geçerli bir CSV dosyası mı?',
14114 invalidJson: 'Dosya işlenemedi, geçerli bir Json dosyası mı?',
14115 jsonNotArray: 'Alınan Json dosyasında bir dizi bulunmalıdır, işlem iptal ediliyor.'
14119 pageToFirst: 'İlk sayfaya',
14120 pageBack: 'Geri git',
14121 pageSelected: 'Seçili sayfa',
14122 pageForward: 'İleri git',
14123 pageToLast: 'Sona git'
14125 sizes: 'Sayfadaki nesne sayısı',
14126 totalItems: 'kayıtlar',
14127 through: '', //note(fsw) : turkish dont have this preposition
14128 of: '' //note(fsw) : turkish dont have this preposition
14132 ungroup: 'Gruplama',
14133 aggregate_count: 'Yekun: Sayı',
14134 aggregate_sum: 'Yekun: Toplam',
14135 aggregate_max: 'Yekun: Maks',
14136 aggregate_min: 'Yekun: Min',
14137 aggregate_avg: 'Yekun: Ort',
14138 aggregate_remove: 'Yekun: Sil'
14146 angular.module('ui.grid').config(['$provide', function($provide) {
14147 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14148 $delegate.add('ua', {
14151 defaultFilterLabel: 'Фільтр стовпчика',
14152 removeFilter: 'Видалити фільтр',
14153 columnMenuButtonLabel: 'Меню ствпчика'
14155 priority: 'Пріоритет:',
14156 filterLabel: "Фільтр стовпчика: "
14162 description: 'Для групування за стовпчиком перетягніть сюди його назву.'
14165 placeholder: 'Пошук...',
14166 showingItems: 'Показати елементи:',
14167 selectedItems: 'Обрані елементи:',
14168 totalItems: 'Усього елементів:',
14169 size: 'Розмір сторінки:',
14170 first: 'Перша сторінка',
14171 next: 'Наступна сторінка',
14172 previous: 'Попередня сторінка',
14173 last: 'Остання сторінка'
14176 text: 'Обрати ствпчики:'
14179 ascending: 'За зростанням',
14180 descending: 'За спаданням',
14181 none: 'Без сортування',
14182 remove: 'Прибрати сортування'
14185 hide: 'Приховати стовпчик'
14188 count: 'усього рядків: ',
14195 pinLeft: 'Закріпити ліворуч',
14196 pinRight: 'Закріпити праворуч',
14197 unpin: 'Відкріпити'
14204 buttonLabel: 'Меню'
14206 columns: 'Стовпчики:',
14207 importerTitle: 'Імпортувати файл',
14208 exporterAllAsCsv: 'Експортувати все в CSV',
14209 exporterVisibleAsCsv: 'Експортувати видимі дані в CSV',
14210 exporterSelectedAsCsv: 'Експортувати обрані дані в CSV',
14211 exporterAllAsPdf: 'Експортувати все в PDF',
14212 exporterVisibleAsPdf: 'Експортувати видимі дані в PDF',
14213 exporterSelectedAsPdf: 'Експортувати обрані дані в PDF',
14214 clearAllFilters: 'Очистити всі фільтри'
14217 noHeaders: 'Не вдалося отримати назви стовпчиків, чи є в файлі заголовок?',
14218 noObjects: 'Не вдалося отримати дані, чи є в файлі рядки окрім заголовка?',
14219 invalidCsv: 'Не вдалося обробити файл, чи це коректний CSV-файл?',
14220 invalidJson: 'Не вдалося обробити файл, чи це коректний JSON?',
14221 jsonNotArray: 'JSON-файл що імпортується повинен містити масив, операцію скасовано.'
14225 pageToFirst: 'Перша сторінка',
14226 pageBack: 'Попередня сторінка',
14227 pageSelected: 'Обрана сторінка',
14228 pageForward: 'Наступна сторінка',
14229 pageToLast: 'Остання сторінка'
14231 sizes: 'рядків на сторінку',
14232 totalItems: 'рядків',
14237 group: 'Групувати',
14238 ungroup: 'Розгрупувати',
14239 aggregate_count: 'Групувати: Кількість',
14240 aggregate_sum: 'Для групи: Сума',
14241 aggregate_max: 'Для групи: Максимум',
14242 aggregate_min: 'Для групи: Мінімум',
14243 aggregate_avg: 'Для групи: Серднє',
14244 aggregate_remove: 'Для групи: Пусто'
14254 * @name ui.grid.i18n
14258 * This module provides i18n functions to ui.grid and any application that wants to use it
14261 * <div doc-module-components="ui.grid.i18n"></div>
14265 var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
14266 var FILTER_ALIASES = ['t', 'uiTranslate'];
14268 var module = angular.module('ui.grid.i18n');
14273 * @name ui.grid.i18n.constant:i18nConstants
14275 * @description constants available in i18n module
14277 module.constant('i18nConstants', {
14278 MISSING: '[MISSING]',
14279 UPDATE_EVENT: '$uiI18n',
14281 LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
14282 // default to english
14286 // module.config(['$provide', function($provide) {
14287 // $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
14291 * @name ui.grid.i18n.service:i18nService
14293 * @description Services for i18n
14295 module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
14296 function ($log, i18nConstants, $rootScope) {
14301 get: function (lang) {
14302 return this._langs[lang.toLowerCase()];
14304 add: function (lang, strings) {
14305 var lower = lang.toLowerCase();
14306 if (!this._langs[lower]) {
14307 this._langs[lower] = {};
14309 angular.extend(this._langs[lower], strings);
14311 getAllLangs: function () {
14313 if (!this._langs) {
14317 for (var key in this._langs) {
14323 setCurrent: function (lang) {
14324 this.current = lang.toLowerCase();
14326 getCurrentLang: function () {
14327 return this.current;
14336 * @methodOf ui.grid.i18n.service:i18nService
14337 * @description Adds the languages and strings to the cache. Decorate this service to
14338 * add more translation strings
14339 * @param {string} lang language to add
14340 * @param {object} stringMaps of strings to add grouped by property names
14343 * i18nService.add('en', {
14346 * label2: 'some more items'
14350 * description: 'Drag a column header here and drop it to group by that column.'
14355 add: function (langs, stringMaps) {
14356 if (typeof(langs) === 'object') {
14357 angular.forEach(langs, function (lang) {
14359 langCache.add(lang, stringMaps);
14363 langCache.add(langs, stringMaps);
14369 * @name getAllLangs
14370 * @methodOf ui.grid.i18n.service:i18nService
14371 * @description return all currently loaded languages
14372 * @returns {array} string
14374 getAllLangs: function () {
14375 return langCache.getAllLangs();
14381 * @methodOf ui.grid.i18n.service:i18nService
14382 * @description return all currently loaded languages
14383 * @param {string} lang to return. If not specified, returns current language
14384 * @returns {object} the translation string maps for the language
14386 get: function (lang) {
14387 var language = lang ? lang : service.getCurrentLang();
14388 return langCache.get(language);
14393 * @name getSafeText
14394 * @methodOf ui.grid.i18n.service:i18nService
14395 * @description returns the text specified in the path or a Missing text if text is not found
14396 * @param {string} path property path to use for retrieving text from string map
14397 * @param {string} lang to return. If not specified, returns current language
14398 * @returns {object} the translation for the path
14401 * i18nService.getSafeText('sort.ascending')
14404 getSafeText: function (path, lang) {
14405 var language = lang ? lang : service.getCurrentLang();
14406 var trans = langCache.get(language);
14409 return i18nConstants.MISSING;
14412 var paths = path.split('.');
14413 var current = trans;
14415 for (var i = 0; i < paths.length; ++i) {
14416 if (current[paths[i]] === undefined || current[paths[i]] === null) {
14417 return i18nConstants.MISSING;
14419 current = current[paths[i]];
14429 * @name setCurrentLang
14430 * @methodOf ui.grid.i18n.service:i18nService
14431 * @description sets the current language to use in the application
14432 * $broadcasts the Update_Event on the $rootScope
14433 * @param {string} lang to set
14436 * i18nService.setCurrentLang('fr');
14440 setCurrentLang: function (lang) {
14442 langCache.setCurrent(lang);
14443 $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
14449 * @name getCurrentLang
14450 * @methodOf ui.grid.i18n.service:i18nService
14451 * @description returns the current language used in the application
14453 getCurrentLang: function () {
14454 var lang = langCache.getCurrentLang();
14456 lang = i18nConstants.DEFAULT_LANG;
14457 langCache.setCurrent(lang);
14468 var localeDirective = function (i18nService, i18nConstants) {
14470 compile: function () {
14472 pre: function ($scope, $elm, $attrs) {
14473 var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
14474 // check for watchable property
14475 var lang = $scope.$eval($attrs[alias]);
14477 $scope.$watch($attrs[alias], function () {
14478 i18nService.setCurrentLang(lang);
14480 } else if ($attrs.$$observers) {
14481 $attrs.$observe(alias, function () {
14482 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
14491 module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
14493 // directive syntax
14494 var uitDirective = function ($parse, i18nService, i18nConstants) {
14497 compile: function () {
14499 pre: function ($scope, $elm, $attrs) {
14500 var alias1 = DIRECTIVE_ALIASES[0],
14501 alias2 = DIRECTIVE_ALIASES[1];
14502 var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
14503 var missing = i18nConstants.MISSING + token;
14505 if ($attrs.$$observers) {
14506 var prop = $attrs[alias1] ? alias1 : alias2;
14507 observer = $attrs.$observe(prop, function (result) {
14509 $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
14513 var getter = $parse(token);
14514 var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
14516 observer($attrs[alias1] || $attrs[alias2]);
14518 // set text based on i18n current language
14519 $elm.html(getter(i18nService.get()) || missing);
14522 $scope.$on('$destroy', listener);
14524 $elm.html(getter(i18nService.get()) || missing);
14531 angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
14532 module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
14535 // optional filter syntax
14536 var uitFilter = function ($parse, i18nService, i18nConstants) {
14537 return function (data) {
14538 var getter = $parse(data);
14539 // set text based on i18n current language
14540 return getter(i18nService.get()) || i18nConstants.MISSING + data;
14544 angular.forEach( FILTER_ALIASES, function ( alias ) {
14545 module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
14551 angular.module('ui.grid').config(['$provide', function($provide) {
14552 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14553 $delegate.add('zh-cn', {
14556 defaultFilterLabel: '列过滤器',
14557 removeFilter: '移除过滤器',
14558 columnMenuButtonLabel: '列菜单'
14561 filterLabel: "列过滤器: "
14567 description: '拖曳表头到此处进行分组'
14571 showingItems: '已显示行数:',
14572 selectedItems: '已选择行数:',
14573 totalItems: '总行数:',
14609 buttonLabel: '表格菜单'
14612 importerTitle: '导入文件',
14613 exporterAllAsCsv: '导出全部数据到CSV',
14614 exporterVisibleAsCsv: '导出可见数据到CSV',
14615 exporterSelectedAsCsv: '导出已选数据到CSV',
14616 exporterAllAsPdf: '导出全部数据到PDF',
14617 exporterVisibleAsPdf: '导出可见数据到PDF',
14618 exporterSelectedAsPdf: '导出已选数据到PDF',
14619 clearAllFilters: '清除所有过滤器'
14622 noHeaders: '无法获取列名,确定文件包含表头?',
14623 noObjects: '无法获取数据,确定文件包含数据?',
14624 invalidCsv: '无法处理文件,确定是合法的CSV文件?',
14625 invalidJson: '无法处理文件,确定是合法的JSON文件?',
14626 jsonNotArray: '导入的文件不是JSON数组!'
14630 pageToFirst: '第一页',
14632 pageSelected: '当前页',
14633 pageForward: '下一页',
14644 aggregate_count: '合计: 计数',
14645 aggregate_sum: '合计: 求和',
14646 aggregate_max: '合计: 最大',
14647 aggregate_min: '合计: 最小',
14648 aggregate_avg: '合计: 平均',
14649 aggregate_remove: '合计: 移除'
14658 angular.module('ui.grid').config(['$provide', function($provide) {
14659 $provide.decorator('i18nService', ['$delegate', function($delegate) {
14660 $delegate.add('zh-tw', {
14665 description: '拖曳表頭到此處進行分組'
14669 showingItems: '已顯示行數:',
14670 selectedItems: '已選擇行數:',
14671 totalItems: '總行數:',
14703 importerTitle: '導入文件',
14704 exporterAllAsCsv: '導出全部數據到CSV',
14705 exporterVisibleAsCsv: '導出可見數據到CSV',
14706 exporterSelectedAsCsv: '導出已選數據到CSV',
14707 exporterAllAsPdf: '導出全部數據到PDF',
14708 exporterVisibleAsPdf: '導出可見數據到PDF',
14709 exporterSelectedAsPdf: '導出已選數據到PDF',
14710 clearAllFilters: '清除所有过滤器'
14713 noHeaders: '無法獲取列名,確定文件包含表頭?',
14714 noObjects: '無法獲取數據,確定文件包含數據?',
14715 invalidCsv: '無法處理文件,確定是合法的CSV文件?',
14716 invalidJson: '無法處理文件,確定是合法的JSON文件?',
14717 jsonNotArray: '導入的文件不是JSON數組!'
14733 * @name ui.grid.autoResize
14737 * #ui.grid.autoResize
14739 * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
14741 * This module provides auto-resizing functionality to UI-Grid.
14743 var module = angular.module('ui.grid.autoResize', ['ui.grid']);
14746 module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
14750 link: function ($scope, $elm, $attrs, uiGridCtrl) {
14751 var prevGridWidth, prevGridHeight;
14753 function getDimensions() {
14754 prevGridHeight = gridUtil.elementHeight($elm);
14755 prevGridWidth = gridUtil.elementWidth($elm);
14758 // Initialize the dimensions
14761 var resizeTimeoutId;
14762 function startTimeout() {
14763 clearTimeout(resizeTimeoutId);
14765 resizeTimeoutId = setTimeout(function () {
14766 var newGridHeight = gridUtil.elementHeight($elm);
14767 var newGridWidth = gridUtil.elementWidth($elm);
14769 if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
14770 uiGridCtrl.grid.gridHeight = newGridHeight;
14771 uiGridCtrl.grid.gridWidth = newGridWidth;
14772 uiGridCtrl.grid.api.core.raise.gridDimensionChanged(prevGridHeight, prevGridWidth, newGridHeight, newGridWidth);
14774 $scope.$apply(function () {
14775 uiGridCtrl.grid.refresh()
14776 .then(function () {
14791 $scope.$on('$destroy', function() {
14792 clearTimeout(resizeTimeoutId);
14804 * @name ui.grid.cellNav
14810 <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
14812 This module provides cell navigation functionality to UI-Grid.
14814 var module = angular.module('ui.grid.cellNav', ['ui.grid']);
14818 * @name ui.grid.cellNav.constant:uiGridCellNavConstants
14820 * @description constants available in cellNav
14822 module.constant('uiGridCellNavConstants', {
14823 FEATURE_NAME: 'gridCellNav',
14824 CELL_NAV_EVENT: 'cellNav',
14825 direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3, PG_UP: 4, PG_DOWN: 5},
14834 module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', 'GridRowColumn', '$q',
14835 function (gridUtil, uiGridConstants, uiGridCellNavConstants, GridRowColumn, $q) {
14838 * @name ui.grid.cellNav.object:CellNav
14839 * @description returns a CellNav prototype function
14840 * @param {object} rowContainer container for rows
14841 * @param {object} colContainer parent column container
14842 * @param {object} leftColContainer column container to the left of parent
14843 * @param {object} rightColContainer column container to the right of parent
14845 var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
14846 this.rows = rowContainer.visibleRowCache;
14847 this.columns = colContainer.visibleColumnCache;
14848 this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
14849 this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
14850 this.bodyContainer = rowContainer;
14853 /** returns focusable columns of all containers */
14854 UiGridCellNav.prototype.getFocusableCols = function () {
14855 var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
14857 return allColumns.filter(function (col) {
14858 return col.colDef.allowCellFocus;
14864 * @name ui.grid.cellNav.api:GridRow
14866 * @description GridRow settings for cellNav feature, these are available to be
14867 * set only internally (for example, by other features)
14872 * @name allowCellFocus
14873 * @propertyOf ui.grid.cellNav.api:GridRow
14874 * @description Enable focus on a cell within this row. If set to false then no cells
14875 * in this row can be focused - group header rows as an example would set this to false.
14876 * <br/>Defaults to true
14878 /** returns focusable rows */
14879 UiGridCellNav.prototype.getFocusableRows = function () {
14880 return this.rows.filter(function(row) {
14881 return row.allowCellFocus !== false;
14885 UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
14886 switch (direction) {
14887 case uiGridCellNavConstants.direction.LEFT:
14888 return this.getRowColLeft(curRow, curCol);
14889 case uiGridCellNavConstants.direction.RIGHT:
14890 return this.getRowColRight(curRow, curCol);
14891 case uiGridCellNavConstants.direction.UP:
14892 return this.getRowColUp(curRow, curCol);
14893 case uiGridCellNavConstants.direction.DOWN:
14894 return this.getRowColDown(curRow, curCol);
14895 case uiGridCellNavConstants.direction.PG_UP:
14896 return this.getRowColPageUp(curRow, curCol);
14897 case uiGridCellNavConstants.direction.PG_DOWN:
14898 return this.getRowColPageDown(curRow, curCol);
14903 UiGridCellNav.prototype.initializeSelection = function () {
14904 var focusableCols = this.getFocusableCols();
14905 var focusableRows = this.getFocusableRows();
14906 if (focusableCols.length === 0 || focusableRows.length === 0) {
14910 var curRowIndex = 0;
14911 var curColIndex = 0;
14912 return new GridRowColumn(focusableRows[0], focusableCols[0]); //return same row
14915 UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
14916 var focusableCols = this.getFocusableCols();
14917 var focusableRows = this.getFocusableRows();
14918 var curColIndex = focusableCols.indexOf(curCol);
14919 var curRowIndex = focusableRows.indexOf(curRow);
14921 //could not find column in focusable Columns so set it to 1
14922 if (curColIndex === -1) {
14926 var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
14928 //get column to left
14929 if (nextColIndex >= curColIndex) {
14930 // On the first row
14931 // if (curRowIndex === 0 && curColIndex === 0) {
14934 if (curRowIndex === 0) {
14935 return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14938 //up one row and far right column
14939 return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[nextColIndex]);
14943 return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14949 UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
14950 var focusableCols = this.getFocusableCols();
14951 var focusableRows = this.getFocusableRows();
14952 var curColIndex = focusableCols.indexOf(curCol);
14953 var curRowIndex = focusableRows.indexOf(curRow);
14955 //could not find column in focusable Columns so set it to 0
14956 if (curColIndex === -1) {
14959 var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
14961 if (nextColIndex <= curColIndex) {
14962 if (curRowIndex === focusableRows.length - 1) {
14963 return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
14966 //down one row and far left column
14967 return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[nextColIndex]);
14971 return new GridRowColumn(curRow, focusableCols[nextColIndex]);
14975 UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
14976 var focusableCols = this.getFocusableCols();
14977 var focusableRows = this.getFocusableRows();
14978 var curColIndex = focusableCols.indexOf(curCol);
14979 var curRowIndex = focusableRows.indexOf(curRow);
14981 //could not find column in focusable Columns so set it to 0
14982 if (curColIndex === -1) {
14986 if (curRowIndex === focusableRows.length - 1) {
14987 return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
14991 return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[curColIndex]);
14995 UiGridCellNav.prototype.getRowColPageDown = function (curRow, curCol) {
14996 var focusableCols = this.getFocusableCols();
14997 var focusableRows = this.getFocusableRows();
14998 var curColIndex = focusableCols.indexOf(curCol);
14999 var curRowIndex = focusableRows.indexOf(curRow);
15001 //could not find column in focusable Columns so set it to 0
15002 if (curColIndex === -1) {
15006 var pageSize = this.bodyContainer.minRowsToRender();
15007 if (curRowIndex >= focusableRows.length - pageSize) {
15008 return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); //return last row
15012 return new GridRowColumn(focusableRows[curRowIndex + pageSize], focusableCols[curColIndex]);
15016 UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
15017 var focusableCols = this.getFocusableCols();
15018 var focusableRows = this.getFocusableRows();
15019 var curColIndex = focusableCols.indexOf(curCol);
15020 var curRowIndex = focusableRows.indexOf(curRow);
15022 //could not find column in focusable Columns so set it to 0
15023 if (curColIndex === -1) {
15027 if (curRowIndex === 0) {
15028 return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
15032 return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[curColIndex]);
15036 UiGridCellNav.prototype.getRowColPageUp = function (curRow, curCol) {
15037 var focusableCols = this.getFocusableCols();
15038 var focusableRows = this.getFocusableRows();
15039 var curColIndex = focusableCols.indexOf(curCol);
15040 var curRowIndex = focusableRows.indexOf(curRow);
15042 //could not find column in focusable Columns so set it to 0
15043 if (curColIndex === -1) {
15047 var pageSize = this.bodyContainer.minRowsToRender();
15048 if (curRowIndex - pageSize < 0) {
15049 return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); //return first row
15053 return new GridRowColumn(focusableRows[curRowIndex - pageSize], focusableCols[curColIndex]);
15056 return UiGridCellNav;
15061 * @name ui.grid.cellNav.service:uiGridCellNavService
15063 * @description Services for cell navigation features. If you don't like the key maps we use,
15064 * or the direction cells navigation, override with a service decorator (see angular docs)
15066 module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory', 'GridRowColumn', 'ScrollEvent',
15067 function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav, GridRowColumn, ScrollEvent) {
15071 initializeGrid: function (grid) {
15072 grid.registerColumnBuilder(service.cellNavColumnBuilder);
15077 * @name ui.grid.cellNav:Grid.cellNav
15078 * @description cellNav properties added to grid class
15081 grid.cellNav.lastRowCol = null;
15082 grid.cellNav.focusedCells = [];
15084 service.defaultGridOptions(grid.options);
15088 * @name ui.grid.cellNav.api:PublicApi
15090 * @description Public Api for cellNav feature
15098 * @eventOf ui.grid.cellNav.api:PublicApi
15099 * @description raised when the active cell is changed
15101 * gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
15103 * @param {object} newRowCol new position
15104 * @param {object} oldRowCol old position
15106 navigate: function (newRowCol, oldRowCol) {},
15109 * @name viewPortKeyDown
15110 * @eventOf ui.grid.cellNav.api:PublicApi
15111 * @description is raised when the viewPort receives a keyDown event. Cells never get focus in uiGrid
15112 * due to the difficulties of setting focus on a cell that is not visible in the viewport. Use this
15113 * event whenever you need a keydown event on a cell
15115 * @param {object} event keydown event
15116 * @param {object} rowCol current rowCol position
15118 viewPortKeyDown: function (event, rowCol) {},
15122 * @name viewPortKeyPress
15123 * @eventOf ui.grid.cellNav.api:PublicApi
15124 * @description is raised when the viewPort receives a keyPress event. Cells never get focus in uiGrid
15125 * due to the difficulties of setting focus on a cell that is not visible in the viewport. Use this
15126 * event whenever you need a keypress event on a cell
15128 * @param {object} event keypress event
15129 * @param {object} rowCol current rowCol position
15131 viewPortKeyPress: function (event, rowCol) {}
15138 * @name scrollToFocus
15139 * @methodOf ui.grid.cellNav.api:PublicApi
15140 * @description brings the specified row and column into view, and sets focus
15142 * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus
15143 * @param {object} colDef to make visible and set focus
15144 * @returns {promise} a promise that is resolved after any scrolling is finished
15146 scrollToFocus: function (rowEntity, colDef) {
15147 return service.scrollToFocus(grid, rowEntity, colDef);
15152 * @name getFocusedCell
15153 * @methodOf ui.grid.cellNav.api:PublicApi
15154 * @description returns the current (or last if Grid does not have focus) focused row and column
15155 * <br> value is null if no selection has occurred
15157 getFocusedCell: function () {
15158 return grid.cellNav.lastRowCol;
15163 * @name getCurrentSelection
15164 * @methodOf ui.grid.cellNav.api:PublicApi
15165 * @description returns an array containing the current selection
15166 * <br> array is empty if no selection has occurred
15168 getCurrentSelection: function () {
15169 return grid.cellNav.focusedCells;
15174 * @name rowColSelectIndex
15175 * @methodOf ui.grid.cellNav.api:PublicApi
15176 * @description returns the index in the order in which the GridRowColumn was selected, returns -1 if the GridRowColumn
15178 * @param {object} rowCol the rowCol to evaluate
15180 rowColSelectIndex: function (rowCol) {
15181 //return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
15183 for (var i = 0; i < grid.cellNav.focusedCells.length; i++) {
15184 if (grid.cellNav.focusedCells[i].col.uid === rowCol.col.uid &&
15185 grid.cellNav.focusedCells[i].row.uid === rowCol.row.uid) {
15196 grid.api.registerEventsFromObject(publicApi.events);
15198 grid.api.registerMethodsFromObject(publicApi.methods);
15202 defaultGridOptions: function (gridOptions) {
15205 * @name ui.grid.cellNav.api:GridOptions
15207 * @description GridOptions for cellNav feature, these are available to be
15208 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15213 * @name modifierKeysToMultiSelectCells
15214 * @propertyOf ui.grid.cellNav.api:GridOptions
15215 * @description Enable multiple cell selection only when using the ctrlKey or shiftKey.
15216 * <br/>Defaults to false
15218 gridOptions.modifierKeysToMultiSelectCells = gridOptions.modifierKeysToMultiSelectCells === true;
15222 * @name keyDownOverrides
15223 * @propertyOf ui.grid.cellNav.api:GridOptions
15224 * @description An array of event objects to override on keydown. If an event is overridden, the viewPortKeyDown event will
15225 * be raised with the overridden events, allowing custom keydown behavior.
15226 * <br/>Defaults to []
15228 gridOptions.keyDownOverrides = gridOptions.keyDownOverrides || [];
15234 * @name decorateRenderContainers
15235 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15236 * @description decorates grid renderContainers with cellNav functions
15238 decorateRenderContainers: function (grid) {
15240 var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
15241 var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
15243 if (leftContainer !== null) {
15244 grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
15246 if (rightContainer !== null) {
15247 grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
15250 grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
15255 * @name getDirection
15256 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15257 * @description determines which direction to for a given keyDown event
15258 * @returns {uiGridCellNavConstants.direction} direction
15260 getDirection: function (evt) {
15261 if (evt.keyCode === uiGridConstants.keymap.LEFT ||
15262 (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
15263 return uiGridCellNavConstants.direction.LEFT;
15265 if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
15266 evt.keyCode === uiGridConstants.keymap.TAB) {
15267 return uiGridCellNavConstants.direction.RIGHT;
15270 if (evt.keyCode === uiGridConstants.keymap.UP ||
15271 (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ) {
15272 return uiGridCellNavConstants.direction.UP;
15275 if (evt.keyCode === uiGridConstants.keymap.PG_UP){
15276 return uiGridCellNavConstants.direction.PG_UP;
15279 if (evt.keyCode === uiGridConstants.keymap.DOWN ||
15280 evt.keyCode === uiGridConstants.keymap.ENTER && !(evt.ctrlKey || evt.altKey)) {
15281 return uiGridCellNavConstants.direction.DOWN;
15284 if (evt.keyCode === uiGridConstants.keymap.PG_DOWN){
15285 return uiGridCellNavConstants.direction.PG_DOWN;
15293 * @name cellNavColumnBuilder
15294 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15295 * @description columnBuilder function that adds cell navigation properties to grid column
15296 * @returns {promise} promise that will load any needed templates when resolved
15298 cellNavColumnBuilder: function (colDef, col, gridOptions) {
15303 * @name ui.grid.cellNav.api:ColumnDef
15305 * @description Column Definitions for cellNav feature, these are available to be
15306 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15311 * @name allowCellFocus
15312 * @propertyOf ui.grid.cellNav.api:ColumnDef
15313 * @description Enable focus on a cell within this column.
15314 * <br/>Defaults to true
15316 colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
15318 return $q.all(promises);
15323 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15324 * @name scrollToFocus
15325 * @description Scroll the grid such that the specified
15326 * row and column is in view, and set focus to the cell in that row and column
15327 * @param {Grid} grid the grid you'd like to act upon, usually available
15328 * from gridApi.grid
15329 * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus to
15330 * @param {object} colDef to make visible and set focus to
15331 * @returns {promise} a promise that is resolved after any scrolling is finished
15333 scrollToFocus: function (grid, rowEntity, colDef) {
15334 var gridRow = null, gridCol = null;
15336 if (typeof(rowEntity) !== 'undefined' && rowEntity !== null) {
15337 gridRow = grid.getRow(rowEntity);
15340 if (typeof(colDef) !== 'undefined' && colDef !== null) {
15341 gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
15343 return grid.api.core.scrollToIfNecessary(gridRow, gridCol).then(function () {
15344 var rowCol = { row: gridRow, col: gridCol };
15346 // Broadcast the navigation
15347 if (gridRow !== null && gridCol !== null) {
15348 grid.cellNav.broadcastCellNav(rowCol);
15359 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
15360 * @name getLeftWidth
15361 * @description Get the current drawn width of the columns in the
15362 * grid up to the numbered column, and add an apportionment for the
15363 * column that we're on. So if we are on column 0, we want to scroll
15364 * 0% (i.e. exclude this column from calc). If we're on the last column
15365 * we want to scroll to 100% (i.e. include this column in the calc). So
15366 * we include (thisColIndex / totalNumberCols) % of this column width
15367 * @param {Grid} grid the grid you'd like to act upon, usually available
15368 * from gridApi.grid
15369 * @param {gridCol} upToCol the column to total up to and including
15371 getLeftWidth: function (grid, upToCol) {
15378 var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
15380 // total column widths up-to but not including the passed in column
15381 grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
15382 if ( index < lastIndex ){
15383 width += col.drawnWidth;
15387 // pro-rata the final column based on % of total columns.
15388 var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
15389 width += upToCol.drawnWidth * percentage;
15400 * @name ui.grid.cellNav.directive:uiCellNav
15404 * @description Adds cell navigation features to the grid columns
15407 <example module="app">
15408 <file name="app.js">
15409 var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
15411 app.controller('MainCtrl', ['$scope', function ($scope) {
15413 { name: 'Bob', title: 'CEO' },
15414 { name: 'Frank', title: 'Lowly Developer' }
15417 $scope.columnDefs = [
15423 <file name="index.html">
15424 <div ng-controller="MainCtrl">
15425 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
15430 module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile',
15431 function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile) {
15435 require: '^uiGrid',
15437 controller: function () {},
15438 compile: function () {
15440 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15441 var _scope = $scope;
15443 var grid = uiGridCtrl.grid;
15444 uiGridCellNavService.initializeGrid(grid);
15446 uiGridCtrl.cellNav = {};
15448 //Ensure that the object has all of the methods we expect it to
15449 uiGridCtrl.cellNav.makeRowCol = function (obj) {
15450 if (!(obj instanceof GridRowColumn)) {
15451 obj = new GridRowColumn(obj.row, obj.col);
15456 uiGridCtrl.cellNav.getActiveCell = function () {
15457 var elms = $elm[0].getElementsByClassName('ui-grid-cell-focus');
15458 if (elms.length > 0){
15465 uiGridCtrl.cellNav.broadcastCellNav = grid.cellNav.broadcastCellNav = function (newRowCol, modifierDown, originEvt) {
15466 modifierDown = !(modifierDown === undefined || !modifierDown);
15468 newRowCol = uiGridCtrl.cellNav.makeRowCol(newRowCol);
15470 uiGridCtrl.cellNav.broadcastFocus(newRowCol, modifierDown, originEvt);
15471 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol, modifierDown, originEvt);
15474 uiGridCtrl.cellNav.clearFocus = grid.cellNav.clearFocus = function () {
15475 grid.cellNav.focusedCells = [];
15476 _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT);
15479 uiGridCtrl.cellNav.broadcastFocus = function (rowCol, modifierDown, originEvt) {
15480 modifierDown = !(modifierDown === undefined || !modifierDown);
15482 rowCol = uiGridCtrl.cellNav.makeRowCol(rowCol);
15484 var row = rowCol.row,
15487 var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
15489 if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1) {
15490 var newRowCol = new GridRowColumn(row, col);
15492 if (grid.cellNav.lastRowCol === null || grid.cellNav.lastRowCol.row !== newRowCol.row || grid.cellNav.lastRowCol.col !== newRowCol.col){
15493 grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol, originEvt);
15494 grid.cellNav.lastRowCol = newRowCol;
15496 if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells && modifierDown) {
15497 grid.cellNav.focusedCells.push(rowCol);
15499 grid.cellNav.focusedCells = [rowCol];
15501 } else if (grid.options.modifierKeysToMultiSelectCells && modifierDown &&
15502 rowColSelectIndex >= 0) {
15504 grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15508 uiGridCtrl.cellNav.handleKeyDown = function (evt) {
15509 var direction = uiGridCellNavService.getDirection(evt);
15510 if (direction === null) {
15514 var containerId = 'body';
15515 if (evt.uiGridTargetRenderContainerId) {
15516 containerId = evt.uiGridTargetRenderContainerId;
15519 // Get the last-focused row+col combo
15520 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15522 // Figure out which new row+combo we're navigating to
15523 var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
15524 var focusableCols = uiGridCtrl.grid.renderContainers[containerId].cellNav.getFocusableCols();
15525 var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
15526 // Shift+tab on top-left cell should exit cellnav on render container
15529 direction === uiGridCellNavConstants.direction.LEFT &&
15530 // New col is last col (i.e. wrap around)
15531 rowCol.col === focusableCols[focusableCols.length - 1] &&
15532 // Staying on same row, which means we're at first row
15533 rowCol.row === lastRowCol.row &&
15534 evt.keyCode === uiGridConstants.keymap.TAB &&
15537 grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15538 uiGridCtrl.cellNav.clearFocus();
15541 // Tab on bottom-right cell should exit cellnav on render container
15543 direction === uiGridCellNavConstants.direction.RIGHT &&
15544 // New col is first col (i.e. wrap around)
15545 rowCol.col === focusableCols[0] &&
15546 // Staying on same row, which means we're at first row
15547 rowCol.row === lastRowCol.row &&
15548 evt.keyCode === uiGridConstants.keymap.TAB &&
15551 grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
15552 uiGridCtrl.cellNav.clearFocus();
15556 // Scroll to the new cell, if it's not completely visible within the render container's viewport
15557 grid.scrollToIfNecessary(rowCol.row, rowCol.col).then(function () {
15558 uiGridCtrl.cellNav.broadcastCellNav(rowCol, null, evt);
15562 evt.stopPropagation();
15563 evt.preventDefault();
15569 post: function ($scope, $elm, $attrs, uiGridCtrl) {
15570 var _scope = $scope;
15571 var grid = uiGridCtrl.grid;
15573 function addAriaLiveRegion(){
15574 // Thanks to google docs for the inspiration behind how to do this
15575 // XXX: Why is this entire mess nessasary?
15576 // Because browsers take a lot of coercing to get them to read out live regions
15577 //http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
15578 var ariaNotifierDomElt = '<div ' +
15579 'id="' + grid.id +'-aria-speakable" ' +
15580 'class="ui-grid-a11y-ariascreenreader-speakable ui-grid-offscreen" ' +
15581 'aria-live="assertive" ' +
15583 'aria-atomic="true" ' +
15584 'aria-hidden="false" ' +
15585 'aria-relevant="additions" ' +
15590 var ariaNotifier = $compile(ariaNotifierDomElt)($scope);
15591 $elm.prepend(ariaNotifier);
15592 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown, originEvt) {
15594 * If the cell nav event was because of a focus event then we don't want to
15595 * change the notifier text.
15596 * Reasoning: Voice Over fires a focus events when moving arround the grid.
15597 * If the screen reader is handing the grid nav properly then we don't need to
15598 * use the alert to notify the user of the movement.
15599 * In all other cases we do want a notification event.
15601 if (originEvt && originEvt.type === 'focus'){return;}
15603 function setNotifyText(text){
15604 if (text === ariaNotifier.text()){return;}
15605 ariaNotifier[0].style.clip = 'rect(0px,0px,0px,0px)';
15607 * This is how google docs handles clearing the div. Seems to work better than setting the text of the div to ''
15609 ariaNotifier[0].innerHTML = "";
15610 ariaNotifier[0].style.visibility = 'hidden';
15611 ariaNotifier[0].style.visibility = 'visible';
15613 ariaNotifier[0].style.clip = 'auto';
15615 * The space after the text is something that google docs does.
15617 ariaNotifier[0].appendChild(document.createTextNode(text + " "));
15618 ariaNotifier[0].style.visibility = 'hidden';
15619 ariaNotifier[0].style.visibility = 'visible';
15624 var currentSelection = grid.api.cellNav.getCurrentSelection();
15625 for (var i = 0; i < currentSelection.length; i++) {
15626 values.push(grid.getCellDisplayValue(currentSelection[i].row, currentSelection[i].col));
15628 var cellText = values.toString();
15629 setNotifyText(cellText);
15633 addAriaLiveRegion();
15640 module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', '$compile','uiGridCellNavConstants',
15641 function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, $compile, uiGridCellNavConstants) {
15644 priority: -99999, //this needs to run very last
15645 require: ['^uiGrid', 'uiGridRenderContainer', '?^uiGridCellnav'],
15647 compile: function () {
15649 post: function ($scope, $elm, $attrs, controllers) {
15650 var uiGridCtrl = controllers[0],
15651 renderContainerCtrl = controllers[1],
15652 uiGridCellnavCtrl = controllers[2];
15654 // Skip attaching cell-nav specific logic if the directive is not attached above us
15655 if (!uiGridCtrl.grid.api.cellNav) { return; }
15657 var containerId = renderContainerCtrl.containerId;
15659 var grid = uiGridCtrl.grid;
15661 //run each time a render container is created
15662 uiGridCellNavService.decorateRenderContainers(grid);
15664 // focusser only created for body
15665 if (containerId !== 'body') {
15671 if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells){
15672 $elm.attr('aria-multiselectable', true);
15674 $elm.attr('aria-multiselectable', false);
15677 //add an element with no dimensions that can be used to set focus and capture keystrokes
15678 var focuser = $compile('<div class="ui-grid-focuser" role="region" aria-live="assertive" aria-atomic="false" tabindex="0" aria-controls="' + grid.id +'-aria-speakable '+ grid.id + '-grid-container' +'" aria-owns="' + grid.id + '-grid-container' + '"></div>')($scope);
15679 $elm.append(focuser);
15681 focuser.on('focus', function (evt) {
15682 evt.uiGridTargetRenderContainerId = containerId;
15683 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15684 if (rowCol === null) {
15685 rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(uiGridCellNavConstants.direction.DOWN, null, null);
15686 if (rowCol.row && rowCol.col) {
15687 uiGridCtrl.cellNav.broadcastCellNav(rowCol);
15692 uiGridCellnavCtrl.setAriaActivedescendant = function(id){
15693 $elm.attr('aria-activedescendant', id);
15696 uiGridCellnavCtrl.removeAriaActivedescendant = function(id){
15697 if ($elm.attr('aria-activedescendant') === id){
15698 $elm.attr('aria-activedescendant', '');
15703 uiGridCtrl.focus = function () {
15704 gridUtil.focus.byElement(focuser[0]);
15705 //allow for first time grid focus
15708 var viewPortKeyDownWasRaisedForRowCol = null;
15709 // Bind to keydown events in the render container
15710 focuser.on('keydown', function (evt) {
15711 evt.uiGridTargetRenderContainerId = containerId;
15712 var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15713 var raiseViewPortKeyDown = uiGridCtrl.grid.options.keyDownOverrides.some(function (override) {
15714 return Object.keys(override).every( function (property) {
15715 return override[property] === evt[property];
15718 var result = raiseViewPortKeyDown ? null : uiGridCtrl.cellNav.handleKeyDown(evt);
15719 if (result === null) {
15720 uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol);
15721 viewPortKeyDownWasRaisedForRowCol = rowCol;
15724 //Bind to keypress events in the render container
15725 //keypress events are needed by edit function so the key press
15726 //that initiated an edit is not lost
15727 //must fire the event in a timeout so the editor can
15728 //initialize and subscribe to the event on another event loop
15729 focuser.on('keypress', function (evt) {
15730 if (viewPortKeyDownWasRaisedForRowCol) {
15731 $timeout(function () {
15732 uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
15735 viewPortKeyDownWasRaisedForRowCol = null;
15739 $scope.$on('$destroy', function(){
15740 //Remove all event handlers associated with this focuser.
15750 module.directive('uiGridViewport', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants','$log','$compile',
15751 function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants, $log, $compile) {
15754 priority: -99999, //this needs to run very last
15755 require: ['^uiGrid', '^uiGridRenderContainer', '?^uiGridCellnav'],
15757 compile: function () {
15759 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15761 post: function ($scope, $elm, $attrs, controllers) {
15762 var uiGridCtrl = controllers[0],
15763 renderContainerCtrl = controllers[1];
15765 // Skip attaching cell-nav specific logic if the directive is not attached above us
15766 if (!uiGridCtrl.grid.api.cellNav) { return; }
15768 var containerId = renderContainerCtrl.containerId;
15769 //no need to process for other containers
15770 if (containerId !== 'body') {
15774 var grid = uiGridCtrl.grid;
15776 grid.api.core.on.scrollBegin($scope, function (args) {
15778 // Skip if there's no currently-focused cell
15779 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15780 if (lastRowCol === null) {
15784 //if not in my container, move on
15785 //todo: worry about horiz scroll
15786 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15790 uiGridCtrl.cellNav.clearFocus();
15794 grid.api.core.on.scrollEnd($scope, function (args) {
15795 // Skip if there's no currently-focused cell
15796 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
15797 if (lastRowCol === null) {
15801 //if not in my container, move on
15802 //todo: worry about horiz scroll
15803 if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
15807 uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
15811 grid.api.cellNav.on.navigate($scope, function () {
15812 //focus again because it can be lost
15813 uiGridCtrl.focus();
15824 * @name ui.grid.cellNav.directive:uiGridCell
15827 * @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
15829 module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn',
15830 function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants, GridRowColumn) {
15832 priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
15834 require: ['^uiGrid', '?^uiGridCellnav'],
15836 link: function ($scope, $elm, $attrs, controllers) {
15837 var uiGridCtrl = controllers[0],
15838 uiGridCellnavCtrl = controllers[1];
15839 // Skip attaching cell-nav specific logic if the directive is not attached above us
15840 if (!uiGridCtrl.grid.api.cellNav) { return; }
15842 if (!$scope.col.colDef.allowCellFocus) {
15846 //Convinience local variables
15847 var grid = uiGridCtrl.grid;
15848 $scope.focused = false;
15850 // Make this cell focusable but only with javascript/a mouse click
15851 $elm.attr('tabindex', -1);
15853 // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
15854 $elm.find('div').on('click', function (evt) {
15855 uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), evt.ctrlKey || evt.metaKey, evt);
15857 evt.stopPropagation();
15863 * XXX Hack for screen readers.
15864 * This allows the grid to focus using only the screen reader cursor.
15865 * Since the focus event doesn't include key press information we can't use it
15866 * as our primary source of the event.
15868 $elm.on('mousedown', preventMouseDown);
15870 //turn on and off for edit events
15871 if (uiGridCtrl.grid.api.edit) {
15872 uiGridCtrl.grid.api.edit.on.beginCellEdit($scope, function () {
15873 $elm.off('mousedown', preventMouseDown);
15876 uiGridCtrl.grid.api.edit.on.afterCellEdit($scope, function () {
15877 $elm.on('mousedown', preventMouseDown);
15880 uiGridCtrl.grid.api.edit.on.cancelCellEdit($scope, function () {
15881 $elm.on('mousedown', preventMouseDown);
15885 // In case we created a new row, and we are the new created row by ngRepeat
15886 // then this cell content might have been selected previously
15887 refreshCellFocus();
15889 function preventMouseDown(evt) {
15890 //Prevents the foucus event from firing if the click event is already going to fire.
15891 //If both events fire it will cause bouncing behavior.
15892 evt.preventDefault();
15895 //You can only focus on elements with a tabindex value
15896 $elm.on('focus', function (evt) {
15897 uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), false, evt);
15898 evt.stopPropagation();
15902 // This event is fired for all cells. If the cell matches, then focus is set
15903 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, refreshCellFocus);
15905 // Refresh cell focus when a new row id added to the grid
15906 var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
15907 // Clear the focus if it's set to avoid the wrong cell getting focused during
15908 // a short period of time (from now until $timeout function executed)
15911 $timeout(refreshCellFocus);
15912 }, [uiGridConstants.dataChange.ROW]);
15914 function refreshCellFocus() {
15915 var isFocused = grid.cellNav.focusedCells.some(function (focusedRowCol, index) {
15916 return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
15925 function setFocused() {
15926 if (!$scope.focused){
15927 var div = $elm.find('div');
15928 div.addClass('ui-grid-cell-focus');
15929 $elm.attr('aria-selected', true);
15930 uiGridCellnavCtrl.setAriaActivedescendant($elm.attr('id'));
15931 $scope.focused = true;
15935 function clearFocus() {
15936 if ($scope.focused){
15937 var div = $elm.find('div');
15938 div.removeClass('ui-grid-cell-focus');
15939 $elm.attr('aria-selected', false);
15940 uiGridCellnavCtrl.removeAriaActivedescendant($elm.attr('id'));
15941 $scope.focused = false;
15945 $scope.$on('$destroy', function () {
15948 //.off withouth paramaters removes all handlers
15949 $elm.find('div').off();
15963 * @name ui.grid.edit
15968 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
15970 * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
15974 * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
15975 * user to key data and then tab, arrow, or enter to the cells beside or below.
15977 * <div doc-module-components="ui.grid.edit"></div>
15980 var module = angular.module('ui.grid.edit', ['ui.grid']);
15984 * @name ui.grid.edit.constant:uiGridEditConstants
15986 * @description constants available in edit module
15988 module.constant('uiGridEditConstants', {
15989 EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
15990 //must be lowercase because template bulder converts to lower
15991 EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
15993 BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
15994 END_CELL_EDIT: 'uiGridEventEndCellEdit',
15995 CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
16001 * @name ui.grid.edit.service:uiGridEditService
16003 * @description Services for editing features
16005 module.service('uiGridEditService', ['$q', 'uiGridConstants', 'gridUtil',
16006 function ($q, uiGridConstants, gridUtil) {
16010 initializeGrid: function (grid) {
16012 service.defaultGridOptions(grid.options);
16014 grid.registerColumnBuilder(service.editColumnBuilder);
16019 * @name ui.grid.edit.api:PublicApi
16021 * @description Public Api for edit feature
16028 * @name afterCellEdit
16029 * @eventOf ui.grid.edit.api:PublicApi
16030 * @description raised when cell editing is complete
16032 * gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
16034 * @param {object} rowEntity the options.data element that was edited
16035 * @param {object} colDef the column that was edited
16036 * @param {object} newValue new value
16037 * @param {object} oldValue old value
16039 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
16043 * @name beginCellEdit
16044 * @eventOf ui.grid.edit.api:PublicApi
16045 * @description raised when cell editing starts on a cell
16047 * gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
16049 * @param {object} rowEntity the options.data element that was edited
16050 * @param {object} colDef the column that was edited
16051 * @param {object} triggerEvent the event that triggered the edit. Useful to prevent losing keystrokes on some
16054 beginCellEdit: function (rowEntity, colDef, triggerEvent) {
16058 * @name cancelCellEdit
16059 * @eventOf ui.grid.edit.api:PublicApi
16060 * @description raised when cell editing is cancelled on a cell
16062 * gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
16064 * @param {object} rowEntity the options.data element that was edited
16065 * @param {object} colDef the column that was edited
16067 cancelCellEdit: function (rowEntity, colDef) {
16076 grid.api.registerEventsFromObject(publicApi.events);
16077 //grid.api.registerMethodsFromObject(publicApi.methods);
16081 defaultGridOptions: function (gridOptions) {
16085 * @name ui.grid.edit.api:GridOptions
16087 * @description Options for configuring the edit feature, these are available to be
16088 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16093 * @name enableCellEdit
16094 * @propertyOf ui.grid.edit.api:GridOptions
16095 * @description If defined, sets the default value for the editable flag on each individual colDefs
16096 * if their individual enableCellEdit configuration is not defined. Defaults to undefined.
16101 * @name cellEditableCondition
16102 * @propertyOf ui.grid.edit.api:GridOptions
16103 * @description If specified, either a value or function to be used by all columns before editing.
16104 * If falsy, then editing of cell is not allowed.
16107 * function($scope){
16108 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
16113 gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
16117 * @name editableCellTemplate
16118 * @propertyOf ui.grid.edit.api:GridOptions
16119 * @description If specified, cellTemplate to use as the editor for all columns.
16120 * <br/> defaults to 'ui-grid/cellTextEditor'
16125 * @name enableCellEditOnFocus
16126 * @propertyOf ui.grid.edit.api:GridOptions
16127 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
16128 * <br/>_requires cellNav feature and the edit feature to be enabled_
16130 //enableCellEditOnFocus can only be used if cellnav module is used
16131 gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
16136 * @name editColumnBuilder
16137 * @methodOf ui.grid.edit.service:uiGridEditService
16138 * @description columnBuilder function that adds edit properties to grid column
16139 * @returns {promise} promise that will load any needed templates when resolved
16141 editColumnBuilder: function (colDef, col, gridOptions) {
16147 * @name ui.grid.edit.api:ColumnDef
16149 * @description Column Definition for edit feature, these are available to be
16150 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
16155 * @name enableCellEdit
16156 * @propertyOf ui.grid.edit.api:ColumnDef
16157 * @description enable editing on column
16159 colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
16160 (colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
16164 * @name cellEditableCondition
16165 * @propertyOf ui.grid.edit.api:ColumnDef
16166 * @description If specified, either a value or function evaluated before editing cell. If falsy, then editing of cell is not allowed.
16169 * function($scope){
16170 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
16175 colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition : colDef.cellEditableCondition;
16179 * @name editableCellTemplate
16180 * @propertyOf ui.grid.edit.api:ColumnDef
16181 * @description cell template to be used when editing this column. Can be Url or text template
16182 * <br/>Defaults to gridOptions.editableCellTemplate
16184 if (colDef.enableCellEdit) {
16185 colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
16187 promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
16189 function (template) {
16190 col.editableCellTemplate = template;
16193 // Todo handle response error here?
16194 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
16200 * @name enableCellEditOnFocus
16201 * @propertyOf ui.grid.edit.api:ColumnDef
16202 * @requires ui.grid.cellNav
16203 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
16204 * <br>_requires both the cellNav feature and the edit feature to be enabled_
16206 //enableCellEditOnFocus can only be used if cellnav module is used
16207 colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
16212 * @name editModelField
16213 * @propertyOf ui.grid.edit.api:ColumnDef
16214 * @description a bindable string value that is used when binding to edit controls instead of colDef.field
16215 * <br/> example: You have a complex property on and object like state:{abbrev:'MS',name:'Mississippi'}. The
16216 * grid should display state.name in the cell and sort/filter based on the state.name property but the editor
16217 * requires the full state object.
16218 * <br/>colDef.field = 'state.name'
16219 * <br/>colDef.editModelField = 'state'
16221 //colDef.editModelField
16223 return $q.all(promises);
16228 * @name isStartEditKey
16229 * @methodOf ui.grid.edit.service:uiGridEditService
16230 * @description Determines if a keypress should start editing. Decorate this service to override with your
16231 * own key events. See service decorator in angular docs.
16232 * @param {Event} evt keydown event
16233 * @returns {boolean} true if an edit should start
16235 isStartEditKey: function (evt) {
16237 evt.keyCode === uiGridConstants.keymap.ESC ||
16238 evt.keyCode === uiGridConstants.keymap.SHIFT ||
16239 evt.keyCode === uiGridConstants.keymap.CTRL ||
16240 evt.keyCode === uiGridConstants.keymap.ALT ||
16241 evt.keyCode === uiGridConstants.keymap.WIN ||
16242 evt.keyCode === uiGridConstants.keymap.CAPSLOCK ||
16244 evt.keyCode === uiGridConstants.keymap.LEFT ||
16245 (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
16247 evt.keyCode === uiGridConstants.keymap.RIGHT ||
16248 evt.keyCode === uiGridConstants.keymap.TAB ||
16250 evt.keyCode === uiGridConstants.keymap.UP ||
16251 (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
16253 evt.keyCode === uiGridConstants.keymap.DOWN ||
16254 evt.keyCode === uiGridConstants.keymap.ENTER) {
16270 * @name ui.grid.edit.directive:uiGridEdit
16274 * @description Adds editing features to the ui-grid directive.
16277 <example module="app">
16278 <file name="app.js">
16279 var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
16281 app.controller('MainCtrl', ['$scope', function ($scope) {
16283 { name: 'Bob', title: 'CEO' },
16284 { name: 'Frank', title: 'Lowly Developer' }
16287 $scope.columnDefs = [
16288 {name: 'name', enableCellEdit: true},
16289 {name: 'title', enableCellEdit: true}
16293 <file name="index.html">
16294 <div ng-controller="MainCtrl">
16295 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
16300 module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
16304 require: '^uiGrid',
16306 compile: function () {
16308 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16309 uiGridEditService.initializeGrid(uiGridCtrl.grid);
16311 post: function ($scope, $elm, $attrs, uiGridCtrl) {
16320 * @name ui.grid.edit.directive:uiGridRenderContainer
16324 * @description Adds keydown listeners to renderContainer element so we can capture when to begin edits
16327 module.directive('uiGridViewport', [ 'uiGridEditConstants',
16328 function ( uiGridEditConstants) {
16331 priority: -99998, //run before cellNav
16332 require: ['^uiGrid', '^uiGridRenderContainer'],
16334 compile: function () {
16336 post: function ($scope, $elm, $attrs, controllers) {
16337 var uiGridCtrl = controllers[0];
16339 // Skip attaching if edit and cellNav is not enabled
16340 if (!uiGridCtrl.grid.api.edit || !uiGridCtrl.grid.api.cellNav) { return; }
16342 var containerId = controllers[1].containerId;
16343 //no need to process for other containers
16344 if (containerId !== 'body') {
16348 //refocus on the grid
16349 $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
16350 uiGridCtrl.focus();
16352 $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
16353 uiGridCtrl.focus();
16364 * @name ui.grid.edit.directive:uiGridCell
16368 * @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
16371 * Binds edit start events to the uiGridCell element. When the events fire, the gridCell element is appended
16372 * with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
16374 * The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
16375 * and do the initial steps needed to edit the cell (setfocus on input element, etc).
16377 * When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
16378 * it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
16380 * If editableCellTemplate recognizes that the editing has been cancelled (esc key)
16381 * it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event. The original value
16382 * will be set back on the model by the uiGridCell directive.
16384 * Events that invoke editing:
16386 * - F2 keydown (when using cell selection)
16388 * Events that end editing:
16389 * - Dependent on the specific editableCellTemplate
16390 * - Standards should be blur and enter keydown
16392 * Events that cancel editing:
16393 * - Dependent on the specific editableCellTemplate
16394 * - Standards should be Esc keydown
16396 * Grid Events that end editing:
16397 * - uiGridConstants.events.GRID_SCROLL
16403 * @name ui.grid.edit.api:GridRow
16405 * @description GridRow options for edit feature, these are available to be
16406 * set internally only, by other features
16411 * @name enableCellEdit
16412 * @propertyOf ui.grid.edit.api:GridRow
16413 * @description enable editing on row, grouping for example might disable editing on group header rows
16416 module.directive('uiGridCell',
16417 ['$compile', '$injector', '$timeout', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService', '$rootScope', '$q',
16418 function ($compile, $injector, $timeout, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService, $rootScope, $q) {
16419 var touchstartTimeout = 500;
16420 if ($injector.has('uiGridCellNavService')) {
16421 var uiGridCellNavService = $injector.get('uiGridCellNavService');
16425 priority: -100, // run after default uiGridCell directive
16428 require: '?^uiGrid',
16429 link: function ($scope, $elm, $attrs, uiGridCtrl) {
16432 var inEdit = false;
16434 var cancelTouchstartTimeout;
16438 if (!$scope.col.colDef.enableCellEdit) {
16442 var cellNavNavigateDereg = function() {};
16443 var viewPortKeyDownDereg = function() {};
16446 var setEditable = function() {
16447 if ($scope.col.colDef.enableCellEdit && $scope.row.enableCellEdit !== false) {
16448 if (!$scope.beginEditEventsWired) { //prevent multiple attachments
16449 registerBeginEditEvents();
16452 if ($scope.beginEditEventsWired) {
16453 cancelBeginEditEvents();
16460 var rowWatchDereg = $scope.$watch('row', function (n, o) {
16467 $scope.$on('$destroy', function destroyEvents() {
16469 // unbind all jquery events in order to avoid memory leaks
16473 function registerBeginEditEvents() {
16474 $elm.on('dblclick', beginEdit);
16476 // Add touchstart handling. If the users starts a touch and it doesn't end after X milliseconds, then start the edit
16477 $elm.on('touchstart', touchStart);
16479 if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16481 viewPortKeyDownDereg = uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
16482 if (rowCol === null) {
16486 if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
16487 //important to do this before scrollToIfNecessary
16488 beginEditKeyDown(evt);
16492 cellNavNavigateDereg = uiGridCtrl.grid.api.cellNav.on.navigate($scope, function (newRowCol, oldRowCol, evt) {
16493 if ($scope.col.colDef.enableCellEditOnFocus) {
16494 // Don't begin edit if the cell hasn't changed
16495 if ((!oldRowCol || newRowCol.row !== oldRowCol.row || newRowCol.col !== oldRowCol.col) &&
16496 newRowCol.row === $scope.row && newRowCol.col === $scope.col) {
16497 $timeout(function () {
16505 $scope.beginEditEventsWired = true;
16509 function touchStart(event) {
16510 // jQuery masks events
16511 if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
16512 event = event.originalEvent;
16515 // Bind touchend handler
16516 $elm.on('touchend', touchEnd);
16519 cancelTouchstartTimeout = $timeout(function() { }, touchstartTimeout);
16521 // Timeout's done! Start the edit
16522 cancelTouchstartTimeout.then(function () {
16523 // Use setTimeout to start the edit because beginEdit expects to be outside of $digest
16524 setTimeout(beginEdit, 0);
16526 // Undbind the touchend handler, we don't need it anymore
16527 $elm.off('touchend', touchEnd);
16531 // Cancel any touchstart timeout
16532 function touchEnd(event) {
16533 $timeout.cancel(cancelTouchstartTimeout);
16534 $elm.off('touchend', touchEnd);
16537 function cancelBeginEditEvents() {
16538 $elm.off('dblclick', beginEdit);
16539 $elm.off('keydown', beginEditKeyDown);
16540 $elm.off('touchstart', touchStart);
16541 cellNavNavigateDereg();
16542 viewPortKeyDownDereg();
16543 $scope.beginEditEventsWired = false;
16546 function beginEditKeyDown(evt) {
16547 if (uiGridEditService.isStartEditKey(evt)) {
16552 function shouldEdit(col, row) {
16553 return !row.isSaving &&
16554 ( angular.isFunction(col.colDef.cellEditableCondition) ?
16555 col.colDef.cellEditableCondition($scope) :
16556 col.colDef.cellEditableCondition );
16560 function beginEdit(triggerEvent) {
16561 //we need to scroll the cell into focus before invoking the editor
16562 $scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
16563 .then(function () {
16564 beginEditAfterScroll(triggerEvent);
16570 * @name editDropdownOptionsArray
16571 * @propertyOf ui.grid.edit.api:ColumnDef
16572 * @description an array of values in the format
16573 * [ {id: xxx, value: xxx} ], which is populated
16574 * into the edit dropdown
16579 * @name editDropdownIdLabel
16580 * @propertyOf ui.grid.edit.api:ColumnDef
16581 * @description the label for the "id" field
16582 * in the editDropdownOptionsArray. Defaults
16586 * $scope.gridOptions = {
16588 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16589 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16590 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16597 * @name editDropdownRowEntityOptionsArrayPath
16598 * @propertyOf ui.grid.edit.api:ColumnDef
16599 * @description a path to a property on row.entity containing an
16600 * array of values in the format
16601 * [ {id: xxx, value: xxx} ], which will be used to populate
16602 * the edit dropdown. This can be used when the dropdown values are dependent on
16603 * the backing row entity.
16604 * If this property is set then editDropdownOptionsArray will be ignored.
16607 * $scope.gridOptions = {
16609 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16610 * editDropdownRowEntityOptionsArrayPath: 'foo.bars[0].baz',
16611 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16618 * @name editDropdownOptionsFunction
16619 * @methodOf ui.grid.edit.api:ColumnDef
16620 * @description a function returning an array of values in the format
16621 * [ {id: xxx, value: xxx} ], which will be used to populate
16622 * the edit dropdown. This can be used when the dropdown values are dependent on
16623 * the backing row entity with some kind of algorithm.
16624 * If this property is set then both editDropdownOptionsArray and
16625 * editDropdownRowEntityOptionsArrayPath will be ignored.
16626 * @param {object} rowEntity the options.data element that the returned array refers to
16627 * @param {object} colDef the column that implements this dropdown
16628 * @returns {object} an array of values in the format
16629 * [ {id: xxx, value: xxx} ] used to populate the edit dropdown
16632 * $scope.gridOptions = {
16634 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16635 * editDropdownOptionsFunction: function(rowEntity, colDef) {
16636 * if (rowEntity.foo === 'bar') {
16637 * return [{id: 'bar1', value: 'BAR 1'},
16638 * {id: 'bar2', value: 'BAR 2'},
16639 * {id: 'bar3', value: 'BAR 3'}];
16641 * return [{id: 'foo1', value: 'FOO 1'},
16642 * {id: 'foo2', value: 'FOO 2'}];
16645 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16652 * @name editDropdownValueLabel
16653 * @propertyOf ui.grid.edit.api:ColumnDef
16654 * @description the label for the "value" field
16655 * in the editDropdownOptionsArray. Defaults
16659 * $scope.gridOptions = {
16661 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16662 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16663 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
16670 * @name editDropdownFilter
16671 * @propertyOf ui.grid.edit.api:ColumnDef
16672 * @description A filter that you would like to apply to the values in the options list
16673 * of the dropdown. For example if you were using angular-translate you might set this
16677 * $scope.gridOptions = {
16679 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
16680 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
16681 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
16686 function beginEditAfterScroll(triggerEvent) {
16687 // If we are already editing, then just skip this so we don't try editing twice...
16692 if (!shouldEdit($scope.col, $scope.row)) {
16696 var modelField = $scope.row.getQualifiedColField($scope.col);
16697 if ($scope.col.colDef.editModelField) {
16698 modelField = gridUtil.preEval('row.entity.' + $scope.col.colDef.editModelField);
16701 cellModel = $parse(modelField);
16703 //get original value from the cell
16704 origCellValue = cellModel($scope);
16706 html = $scope.col.editableCellTemplate;
16707 html = html.replace(uiGridConstants.MODEL_COL_FIELD, modelField);
16708 html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
16710 var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
16711 html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
16713 var inputType = 'text';
16714 switch ($scope.col.colDef.type){
16716 inputType = 'checkbox';
16719 inputType = 'number';
16722 inputType = 'date';
16725 html = html.replace('INPUT_TYPE', inputType);
16727 // In order to fill dropdown options we use:
16728 // - A function/promise or
16729 // - An array inside of row entity if no function exists or
16730 // - A single array for the whole column if none of the previous exists.
16731 var editDropdownOptionsFunction = $scope.col.colDef.editDropdownOptionsFunction;
16732 if (editDropdownOptionsFunction) {
16733 $q.when(editDropdownOptionsFunction($scope.row.entity, $scope.col.colDef))
16734 .then(function(result) {
16735 $scope.editDropdownOptionsArray = result;
16738 var editDropdownRowEntityOptionsArrayPath = $scope.col.colDef.editDropdownRowEntityOptionsArrayPath;
16739 if (editDropdownRowEntityOptionsArrayPath) {
16740 $scope.editDropdownOptionsArray = resolveObjectFromPath($scope.row.entity, editDropdownRowEntityOptionsArrayPath);
16743 $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
16746 $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
16747 $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
16750 var createEditor = function(){
16752 cancelBeginEditEvents();
16753 var cellElement = angular.element(html);
16754 $elm.append(cellElement);
16755 editCellScope = $scope.$new();
16756 $compile(cellElement)(editCellScope);
16757 var gridCellContentsEl = angular.element($elm.children()[0]);
16758 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
16760 if (!$rootScope.$$phase) {
16761 $scope.$apply(createEditor);
16766 //stop editing when grid is scrolled
16767 var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
16768 if ($scope.grid.disableScrolling) {
16772 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16773 deregOnGridScroll();
16774 deregOnEndCellEdit();
16775 deregOnCancelCellEdit();
16779 var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
16781 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
16782 deregOnEndCellEdit();
16783 deregOnGridScroll();
16784 deregOnCancelCellEdit();
16788 var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
16790 deregOnCancelCellEdit();
16791 deregOnGridScroll();
16792 deregOnEndCellEdit();
16795 $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT, triggerEvent);
16796 $timeout(function () {
16797 //execute in a timeout to give any complex editor templates a cycle to completely render
16798 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
16802 function endEdit() {
16803 $scope.grid.disableScrolling = false;
16808 //sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
16809 //back to grid here. The focus call needs to be before the $destroy and removal of the control,
16810 //otherwise ng-model-options of UpdateOn: 'blur' will not work.
16811 if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16812 uiGridCtrl.focus();
16815 var gridCellContentsEl = angular.element($elm.children()[0]);
16816 //remove edit element
16817 editCellScope.$destroy();
16818 var children = $elm.children();
16819 for (var i = 1; i < children.length; i++) {
16820 angular.element(children[i]).remove();
16822 gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
16824 registerBeginEditEvents();
16825 $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.EDIT );
16828 function cancelEdit() {
16829 $scope.grid.disableScrolling = false;
16833 cellModel.assign($scope, origCellValue);
16836 $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
16840 // resolves a string path against the given object
16841 // shamelessly borrowed from
16842 // http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key
16843 function resolveObjectFromPath(object, path) {
16844 path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
16845 path = path.replace(/^\./, ''); // strip a leading dot
16846 var a = path.split('.');
16850 object = object[n];
16864 * @name ui.grid.edit.directive:uiGridEditor
16868 * @description input editor directive for editable fields.
16869 * Provides EndEdit and CancelEdit events
16871 * Events that end editing:
16872 * blur and enter keydown
16874 * Events that cancel editing:
16878 module.directive('uiGridEditor',
16879 ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout', 'uiGridEditService',
16880 function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
16883 require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
16884 compile: function () {
16886 pre: function ($scope, $elm, $attrs) {
16889 post: function ($scope, $elm, $attrs, controllers) {
16890 var uiGridCtrl, renderContainerCtrl, ngModel;
16891 if (controllers[0]) { uiGridCtrl = controllers[0]; }
16892 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
16893 if (controllers[2]) { ngModel = controllers[2]; }
16895 //set focus at start of edit
16896 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt,triggerEvent) {
16897 $timeout(function () {
16899 //only select text if it is not being replaced below in the cellNav viewPortKeyPress
16900 if ($elm[0].select && ($scope.col.colDef.enableCellEditOnFocus || !(uiGridCtrl && uiGridCtrl.grid.api.cellNav))) {
16904 //some browsers (Chrome) stupidly, imo, support the w3 standard that number, email, ...
16905 //fields should not allow setSelectionRange. We ignore the error for those browsers
16906 //https://www.w3.org/Bugs/Public/show_bug.cgi?id=24796
16908 $elm[0].setSelectionRange($elm[0].value.length, $elm[0].value.length);
16916 //set the keystroke that started the edit event
16917 //we must do this because the BeginEdit is done in a different event loop than the intitial
16919 //fire this event for the keypress that is received
16920 if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16921 var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
16922 if (uiGridEditService.isStartEditKey(evt)) {
16923 ngModel.$setViewValue(String.fromCharCode( typeof evt.which === 'number' ? evt.which : evt.keyCode), evt);
16926 viewPortKeyDownUnregister();
16930 // macOS will blur the checkbox when clicked in Safari and Firefox,
16931 // to get around this, we disable the blur handler on mousedown,
16932 // and then focus the checkbox and re-enable the blur handler after $timeout
16933 $elm.on('mousedown', function(evt) {
16934 if ($elm[0].type === 'checkbox') {
16935 $elm.off('blur', $scope.stopEdit);
16936 $timeout(function() {
16938 $elm.on('blur', $scope.stopEdit);
16943 $elm.on('blur', $scope.stopEdit);
16947 $scope.deepEdit = false;
16949 $scope.stopEdit = function (evt) {
16950 if ($scope.inputForm && !$scope.inputForm.$valid) {
16951 evt.stopPropagation();
16952 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16955 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
16957 $scope.deepEdit = false;
16961 $elm.on('click', function (evt) {
16962 if ($elm[0].type !== 'checkbox') {
16963 $scope.deepEdit = true;
16964 $timeout(function () {
16965 $scope.grid.disableScrolling = true;
16970 $elm.on('keydown', function (evt) {
16971 switch (evt.keyCode) {
16972 case uiGridConstants.keymap.ESC:
16973 evt.stopPropagation();
16974 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
16978 if ($scope.deepEdit &&
16979 (evt.keyCode === uiGridConstants.keymap.LEFT ||
16980 evt.keyCode === uiGridConstants.keymap.RIGHT ||
16981 evt.keyCode === uiGridConstants.keymap.UP ||
16982 evt.keyCode === uiGridConstants.keymap.DOWN)) {
16983 evt.stopPropagation();
16985 // Pass the keydown event off to the cellNav service, if it exists
16986 else if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
16987 evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
16988 if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
16989 $scope.stopEdit(evt);
16993 //handle enter and tab for editing not using cellNav
16994 switch (evt.keyCode) {
16995 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
16996 case uiGridConstants.keymap.TAB:
16997 evt.stopPropagation();
16998 evt.preventDefault();
16999 $scope.stopEdit(evt);
17007 $scope.$on('$destroy', function unbindEvents() {
17008 // unbind all jquery events in order to avoid memory leaks
17019 * @name ui.grid.edit.directive:input
17023 * @description directive to provide binding between input[date] value and ng-model for angular 1.2
17024 * It is similar to input[date] directive of angular 1.3
17026 * Supported date format for input is 'yyyy-MM-dd'
17027 * The directive will set the $valid property of input element and the enclosing form to false if
17028 * model is invalid date or value of input is entered wrong.
17031 module.directive('uiGridEditor', ['$filter', function ($filter) {
17032 function parseDateString(dateString) {
17033 if (typeof(dateString) === 'undefined' || dateString === '') {
17036 var parts = dateString.split('-');
17037 if (parts.length !== 3) {
17040 var year = parseInt(parts[0], 10);
17041 var month = parseInt(parts[1], 10);
17042 var day = parseInt(parts[2], 10);
17044 if (month < 1 || year < 1 || day < 1) {
17047 return new Date(year, (month - 1), day);
17050 priority: -100, // run after default uiGridEditor directive
17051 require: '?ngModel',
17052 link: function (scope, element, attrs, ngModel) {
17054 if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
17056 ngModel.$formatters.push(function (modelValue) {
17057 ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
17058 return $filter('date')(modelValue, 'yyyy-MM-dd');
17061 ngModel.$parsers.push(function (viewValue) {
17062 if (viewValue && viewValue.length > 0) {
17063 var dateValue = parseDateString(viewValue);
17064 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
17068 ngModel.$setValidity(null, true);
17080 * @name ui.grid.edit.directive:uiGridEditDropdown
17084 * @description dropdown editor for editable fields.
17085 * Provides EndEdit and CancelEdit events
17087 * Events that end editing:
17088 * blur and enter keydown, and any left/right nav
17090 * Events that cancel editing:
17094 module.directive('uiGridEditDropdown',
17095 ['uiGridConstants', 'uiGridEditConstants', '$timeout',
17096 function (uiGridConstants, uiGridEditConstants, $timeout) {
17098 require: ['?^uiGrid', '?^uiGridRenderContainer'],
17100 compile: function () {
17102 pre: function ($scope, $elm, $attrs) {
17105 post: function ($scope, $elm, $attrs, controllers) {
17106 var uiGridCtrl = controllers[0];
17107 var renderContainerCtrl = controllers[1];
17109 //set focus at start of edit
17110 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
17111 $timeout(function(){
17115 $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
17116 $elm.on('blur', function (evt) {
17117 $scope.stopEdit(evt);
17122 $scope.stopEdit = function (evt) {
17123 // no need to validate a dropdown - invalid values shouldn't be
17124 // available in the list
17125 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
17128 $elm.on('keydown', function (evt) {
17129 switch (evt.keyCode) {
17130 case uiGridConstants.keymap.ESC:
17131 evt.stopPropagation();
17132 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
17135 if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
17136 evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
17137 if (uiGridCtrl.cellNav.handleKeyDown(evt) !== null) {
17138 $scope.stopEdit(evt);
17142 //handle enter and tab for editing not using cellNav
17143 switch (evt.keyCode) {
17144 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
17145 case uiGridConstants.keymap.TAB:
17146 evt.stopPropagation();
17147 evt.preventDefault();
17148 $scope.stopEdit(evt);
17155 $scope.$on('$destroy', function unbindEvents() {
17156 // unbind jquery events to prevent memory leaks
17167 * @name ui.grid.edit.directive:uiGridEditFileChooser
17171 * @description input editor directive for editable fields.
17172 * Provides EndEdit and CancelEdit events
17174 * Events that end editing:
17175 * blur and enter keydown
17177 * Events that cancel editing:
17181 module.directive('uiGridEditFileChooser',
17182 ['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout',
17183 function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout) {
17186 require: ['?^uiGrid', '?^uiGridRenderContainer'],
17187 compile: function () {
17189 pre: function ($scope, $elm, $attrs) {
17192 post: function ($scope, $elm, $attrs, controllers) {
17193 var uiGridCtrl, renderContainerCtrl;
17194 if (controllers[0]) { uiGridCtrl = controllers[0]; }
17195 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
17196 var grid = uiGridCtrl.grid;
17198 var handleFileSelect = function( event ){
17199 var target = event.srcElement || event.target;
17201 if (target && target.files && target.files.length > 0) {
17204 * @name editFileChooserCallback
17205 * @propertyOf ui.grid.edit.api:ColumnDef
17206 * @description A function that should be called when any files have been chosen
17207 * by the user. You should use this to process the files appropriately for your
17210 * It passes the gridCol, the gridRow (from which you can get gridRow.entity),
17211 * and the files. The files are in the format as returned from the file chooser,
17212 * an array of files, with each having useful information such as:
17213 * - `files[0].lastModifiedDate`
17214 * - `files[0].name`
17215 * - `files[0].size` (appears to be in bytes)
17216 * - `files[0].type` (MIME type by the looks)
17218 * Typically you would do something with these files - most commonly you would
17219 * use the filename or read the file itself in. The example function does both.
17223 * editFileChooserCallBack: function(gridRow, gridCol, files ){
17224 * // ignore all but the first file, it can only choose one anyway
17225 * // set the filename into this column
17226 * gridRow.entity.filename = file[0].name;
17228 * // read the file and set it into a hidden column, which we may do stuff with later
17229 * var setFile = function(fileContent){
17230 * gridRow.entity.file = fileContent.currentTarget.result;
17232 * var reader = new FileReader();
17233 * reader.onload = setFile;
17234 * reader.readAsText( files[0] );
17238 if ( typeof($scope.col.colDef.editFileChooserCallback) === 'function' ) {
17239 $scope.col.colDef.editFileChooserCallback($scope.row, $scope.col, target.files);
17241 gridUtil.logError('You need to set colDef.editFileChooserCallback to use the file chooser');
17244 target.form.reset();
17245 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
17247 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
17251 $elm[0].addEventListener('change', handleFileSelect, false);
17253 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
17257 $elm.on('blur', function (evt) {
17258 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
17262 $scope.$on('$destroy', function unbindEvents() {
17263 // unbind jquery events to prevent memory leaks
17265 $elm[0].removeEventListener('change', handleFileSelect, false);
17279 * @name ui.grid.emptyBaseLayer
17282 * # ui.grid.emptyBaseLayer
17284 * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
17286 * This module provides the ability to have the background of the ui-grid be empty rows, this would be displayed in the case were
17287 * the grid height is greater then the amount of rows displayed.
17289 * <div doc-module-components="ui.grid.emptyBaseLayer"></div>
17291 var module = angular.module('ui.grid.emptyBaseLayer', ['ui.grid']);
17296 * @name ui.grid.emptyBaseLayer.service:uiGridBaseLayerService
17298 * @description Services for the empty base layer grid
17300 module.service('uiGridBaseLayerService', ['gridUtil', '$compile', function (gridUtil, $compile) {
17302 initializeGrid: function (grid, disableEmptyBaseLayer) {
17306 * @name ui.grid.emptyBaseLayer.api:GridOptions
17308 * @description GridOptions for emptyBaseLayer feature, these are available to be
17309 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17317 * @name enableEmptyGridBaseLayer
17318 * @propertyOf ui.grid.emptyBaseLayer.api:GridOptions
17319 * @description Enable empty base layer, which shows empty rows as background on the entire grid
17320 * <br/>Defaults to true, if the directive is used.
17321 * <br/>Set to false either by setting this attribute or passing false to the directive.
17323 //default option to true unless it was explicitly set to false
17324 if (grid.options.enableEmptyGridBaseLayer !== false) {
17325 grid.options.enableEmptyGridBaseLayer = !disableEmptyBaseLayer;
17329 setNumberOfEmptyRows: function(viewportHeight, grid) {
17330 var rowHeight = grid.options.rowHeight,
17331 rows = Math.ceil(viewportHeight / rowHeight);
17333 grid.baseLayer.emptyRows = [];
17334 for (var i = 0; i < rows; i++) {
17335 grid.baseLayer.emptyRows.push({});
17345 * @name ui.grid.emptyBaseLayer.directive:uiGridEmptyBaseLayer
17346 * @description Shows empty rows in the background of the ui-grid, these span
17347 * the full height of the ui-grid, so that there won't be blank space below the shown rows.
17350 * <div ui-grid="gridOptions" class="grid" ui-grid-empty-base-layer></div>
17352 * Or you can enable/disable it dynamically by passing in true or false. It doesn't
17353 * the value, so it would only be set on initial render.
17355 * <div ui-grid="gridOptions" class="grid" ui-grid-empty-base-layer="false"></div>
17358 module.directive('uiGridEmptyBaseLayer', ['gridUtil', 'uiGridBaseLayerService',
17360 function (gridUtil, uiGridBaseLayerService, $parse) {
17362 require: '^uiGrid',
17364 compile: function ($elm, $attrs) {
17366 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17367 var disableEmptyBaseLayer = $parse($attrs.uiGridEmptyBaseLayer)($scope) === false;
17368 uiGridBaseLayerService.initializeGrid(uiGridCtrl.grid, disableEmptyBaseLayer);
17370 post: function ($scope, $elm, $attrs, uiGridCtrl) {
17371 if (!uiGridCtrl.grid.options.enableEmptyGridBaseLayer) {
17375 var renderBodyContainer = uiGridCtrl.grid.renderContainers.body,
17376 viewportHeight = renderBodyContainer.getViewportHeight();
17378 function heightHasChanged() {
17379 var newViewPortHeight = renderBodyContainer.getViewportHeight();
17381 if (newViewPortHeight !== viewportHeight) {
17382 viewportHeight = newViewPortHeight;
17388 function getEmptyBaseLayerCss(viewportHeight) {
17389 // Set ui-grid-empty-base-layer height
17390 return '.grid' + uiGridCtrl.grid.id +
17391 ' .ui-grid-render-container ' +
17392 '.ui-grid-empty-base-layer-container.ui-grid-canvas ' +
17393 '{ height: ' + viewportHeight + 'px; }';
17396 uiGridCtrl.grid.registerStyleComputation({
17398 if (heightHasChanged()) {
17399 uiGridBaseLayerService.setNumberOfEmptyRows(viewportHeight, uiGridCtrl.grid);
17401 return getEmptyBaseLayerCss(viewportHeight);
17412 * @name ui.grid.emptyBaseLayer.directive:uiGridViewport
17413 * @description stacks on the uiGridViewport directive to append the empty grid base layer html elements to the
17414 * default gridRow template
17416 module.directive('uiGridViewport',
17417 ['$compile', 'gridUtil', '$templateCache',
17418 function ($compile, gridUtil, $templateCache) {
17422 compile: function ($elm, $attrs) {
17423 var emptyBaseLayerContainer = $templateCache.get('ui-grid/emptyBaseLayerContainer');
17424 $elm.prepend(emptyBaseLayerContainer);
17426 pre: function ($scope, $elm, $attrs, controllers) {
17428 post: function ($scope, $elm, $attrs, controllers) {
17442 * @name ui.grid.expandable
17445 * # ui.grid.expandable
17447 * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
17449 * This module provides the ability to create subgrids with the ability to expand a row
17450 * to show the subgrid.
17452 * <div doc-module-components="ui.grid.expandable"></div>
17454 var module = angular.module('ui.grid.expandable', ['ui.grid']);
17458 * @name ui.grid.expandable.service:uiGridExpandableService
17460 * @description Services for the expandable grid
17462 module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
17464 initializeGrid: function (grid) {
17466 grid.expandable = {};
17467 grid.expandable.expandedAll = false;
17471 * @name enableExpandable
17472 * @propertyOf ui.grid.expandable.api:GridOptions
17473 * @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
17474 * within your application, or in specific modes on _this_ grid. Defaults to true.
17477 * $scope.gridOptions = {
17478 * enableExpandable: false
17482 grid.options.enableExpandable = grid.options.enableExpandable !== false;
17486 * @name expandableRowHeight
17487 * @propertyOf ui.grid.expandable.api:GridOptions
17488 * @description Height in pixels of the expanded subgrid. Defaults to
17492 * $scope.gridOptions = {
17493 * expandableRowHeight: 150
17497 grid.options.expandableRowHeight = grid.options.expandableRowHeight || 150;
17502 * @propertyOf ui.grid.expandable.api:GridOptions
17503 * @description Width in pixels of the expandable column. Defaults to 40
17506 * $scope.gridOptions = {
17507 * expandableRowHeaderWidth: 40
17511 grid.options.expandableRowHeaderWidth = grid.options.expandableRowHeaderWidth || 40;
17515 * @name expandableRowTemplate
17516 * @propertyOf ui.grid.expandable.api:GridOptions
17517 * @description Mandatory. The template for your expanded row
17520 * $scope.gridOptions = {
17521 * expandableRowTemplate: 'expandableRowTemplate.html'
17525 if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
17526 gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
17527 grid.options.enableExpandable = false;
17532 * @name ui.grid.expandable.api:PublicApi
17534 * @description Public Api for expandable feature
17538 * @name ui.grid.expandable.api:GridRow
17540 * @description Additional properties added to GridRow when using the expandable module
17544 * @name ui.grid.expandable.api:GridOptions
17546 * @description Options for configuring the expandable feature, these are available to be
17547 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17554 * @name rowExpandedStateChanged
17555 * @eventOf ui.grid.expandable.api:PublicApi
17556 * @description raised when row expanded or collapsed
17558 * gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
17560 * @param {GridRow} row the row that was expanded
17562 rowExpandedBeforeStateChanged: function(scope,row){
17564 rowExpandedStateChanged: function (scope, row) {
17573 * @name toggleRowExpansion
17574 * @methodOf ui.grid.expandable.api:PublicApi
17575 * @description Toggle a specific row
17577 * gridApi.expandable.toggleRowExpansion(rowEntity);
17579 * @param {object} rowEntity the data entity for the row you want to expand
17581 toggleRowExpansion: function (rowEntity) {
17582 var row = grid.getRow(rowEntity);
17583 if (row !== null) {
17584 service.toggleRowExpansion(grid, row);
17590 * @name expandAllRows
17591 * @methodOf ui.grid.expandable.api:PublicApi
17592 * @description Expand all subgrids.
17594 * gridApi.expandable.expandAllRows();
17597 expandAllRows: function() {
17598 service.expandAllRows(grid);
17603 * @name collapseAllRows
17604 * @methodOf ui.grid.expandable.api:PublicApi
17605 * @description Collapse all subgrids.
17607 * gridApi.expandable.collapseAllRows();
17610 collapseAllRows: function() {
17611 service.collapseAllRows(grid);
17616 * @name toggleAllRows
17617 * @methodOf ui.grid.expandable.api:PublicApi
17618 * @description Toggle all subgrids.
17620 * gridApi.expandable.toggleAllRows();
17623 toggleAllRows: function() {
17624 service.toggleAllRows(grid);
17629 * @methodOf ui.grid.expandable.api:PublicApi
17630 * @description Expand the data row
17631 * @param {object} rowEntity gridOptions.data[] array instance
17633 expandRow: function (rowEntity) {
17634 var row = grid.getRow(rowEntity);
17635 if (row !== null && !row.isExpanded) {
17636 service.toggleRowExpansion(grid, row);
17641 * @name collapseRow
17642 * @methodOf ui.grid.expandable.api:PublicApi
17643 * @description Collapse the data row
17644 * @param {object} rowEntity gridOptions.data[] array instance
17646 collapseRow: function (rowEntity) {
17647 var row = grid.getRow(rowEntity);
17648 if (row !== null && row.isExpanded) {
17649 service.toggleRowExpansion(grid, row);
17654 * @name getExpandedRows
17655 * @methodOf ui.grid.expandable.api:PublicApi
17656 * @description returns all expandedRow's entity references
17658 getExpandedRows: function () {
17659 return service.getExpandedRows(grid).map(function (gridRow) {
17660 return gridRow.entity;
17666 grid.api.registerEventsFromObject(publicApi.events);
17667 grid.api.registerMethodsFromObject(publicApi.methods);
17670 toggleRowExpansion: function (grid, row) {
17671 // trigger the "before change" event. Can change row height dynamically this way.
17672 grid.api.expandable.raise.rowExpandedBeforeStateChanged(row);
17676 * @propertyOf ui.grid.expandable.api:GridRow
17677 * @description Whether or not the row is currently expanded.
17680 * $scope.api.expandable.on.rowExpandedStateChanged($scope, function (row) {
17681 * if (row.isExpanded) {
17687 row.isExpanded = !row.isExpanded;
17688 if (angular.isUndefined(row.expandedRowHeight)){
17689 row.expandedRowHeight = grid.options.expandableRowHeight;
17692 if (row.isExpanded) {
17693 row.height = row.grid.options.rowHeight + row.expandedRowHeight;
17696 row.height = row.grid.options.rowHeight;
17697 grid.expandable.expandedAll = false;
17699 grid.api.expandable.raise.rowExpandedStateChanged(row);
17702 expandAllRows: function(grid, $scope) {
17703 grid.renderContainers.body.visibleRowCache.forEach( function(row) {
17704 if (!row.isExpanded) {
17705 service.toggleRowExpansion(grid, row);
17708 grid.expandable.expandedAll = true;
17709 grid.queueGridRefresh();
17712 collapseAllRows: function(grid) {
17713 grid.renderContainers.body.visibleRowCache.forEach( function(row) {
17714 if (row.isExpanded) {
17715 service.toggleRowExpansion(grid, row);
17718 grid.expandable.expandedAll = false;
17719 grid.queueGridRefresh();
17722 toggleAllRows: function(grid) {
17723 if (grid.expandable.expandedAll) {
17724 service.collapseAllRows(grid);
17727 service.expandAllRows(grid);
17731 getExpandedRows: function (grid) {
17732 return grid.rows.filter(function (row) {
17733 return row.isExpanded;
17742 * @name enableExpandableRowHeader
17743 * @propertyOf ui.grid.expandable.api:GridOptions
17744 * @description Show a rowHeader to provide the expandable buttons. If set to false then implies
17745 * you're going to use a custom method for expanding and collapsing the subgrids. Defaults to true.
17748 * $scope.gridOptions = {
17749 * enableExpandableRowHeader: false
17753 module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
17754 function (uiGridExpandableService, $templateCache) {
17758 require: '^uiGrid',
17760 compile: function () {
17762 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17763 uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
17765 if (!uiGridCtrl.grid.options.enableExpandable) {
17769 if (uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
17770 var expandableRowHeaderColDef = {
17771 name: 'expandableButtons',
17773 exporterSuppressExport: true,
17774 enableColumnResizing: false,
17775 enableColumnMenu: false,
17776 width: uiGridCtrl.grid.options.expandableRowHeaderWidth || 40
17778 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
17779 expandableRowHeaderColDef.headerCellTemplate = $templateCache.get('ui-grid/expandableTopRowHeader');
17780 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef, -90);
17784 post: function ($scope, $elm, $attrs, uiGridCtrl) {
17793 * @name ui.grid.expandable.directive:uiGrid
17794 * @description stacks on the uiGrid directive to register child grid with parent row when child is created
17796 module.directive('uiGrid', ['uiGridExpandableService', '$templateCache',
17797 function (uiGridExpandableService, $templateCache) {
17801 require: '^uiGrid',
17803 compile: function () {
17805 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17807 uiGridCtrl.grid.api.core.on.renderingComplete($scope, function() {
17808 //if a parent grid row is on the scope, then add the parentRow property to this childGrid
17809 if ($scope.row && $scope.row.grid && $scope.row.grid.options && $scope.row.grid.options.enableExpandable) {
17813 * @name ui.grid.expandable.class:Grid
17814 * @description Additional Grid properties added by expandable module
17820 * @propertyOf ui.grid.expandable.class:Grid
17821 * @description reference to the expanded parent row that owns this grid
17823 uiGridCtrl.grid.parentRow = $scope.row;
17825 //todo: adjust height on parent row when child grid height changes. we need some sort of gridHeightChanged event
17826 // uiGridCtrl.grid.core.on.canvasHeightChanged($scope, function(oldHeight, newHeight) {
17827 // uiGridCtrl.grid.parentRow = newHeight;
17833 post: function ($scope, $elm, $attrs, uiGridCtrl) {
17843 * @name ui.grid.expandable.directive:uiGridExpandableRow
17844 * @description directive to render the expandable row template
17846 module.directive('uiGridExpandableRow',
17847 ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
17848 function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
17855 compile: function () {
17857 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17858 gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
17859 function (template) {
17860 if ($scope.grid.options.expandableRowScope) {
17863 * @name expandableRowScope
17864 * @propertyOf ui.grid.expandable.api:GridOptions
17865 * @description Variables of object expandableScope will be available in the scope of the expanded subgrid
17868 * $scope.gridOptions = {
17869 * expandableRowScope: expandableScope
17873 var expandableRowScope = $scope.grid.options.expandableRowScope;
17874 for (var property in expandableRowScope) {
17875 if (expandableRowScope.hasOwnProperty(property)) {
17876 $scope[property] = expandableRowScope[property];
17880 var expandedRowElement = angular.element(template);
17881 $elm.append(expandedRowElement);
17882 expandedRowElement = $compile(expandedRowElement)($scope);
17883 $scope.row.expandedRendered = true;
17887 post: function ($scope, $elm, $attrs, uiGridCtrl) {
17888 $scope.$on('$destroy', function() {
17889 $scope.row.expandedRendered = false;
17899 * @name ui.grid.expandable.directive:uiGridRow
17900 * @description stacks on the uiGridRow directive to add support for expandable rows
17902 module.directive('uiGridRow',
17903 ['$compile', 'gridUtil', '$templateCache',
17904 function ($compile, gridUtil, $templateCache) {
17908 compile: function ($elm, $attrs) {
17910 pre: function ($scope, $elm, $attrs, controllers) {
17912 if (!$scope.grid.options.enableExpandable) {
17916 $scope.expandableRow = {};
17918 $scope.expandableRow.shouldRenderExpand = function () {
17919 var ret = $scope.colContainer.name === 'body' && $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
17923 $scope.expandableRow.shouldRenderFiller = function () {
17924 var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
17929 * Commented out @PaulL1. This has no purpose that I can see, and causes #2964. If this code needs to be reinstated for some
17930 * reason it needs to use drawnWidth, not width, and needs to check column visibility. It should really use render container
17931 * visible column cache also instead of checking column.renderContainer.
17932 function updateRowContainerWidth() {
17933 var grid = $scope.grid;
17935 grid.columns.forEach( function (column) {
17936 if (column.renderContainer === 'left') {
17937 colWidth += column.width;
17940 colWidth = Math.floor(colWidth);
17941 return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
17942 ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
17943 ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
17946 if ($scope.colContainer.name === 'left') {
17947 $scope.grid.registerStyleComputation({
17949 func: updateRowContainerWidth
17954 post: function ($scope, $elm, $attrs, controllers) {
17963 * @name ui.grid.expandable.directive:uiGridViewport
17964 * @description stacks on the uiGridViewport directive to append the expandable row html elements to the
17965 * default gridRow template
17967 module.directive('uiGridViewport',
17968 ['$compile', 'gridUtil', '$templateCache',
17969 function ($compile, gridUtil, $templateCache) {
17973 compile: function ($elm, $attrs) {
17975 //todo: this adds ng-if watchers to each row even if the grid is not using expandable directive
17976 // or options.enableExpandable == false
17977 // The alternative is to compile the template and append to each row in a uiGridRow directive
17979 var rowRepeatDiv = angular.element($elm.children().children()[0]);
17980 var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
17981 var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
17982 rowRepeatDiv.append(expandedRowElement);
17983 rowRepeatDiv.append(expandedRowFillerElement);
17985 pre: function ($scope, $elm, $attrs, controllers) {
17987 post: function ($scope, $elm, $attrs, controllers) {
17996 /* global console */
18003 * @name ui.grid.exporter
18006 * # ui.grid.exporter
18008 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
18010 * This module provides the ability to export data from the grid.
18012 * Data can be exported in a range of formats, and all data, visible
18013 * data, or selected rows can be exported, with all columns or visible
18016 * No UI is provided, the caller should provide their own UI/buttons
18017 * as appropriate, or enable the gridMenu
18022 * <div doc-module-components="ui.grid.exporter"></div>
18025 var module = angular.module('ui.grid.exporter', ['ui.grid']);
18029 * @name ui.grid.exporter.constant:uiGridExporterConstants
18031 * @description constants available in exporter module
18035 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
18037 * @description export all data, including data not visible. Can
18038 * be set for either rowTypes or colTypes
18042 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
18044 * @description export only visible data, including data not visible. Can
18045 * be set for either rowTypes or colTypes
18049 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
18051 * @description export all data, including data not visible. Can
18052 * be set only for rowTypes, selection of only some columns is
18055 module.constant('uiGridExporterConstants', {
18056 featureName: 'exporter',
18058 VISIBLE: 'visible',
18059 SELECTED: 'selected',
18060 CSV_CONTENT: 'CSV_CONTENT',
18061 BUTTON_LABEL: 'BUTTON_LABEL',
18062 FILE_NAME: 'FILE_NAME'
18067 * @name ui.grid.exporter.service:uiGridExporterService
18069 * @description Services for exporter feature
18071 module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
18072 function ($q, uiGridExporterConstants, gridUtil, $compile, $interval, i18nService) {
18078 initializeGrid: function (grid) {
18080 //add feature namespace and any properties to grid for needed state
18081 grid.exporter = {};
18082 this.defaultGridOptions(grid.options);
18086 * @name ui.grid.exporter.api:PublicApi
18088 * @description Public Api for exporter feature
18100 * @methodOf ui.grid.exporter.api:PublicApi
18101 * @description Exports rows from the grid in csv format,
18102 * the data exported is selected based on the provided options
18103 * @param {string} rowTypes which rows to export, valid values are
18104 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18105 * uiGridExporterConstants.SELECTED
18106 * @param {string} colTypes which columns to export, valid values are
18107 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
18109 csvExport: function (rowTypes, colTypes) {
18110 service.csvExport(grid, rowTypes, colTypes);
18115 * @methodOf ui.grid.exporter.api:PublicApi
18116 * @description Exports rows from the grid in pdf format,
18117 * the data exported is selected based on the provided options
18118 * Note that this function has a dependency on pdfMake, all
18119 * going well this has been installed for you.
18120 * The resulting pdf opens in a new browser window.
18121 * @param {string} rowTypes which rows to export, valid values are
18122 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18123 * uiGridExporterConstants.SELECTED
18124 * @param {string} colTypes which columns to export, valid values are
18125 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
18127 pdfExport: function (rowTypes, colTypes) {
18128 service.pdfExport(grid, rowTypes, colTypes);
18134 grid.api.registerEventsFromObject(publicApi.events);
18136 grid.api.registerMethodsFromObject(publicApi.methods);
18138 if (grid.api.core.addToGridMenu){
18139 service.addToMenu( grid );
18141 // order of registration is not guaranteed, register in a little while
18142 $interval( function() {
18143 if (grid.api.core.addToGridMenu){
18144 service.addToMenu( grid );
18151 defaultGridOptions: function (gridOptions) {
18152 //default option to true unless it was explicitly set to false
18155 * @name ui.grid.exporter.api:GridOptions
18157 * @description GridOptions for exporter feature, these are available to be
18158 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
18162 * @name ui.grid.exporter.api:ColumnDef
18163 * @description ColumnDef settings for exporter
18167 * @name exporterSuppressMenu
18168 * @propertyOf ui.grid.exporter.api:GridOptions
18169 * @description Don't show the export menu button, implying the user
18170 * will roll their own UI for calling the exporter
18171 * <br/>Defaults to false
18173 gridOptions.exporterSuppressMenu = gridOptions.exporterSuppressMenu === true;
18176 * @name exporterMenuLabel
18177 * @propertyOf ui.grid.exporter.api:GridOptions
18178 * @description The text to show on the exporter menu button
18180 * <br/>Defaults to 'Export'
18182 gridOptions.exporterMenuLabel = gridOptions.exporterMenuLabel ? gridOptions.exporterMenuLabel : 'Export';
18185 * @name exporterSuppressColumns
18186 * @propertyOf ui.grid.exporter.api:GridOptions
18187 * @description Columns that should not be exported. The selectionRowHeader is already automatically
18188 * suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
18189 * output then add it in this list. You should provide an array of column names.
18190 * <br/>Defaults to: []
18192 * gridOptions.exporterSuppressColumns = [ 'buttons' ];
18195 gridOptions.exporterSuppressColumns = gridOptions.exporterSuppressColumns ? gridOptions.exporterSuppressColumns : [];
18198 * @name exporterCsvColumnSeparator
18199 * @propertyOf ui.grid.exporter.api:GridOptions
18200 * @description The character to use as column separator
18202 * <br/>Defaults to ','
18204 gridOptions.exporterCsvColumnSeparator = gridOptions.exporterCsvColumnSeparator ? gridOptions.exporterCsvColumnSeparator : ',';
18207 * @name exporterCsvFilename
18208 * @propertyOf ui.grid.exporter.api:GridOptions
18209 * @description The default filename to use when saving the downloaded csv.
18210 * This will only work in some browsers.
18211 * <br/>Defaults to 'download.csv'
18213 gridOptions.exporterCsvFilename = gridOptions.exporterCsvFilename ? gridOptions.exporterCsvFilename : 'download.csv';
18216 * @name exporterPdfFilename
18217 * @propertyOf ui.grid.exporter.api:GridOptions
18218 * @description The default filename to use when saving the downloaded pdf, only used in IE (other browsers open pdfs in a new window)
18219 * <br/>Defaults to 'download.pdf'
18221 gridOptions.exporterPdfFilename = gridOptions.exporterPdfFilename ? gridOptions.exporterPdfFilename : 'download.pdf';
18224 * @name exporterOlderExcelCompatibility
18225 * @propertyOf ui.grid.exporter.api:GridOptions
18226 * @description Some versions of excel don't like the utf-16 BOM on the front, and it comes
18227 * through as  in the first column header. Setting this option to false will suppress this, at the
18228 * expense of proper utf-16 handling in applications that do recognise the BOM
18229 * <br/>Defaults to false
18231 gridOptions.exporterOlderExcelCompatibility = gridOptions.exporterOlderExcelCompatibility === true;
18234 * @name exporterIsExcelCompatible
18235 * @propertyOf ui.grid.exporter.api:GridOptions
18236 * @description Separator header, used to set a custom column separator in a csv file, only works on MS Excel.
18237 * Used it on other programs will make csv content display unproperly. Setting this option to false won't add this header.
18238 * <br/>Defaults to false
18240 gridOptions.exporterIsExcelCompatible = gridOptions.exporterIsExcelCompatible === true;
18243 * @name exporterMenuItemOrder
18244 * @propertyOf ui.grid.exporter.api:GridOptions
18245 * @description An option to determine the starting point for the menu items created by the exporter
18246 * <br/>Defaults to 200
18248 gridOptions.exporterMenuItemOrder = gridOptions.exporterMenuItemOrder ? gridOptions.exporterMenuItemOrder : 200;
18251 * @name exporterPdfDefaultStyle
18252 * @propertyOf ui.grid.exporter.api:GridOptions
18253 * @description The default style in pdfMake format
18254 * <br/>Defaults to:
18261 gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
18264 * @name exporterPdfTableStyle
18265 * @propertyOf ui.grid.exporter.api:GridOptions
18266 * @description The table style in pdfMake format
18267 * <br/>Defaults to:
18270 * margin: [0, 5, 0, 15]
18274 gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
18277 * @name exporterPdfTableHeaderStyle
18278 * @propertyOf ui.grid.exporter.api:GridOptions
18279 * @description The tableHeader style in pdfMake format
18280 * <br/>Defaults to:
18289 gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
18292 * @name exporterPdfHeader
18293 * @propertyOf ui.grid.exporter.api:GridOptions
18294 * @description The header section for pdf exports. Can be
18297 * gridOptions.exporterPdfHeader = 'My Header';
18299 * Can be a more complex object in pdfMake format:
18301 * gridOptions.exporterPdfHeader = {
18304 * { text: 'Right part', alignment: 'right' }
18308 * Or can be a function, allowing page numbers and the like
18310 * gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
18313 gridOptions.exporterPdfHeader = gridOptions.exporterPdfHeader ? gridOptions.exporterPdfHeader : null;
18316 * @name exporterPdfFooter
18317 * @propertyOf ui.grid.exporter.api:GridOptions
18318 * @description The header section for pdf exports. Can be
18321 * gridOptions.exporterPdfFooter = 'My Footer';
18323 * Can be a more complex object in pdfMake format:
18325 * gridOptions.exporterPdfFooter = {
18328 * { text: 'Right part', alignment: 'right' }
18332 * Or can be a function, allowing page numbers and the like
18334 * gridOptions.exporterPdfFooter: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
18337 gridOptions.exporterPdfFooter = gridOptions.exporterPdfFooter ? gridOptions.exporterPdfFooter : null;
18340 * @name exporterPdfOrientation
18341 * @propertyOf ui.grid.exporter.api:GridOptions
18342 * @description The orientation, should be a valid pdfMake value,
18343 * 'landscape' or 'portrait'
18344 * <br/>Defaults to landscape
18346 gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
18349 * @name exporterPdfPageSize
18350 * @propertyOf ui.grid.exporter.api:GridOptions
18351 * @description The orientation, should be a valid pdfMake
18352 * paper size, usually 'A4' or 'LETTER'
18353 * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
18354 * <br/>Defaults to A4
18356 gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
18359 * @name exporterPdfMaxGridWidth
18360 * @propertyOf ui.grid.exporter.api:GridOptions
18361 * @description The maxium grid width - the current grid width
18362 * will be scaled to match this, with any fixed width columns
18363 * being adjusted accordingly.
18364 * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER
18366 gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
18369 * @name exporterPdfTableLayout
18370 * @propertyOf ui.grid.exporter.api:GridOptions
18371 * @description A tableLayout in pdfMake format,
18372 * controls gridlines and the like. We use the default
18374 * <br/>Defaults to null, which means no layout
18379 * @name exporterMenuAllData
18380 * @porpertyOf ui.grid.exporter.api:GridOptions
18381 * @description Add export all data as cvs/pdf menu items to the ui-grid grid menu, if it's present. Defaults to true.
18383 gridOptions.exporterMenuAllData = gridOptions.exporterMenuAllData !== undefined ? gridOptions.exporterMenuAllData : true;
18387 * @name exporterMenuVisibleData
18388 * @porpertyOf ui.grid.exporter.api:GridOptions
18389 * @description Add export visible data as cvs/pdf menu items to the ui-grid grid menu, if it's present. Defaults to true.
18391 gridOptions.exporterMenuVisibleData = gridOptions.exporterMenuVisibleData !== undefined ? gridOptions.exporterMenuVisibleData : true;
18395 * @name exporterMenuSelectedData
18396 * @porpertyOf ui.grid.exporter.api:GridOptions
18397 * @description Add export selected data as cvs/pdf menu items to the ui-grid grid menu, if it's present. Defaults to true.
18399 gridOptions.exporterMenuSelectedData = gridOptions.exporterMenuSelectedData !== undefined ? gridOptions.exporterMenuSelectedData : true;
18403 * @name exporterMenuCsv
18404 * @propertyOf ui.grid.exporter.api:GridOptions
18405 * @description Add csv export menu items to the ui-grid grid menu, if it's present. Defaults to true.
18407 gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
18411 * @name exporterMenuPdf
18412 * @propertyOf ui.grid.exporter.api:GridOptions
18413 * @description Add pdf export menu items to the ui-grid grid menu, if it's present. Defaults to true.
18415 gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
18419 * @name exporterPdfCustomFormatter
18420 * @propertyOf ui.grid.exporter.api:GridOptions
18421 * @description A custom callback routine that changes the pdf document, adding any
18422 * custom styling or content that is supported by pdfMake. Takes in the complete docDefinition, and
18423 * must return an updated docDefinition ready for pdfMake.
18425 * In this example we add a style to the style array, so that we can use it in our
18426 * footer definition.
18428 * gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
18429 * docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
18430 * return docDefinition;
18433 * gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
18436 gridOptions.exporterPdfCustomFormatter = ( gridOptions.exporterPdfCustomFormatter && typeof( gridOptions.exporterPdfCustomFormatter ) === 'function' ) ? gridOptions.exporterPdfCustomFormatter : function ( docDef ) { return docDef; };
18440 * @name exporterHeaderFilterUseName
18441 * @propertyOf ui.grid.exporter.api:GridOptions
18442 * @description Defaults to false, which leads to `displayName` being passed into the headerFilter.
18443 * If set to true, then will pass `name` instead.
18448 * gridOptions.exporterHeaderFilterUseName = true;
18451 gridOptions.exporterHeaderFilterUseName = gridOptions.exporterHeaderFilterUseName === true;
18455 * @name exporterHeaderFilter
18456 * @propertyOf ui.grid.exporter.api:GridOptions
18457 * @description A function to apply to the header displayNames before exporting. Useful for internationalisation,
18458 * for example if you were using angular-translate you'd set this to `$translate.instant`. Note that this
18459 * call must be synchronous, it cannot be a call that returns a promise.
18461 * Behaviour can be changed to pass in `name` instead of `displayName` through use of `exporterHeaderFilterUseName: true`.
18465 * gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + name; };
18469 * gridOptions.exporterHeaderFilter = $translate.instant;
18475 * @name exporterFieldCallback
18476 * @propertyOf ui.grid.exporter.api:GridOptions
18477 * @description A function to call for each field before exporting it. Allows
18478 * massaging of raw data into a display format, for example if you have applied
18479 * filters to convert codes into decodes, or you require
18480 * a specific date format in the exported content.
18482 * The method is called once for each field exported, and provides the grid, the
18483 * gridCol and the GridRow for you to use as context in massaging the data.
18485 * @param {Grid} grid provides the grid in case you have need of it
18486 * @param {GridRow} row the row from which the data comes
18487 * @param {GridCol} col the column from which the data comes
18488 * @param {object} value the value for your massaging
18489 * @returns {object} you must return the massaged value ready for exporting
18493 * gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
18494 * if ( col.name === 'status' ){
18495 * value = decodeStatus( value );
18501 gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
18505 * @name exporterAllDataFn
18506 * @propertyOf ui.grid.exporter.api:GridOptions
18507 * @description This promise is needed when exporting all rows,
18508 * and the data need to be provided by server side. Default is null.
18509 * @returns {Promise} a promise to load all data from server
18513 * gridOptions.exporterAllDataFn = function () {
18514 * return $http.get('/data/100.json')
18518 gridOptions.exporterAllDataFn = gridOptions.exporterAllDataFn ? gridOptions.exporterAllDataFn : null;
18522 * @name exporterAllDataPromise
18523 * @propertyOf ui.grid.exporter.api:GridOptions
18524 * @description DEPRECATED - exporterAllDataFn used to be
18525 * called this, but it wasn't a promise, it was a function that returned
18526 * a promise. Deprecated, but supported for backward compatibility, use
18527 * exporterAllDataFn instead.
18528 * @returns {Promise} a promise to load all data from server
18532 * gridOptions.exporterAllDataFn = function () {
18533 * return $http.get('/data/100.json')
18537 if ( gridOptions.exporterAllDataFn == null && gridOptions.exporterAllDataPromise ) {
18538 gridOptions.exporterAllDataFn = gridOptions.exporterAllDataPromise;
18546 * @methodOf ui.grid.exporter.service:uiGridExporterService
18547 * @description Adds export items to the grid menu,
18548 * allowing the user to select export options
18549 * @param {Grid} grid the grid from which data should be exported
18551 addToMenu: function ( grid ) {
18552 grid.api.core.addToGridMenu( grid, [
18554 title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
18555 action: function ($event) {
18556 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
18558 shown: function() {
18559 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuAllData;
18561 order: grid.options.exporterMenuItemOrder
18564 title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
18565 action: function ($event) {
18566 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
18568 shown: function() {
18569 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuVisibleData;
18571 order: grid.options.exporterMenuItemOrder + 1
18574 title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
18575 action: function ($event) {
18576 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
18578 shown: function() {
18579 return this.grid.options.exporterMenuCsv && this.grid.options.exporterMenuSelectedData &&
18580 ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
18582 order: grid.options.exporterMenuItemOrder + 2
18585 title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
18586 action: function ($event) {
18587 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
18589 shown: function() {
18590 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuAllData;
18592 order: grid.options.exporterMenuItemOrder + 3
18595 title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
18596 action: function ($event) {
18597 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
18599 shown: function() {
18600 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuVisibleData;
18602 order: grid.options.exporterMenuItemOrder + 4
18605 title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
18606 action: function ($event) {
18607 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
18609 shown: function() {
18610 return this.grid.options.exporterMenuPdf && this.grid.options.exporterMenuSelectedData &&
18611 ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
18613 order: grid.options.exporterMenuItemOrder + 5
18622 * @methodOf ui.grid.exporter.service:uiGridExporterService
18623 * @description Exports rows from the grid in csv format,
18624 * the data exported is selected based on the provided options
18625 * @param {Grid} grid the grid from which data should be exported
18626 * @param {string} rowTypes which rows to export, valid values are
18627 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18628 * uiGridExporterConstants.SELECTED
18629 * @param {string} colTypes which columns to export, valid values are
18630 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18631 * uiGridExporterConstants.SELECTED
18633 csvExport: function (grid, rowTypes, colTypes) {
18635 this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function() {
18636 var exportColumnHeaders = grid.options.showHeader ? self.getColumnHeaders(grid, colTypes) : [];
18637 var exportData = self.getData(grid, rowTypes, colTypes);
18638 var csvContent = self.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
18640 self.downloadFile (grid.options.exporterCsvFilename, csvContent, grid.options.exporterCsvColumnSeparator, grid.options.exporterOlderExcelCompatibility, grid.options.exporterIsExcelCompatible);
18646 * @name loadAllDataIfNeeded
18647 * @methodOf ui.grid.exporter.service:uiGridExporterService
18648 * @description When using server side pagination, use exporterAllDataFn to
18649 * load all data before continuing processing.
18650 * When using client side pagination, return a resolved promise so processing
18651 * continues immediately
18652 * @param {Grid} grid the grid from which data should be exported
18653 * @param {string} rowTypes which rows to export, valid values are
18654 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18655 * uiGridExporterConstants.SELECTED
18656 * @param {string} colTypes which columns to export, valid values are
18657 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18658 * uiGridExporterConstants.SELECTED
18660 loadAllDataIfNeeded: function (grid, rowTypes, colTypes) {
18661 if ( rowTypes === uiGridExporterConstants.ALL && grid.rows.length !== grid.options.totalItems && grid.options.exporterAllDataFn) {
18662 return grid.options.exporterAllDataFn()
18664 grid.modifyRows(grid.options.data);
18667 var deferred = $q.defer();
18668 deferred.resolve();
18669 return deferred.promise;
18675 * @propertyOf ui.grid.exporter.api:ColumnDef
18676 * @name exporterSuppressExport
18677 * @description Suppresses export for this column. Used by selection and expandable.
18682 * @name getColumnHeaders
18683 * @methodOf ui.grid.exporter.service:uiGridExporterService
18684 * @description Gets the column headers from the grid to use
18685 * as a title row for the exported file, all headers have
18686 * headerCellFilters applied as appropriate.
18688 * Column headers are an array of objects, each object has
18689 * name, displayName, width and align attributes. Only name is
18690 * used for csv, all attributes are used for pdf.
18692 * @param {Grid} grid the grid from which data should be exported
18693 * @param {string} colTypes which columns to export, valid values are
18694 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18695 * uiGridExporterConstants.SELECTED
18697 getColumnHeaders: function (grid, colTypes) {
18701 if ( colTypes === uiGridExporterConstants.ALL ){
18702 columns = grid.columns;
18704 var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18705 var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18706 var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18708 columns = leftColumns.concat(bodyColumns,rightColumns);
18711 columns.forEach( function( gridCol, index ) {
18712 if ( gridCol.colDef.exporterSuppressExport !== true &&
18713 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
18715 name: gridCol.field,
18716 displayName: grid.options.exporterHeaderFilter ? ( grid.options.exporterHeaderFilterUseName ? grid.options.exporterHeaderFilter(gridCol.name) : grid.options.exporterHeaderFilter(gridCol.displayName) ) : gridCol.displayName,
18717 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
18718 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
18729 * @propertyOf ui.grid.exporter.api:ColumnDef
18730 * @name exporterPdfAlign
18731 * @description the alignment you'd like for this specific column when
18732 * exported into a pdf. Can be 'left', 'right', 'center' or any other
18733 * valid pdfMake alignment option.
18739 * @name ui.grid.exporter.api:GridRow
18740 * @description GridRow settings for exporter
18744 * @name exporterEnableExporting
18745 * @propertyOf ui.grid.exporter.api:GridRow
18746 * @description If set to false, then don't export this row, notwithstanding visible or
18748 * <br/>Defaults to true
18754 * @methodOf ui.grid.exporter.service:uiGridExporterService
18755 * @description Gets data from the grid based on the provided options,
18756 * all cells have cellFilters applied as appropriate. Any rows marked
18757 * `exporterEnableExporting: false` will not be exported
18758 * @param {Grid} grid the grid from which data should be exported
18759 * @param {string} rowTypes which rows to export, valid values are
18760 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18761 * uiGridExporterConstants.SELECTED
18762 * @param {string} colTypes which columns to export, valid values are
18763 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
18764 * uiGridExporterConstants.SELECTED
18765 * @param {boolean} applyCellFilters whether or not to get the display value or the raw value of the data
18767 getData: function (grid, rowTypes, colTypes, applyCellFilters) {
18772 switch ( rowTypes ) {
18773 case uiGridExporterConstants.ALL:
18776 case uiGridExporterConstants.VISIBLE:
18777 rows = grid.getVisibleRows();
18779 case uiGridExporterConstants.SELECTED:
18780 if ( grid.api.selection ){
18781 rows = grid.api.selection.getSelectedGridRows();
18783 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
18788 if ( colTypes === uiGridExporterConstants.ALL ){
18789 columns = grid.columns;
18791 var leftColumns = grid.renderContainers.left ? grid.renderContainers.left.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18792 var bodyColumns = grid.renderContainers.body ? grid.renderContainers.body.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18793 var rightColumns = grid.renderContainers.right ? grid.renderContainers.right.visibleColumnCache.filter( function( column ){ return column.visible; } ) : [];
18795 columns = leftColumns.concat(bodyColumns,rightColumns);
18798 rows.forEach( function( row, index ) {
18800 if (row.exporterEnableExporting !== false) {
18801 var extractedRow = [];
18804 columns.forEach( function( gridCol, index ) {
18805 if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
18806 gridCol.colDef.exporterSuppressExport !== true &&
18807 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
18808 var cellValue = applyCellFilters ? grid.getCellDisplayValue( row, gridCol ) : grid.getCellValue( row, gridCol );
18809 var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, cellValue ) };
18810 if ( gridCol.colDef.exporterPdfAlign ) {
18811 extractedField.alignment = gridCol.colDef.exporterPdfAlign;
18813 extractedRow.push(extractedField);
18817 data.push(extractedRow);
18827 * @name formatAsCsv
18828 * @methodOf ui.grid.exporter.service:uiGridExporterService
18829 * @description Formats the column headers and data as a CSV,
18830 * and sends that data to the user
18831 * @param {array} exportColumnHeaders an array of column headers,
18832 * where each header is an object with name, width and maybe alignment
18833 * @param {array} exportData an array of rows, where each row is
18834 * an array of column data
18835 * @param {string} separator a string that represents the separator to be used in the csv file
18836 * @returns {string} csv the formatted csv as a string
18838 formatAsCsv: function (exportColumnHeaders, exportData, separator) {
18841 var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
18843 var csv = bareHeaders.length > 0 ? (self.formatRowAsCsv(this, separator)(bareHeaders) + '\n') : '';
18845 csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
18852 * @name formatRowAsCsv
18853 * @methodOf ui.grid.exporter.service:uiGridExporterService
18854 * @description Renders a single field as a csv field, including
18855 * quotes around the value
18856 * @param {exporterService} exporter pass in exporter
18857 * @param {array} row the row to be turned into a csv string
18858 * @returns {string} a csv-ified version of the row
18860 formatRowAsCsv: function (exporter, separator) {
18861 return function (row) {
18862 return row.map(exporter.formatFieldAsCsv).join(separator);
18868 * @name formatFieldAsCsv
18869 * @methodOf ui.grid.exporter.service:uiGridExporterService
18870 * @description Renders a single field as a csv field, including
18871 * quotes around the value
18872 * @param {field} field the field to be turned into a csv string,
18873 * may be of any type
18874 * @returns {string} a csv-ified version of the field
18876 formatFieldAsCsv: function (field) {
18877 if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
18880 if (typeof(field.value) === 'number') {
18881 return field.value;
18883 if (typeof(field.value) === 'boolean') {
18884 return (field.value ? 'TRUE' : 'FALSE') ;
18886 if (typeof(field.value) === 'string') {
18887 return '"' + field.value.replace(/"/g,'""') + '"';
18890 return JSON.stringify(field.value);
18897 * @methodOf ui.grid.exporter.service:uiGridExporterService
18898 * @description Checks whether current browser is IE and returns it's version if it is
18900 isIE: function () {
18901 var match = navigator.userAgent.search(/(?:Edge|MSIE|Trident\/.*; rv:)/);
18904 if (match !== -1) {
18914 * @name downloadFile
18915 * @methodOf ui.grid.exporter.service:uiGridExporterService
18916 * @description Triggers download of a csv file. Logic provided
18917 * by @cssensei (from his colleagues at https://github.com/ifeelgoods) in issue #2391
18918 * @param {string} fileName the filename we'd like our file to be
18920 * @param {string} csvContent the csv content that we'd like to
18921 * download as a file
18922 * @param {boolean} exporterOlderExcelCompatibility whether or not we put a utf-16 BOM on the from (\uFEFF)
18923 * @param {boolean} exporterIsExcelCompatible whether or not we add separator header ('sep=X')
18925 downloadFile: function (fileName, csvContent, columnSeparator, exporterOlderExcelCompatibility, exporterIsExcelCompatible) {
18927 var a = D.createElement('a');
18928 var strMimeType = 'application/octet-stream;charset=utf-8';
18930 var ieVersion = this.isIE();
18932 if (exporterIsExcelCompatible) {
18933 csvContent = 'sep=' + columnSeparator + '\r\n' + csvContent;
18937 if (navigator.msSaveBlob) {
18938 return navigator.msSaveOrOpenBlob(
18940 [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18941 { type: strMimeType } ),
18947 var frame = D.createElement('iframe');
18948 document.body.appendChild(frame);
18950 frame.contentWindow.document.open('text/html', 'replace');
18951 frame.contentWindow.document.write(csvContent);
18952 frame.contentWindow.document.close();
18953 frame.contentWindow.focus();
18954 frame.contentWindow.document.execCommand('SaveAs', true, fileName);
18956 document.body.removeChild(frame);
18960 //html5 A[download]
18961 if ('download' in a) {
18962 var blob = new Blob(
18963 [exporterOlderExcelCompatibility ? "\uFEFF" : '', csvContent],
18964 { type: strMimeType }
18966 rawFile = URL.createObjectURL(blob);
18967 a.setAttribute('download', fileName);
18969 rawFile = 'data:' + strMimeType + ',' + encodeURIComponent(csvContent);
18970 a.setAttribute('target', '_blank');
18974 a.setAttribute('style', 'display:none;');
18975 D.body.appendChild(a);
18976 setTimeout(function() {
18979 // Workaround for Safari 5
18980 } else if (document.createEvent) {
18981 var eventObj = document.createEvent('MouseEvents');
18982 eventObj.initEvent('click', true, true);
18983 a.dispatchEvent(eventObj);
18985 D.body.removeChild(a);
18993 * @methodOf ui.grid.exporter.service:uiGridExporterService
18994 * @description Exports rows from the grid in pdf format,
18995 * the data exported is selected based on the provided options.
18996 * Note that this function has a dependency on pdfMake, which must
18997 * be installed. The resulting pdf opens in a new
18999 * @param {Grid} grid the grid from which data should be exported
19000 * @param {string} rowTypes which rows to export, valid values are
19001 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
19002 * uiGridExporterConstants.SELECTED
19003 * @param {string} colTypes which columns to export, valid values are
19004 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
19005 * uiGridExporterConstants.SELECTED
19007 pdfExport: function (grid, rowTypes, colTypes) {
19009 this.loadAllDataIfNeeded(grid, rowTypes, colTypes).then(function () {
19010 var exportColumnHeaders = self.getColumnHeaders(grid, colTypes);
19011 var exportData = self.getData(grid, rowTypes, colTypes);
19012 var docDefinition = self.prepareAsPdf(grid, exportColumnHeaders, exportData);
19014 if (self.isIE() || navigator.appVersion.indexOf("Edge") !== -1) {
19015 self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
19017 pdfMake.createPdf(docDefinition).open();
19025 * @name downloadPdf
19026 * @methodOf ui.grid.exporter.service:uiGridExporterService
19027 * @description Generates and retrieves the pdf as a blob, then downloads
19028 * it as a file. Only used in IE, in all other browsers we use the native
19029 * pdfMake.open function to just open the PDF
19030 * @param {string} fileName the filename to give to the pdf, can be set
19031 * through exporterPdfFilename
19032 * @param {object} docDefinition a pdf docDefinition that we can generate
19033 * and get a blob from
19035 downloadPDF: function (fileName, docDefinition) {
19037 var a = D.createElement('a');
19038 var strMimeType = 'application/octet-stream;charset=utf-8';
19042 ieVersion = this.isIE(); // This is now a boolean value
19043 var doc = pdfMake.createPdf(docDefinition);
19046 doc.getBuffer( function (buffer) {
19047 blob = new Blob([buffer]);
19050 if (navigator.msSaveBlob) {
19051 return navigator.msSaveBlob(
19056 // Previously: && ieVersion < 10
19057 // ieVersion now returns a boolean for the
19058 // sake of sanity. We just check `msSaveBlob` first.
19060 var frame = D.createElement('iframe');
19061 document.body.appendChild(frame);
19063 frame.contentWindow.document.open("text/html", "replace");
19064 frame.contentWindow.document.write(blob);
19065 frame.contentWindow.document.close();
19066 frame.contentWindow.focus();
19067 frame.contentWindow.document.execCommand('SaveAs', true, fileName);
19069 document.body.removeChild(frame);
19078 * @name renderAsPdf
19079 * @methodOf ui.grid.exporter.service:uiGridExporterService
19080 * @description Renders the data into a pdf, and opens that pdf.
19082 * @param {Grid} grid the grid from which data should be exported
19083 * @param {array} exportColumnHeaders an array of column headers,
19084 * where each header is an object with name, width and maybe alignment
19085 * @param {array} exportData an array of rows, where each row is
19086 * an array of column data
19087 * @returns {object} a pdfMake format document definition, ready
19090 prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
19091 var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
19093 var headerColumns = exportColumnHeaders.map( function( header ) {
19094 return { text: header.displayName, style: 'tableHeader' };
19097 var stringData = exportData.map(this.formatRowAsPdf(this));
19099 var allData = [headerColumns].concat(stringData);
19101 var docDefinition = {
19102 pageOrientation: grid.options.exporterPdfOrientation,
19103 pageSize: grid.options.exporterPdfPageSize,
19105 style: 'tableStyle',
19108 widths: headerWidths,
19113 tableStyle: grid.options.exporterPdfTableStyle,
19114 tableHeader: grid.options.exporterPdfTableHeaderStyle
19116 defaultStyle: grid.options.exporterPdfDefaultStyle
19119 if ( grid.options.exporterPdfLayout ){
19120 docDefinition.layout = grid.options.exporterPdfLayout;
19123 if ( grid.options.exporterPdfHeader ){
19124 docDefinition.header = grid.options.exporterPdfHeader;
19127 if ( grid.options.exporterPdfFooter ){
19128 docDefinition.footer = grid.options.exporterPdfFooter;
19131 if ( grid.options.exporterPdfCustomFormatter ){
19132 docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
19134 return docDefinition;
19141 * @name calculatePdfHeaderWidths
19142 * @methodOf ui.grid.exporter.service:uiGridExporterService
19143 * @description Determines the column widths base on the
19144 * widths we got from the grid. If the column is drawn
19145 * then we have a drawnWidth. If the column is not visible
19146 * then we have '*', 'x%' or a width. When columns are
19147 * not visible they don't contribute to the overall gridWidth,
19148 * so we need to adjust to allow for extra columns
19150 * Our basic heuristic is to take the current gridWidth, plus
19151 * numeric columns and call this the base gridwidth.
19153 * To that we add 100 for any '*' column, and x% of the base gridWidth
19154 * for any column that is a %
19156 * @param {Grid} grid the grid from which data should be exported
19157 * @param {array} exportHeaders array of header information
19158 * @returns {object} an array of header widths
19160 calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
19161 var baseGridWidth = 0;
19162 exportHeaders.forEach( function(value){
19163 if (typeof(value.width) === 'number'){
19164 baseGridWidth += value.width;
19168 var extraColumns = 0;
19169 exportHeaders.forEach( function(value){
19170 if (value.width === '*'){
19171 extraColumns += 100;
19173 if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
19174 var percent = parseInt(value.width.match(/(\d)*%/)[0]);
19176 value.width = baseGridWidth * percent / 100;
19177 extraColumns += value.width;
19181 var gridWidth = baseGridWidth + extraColumns;
19183 return exportHeaders.map(function( header ) {
19184 return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
19191 * @name formatRowAsPdf
19192 * @methodOf ui.grid.exporter.service:uiGridExporterService
19193 * @description Renders a row in a format consumable by PDF,
19194 * mainly meaning casting everything to a string
19195 * @param {exporterService} exporter pass in exporter
19196 * @param {array} row the row to be turned into a csv string
19197 * @returns {string} a csv-ified version of the row
19199 formatRowAsPdf: function ( exporter ) {
19200 return function( row ) {
19201 return row.map(exporter.formatFieldAsPdfString);
19208 * @name formatFieldAsCsv
19209 * @methodOf ui.grid.exporter.service:uiGridExporterService
19210 * @description Renders a single field as a pdf-able field, which
19211 * is different from a csv field only in that strings don't have quotes
19213 * @param {field} field the field to be turned into a pdf string,
19214 * may be of any type
19215 * @returns {string} a string-ified version of the field
19217 formatFieldAsPdfString: function (field) {
19219 if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
19221 } else if (typeof(field.value) === 'number') {
19222 returnVal = field.value.toString();
19223 } else if (typeof(field.value) === 'boolean') {
19224 returnVal = (field.value ? 'TRUE' : 'FALSE') ;
19225 } else if (typeof(field.value) === 'string') {
19226 returnVal = field.value.replace(/"/g,'""');
19228 returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
19231 if (field.alignment && typeof(field.alignment) === 'string' ){
19232 returnVal = { text: returnVal, alignment: field.alignment };
19246 * @name ui.grid.exporter.directive:uiGridExporter
19250 * @description Adds exporter features to grid
19253 <example module="app">
19254 <file name="app.js">
19255 var app = angular.module('app', ['ui.grid', 'ui.grid.exporter']);
19257 app.controller('MainCtrl', ['$scope', function ($scope) {
19259 { name: 'Bob', title: 'CEO' },
19260 { name: 'Frank', title: 'Lowly Developer' }
19263 $scope.gridOptions = {
19264 enableGridMenu: true,
19265 exporterMenuCsv: false,
19267 {name: 'name', enableCellEdit: true},
19268 {name: 'title', enableCellEdit: true}
19274 <file name="index.html">
19275 <div ng-controller="MainCtrl">
19276 <div ui-grid="gridOptions" ui-grid-exporter></div>
19281 module.directive('uiGridExporter', ['uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
19282 function (uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
19286 require: '^uiGrid',
19288 link: function ($scope, $elm, $attrs, uiGridCtrl) {
19289 uiGridExporterService.initializeGrid(uiGridCtrl.grid);
19290 uiGridCtrl.grid.exporter.$scope = $scope;
19302 * @name ui.grid.grouping
19305 * # ui.grid.grouping
19307 * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
19309 * This module provides grouping of rows based on the data in them, similar
19310 * in concept to excel grouping. You can group multiple columns, resulting in
19313 * In concept this feature is similar to sorting + grid footer/aggregation, it
19314 * sorts the data based on the grouped columns, then creates group rows that
19315 * reflect a break in the data. Each of those group rows can have aggregations for
19316 * the data within that group.
19318 * This feature leverages treeBase to provide the tree functionality itself,
19319 * the key thing this feature does therefore is to set treeLevels on the rows
19320 * and insert the group headers.
19322 * Design information:
19323 * -------------------
19325 * Each column will get new menu items - group by, and aggregate by. Group by
19326 * will cause this column to be sorted (if not already), and will move this column
19327 * to the front of the sorted columns (i.e. grouped columns take precedence over
19328 * sorted columns). It will respect the sort order already set if there is one,
19329 * and it will allow the sorting logic to change that sort order, it just forces
19330 * the column to the front of the sorting. You can group by multiple columns, the
19331 * logic will add this column to the sorting after any already grouped columns.
19333 * Once a grouping is defined, grouping logic is added to the rowsProcessors. This
19334 * will process the rows, identifying a break in the data value, and inserting a grouping row.
19335 * Grouping rows have specific attributes on them:
19337 * - internalRow = true: tells us that this isn't a real row, so we can ignore it
19338 * from any processing that it looking at core data rows. This is used by the core
19339 * logic (or will be one day), as it's not grouping specific
19340 * - groupHeader = true: tells us this is a groupHeader. This is used by the grouping logic
19341 * to know if this is a groupHeader row or not
19343 * Since the logic is baked into the rowsProcessors, it should get triggered whenever
19344 * row order or filtering or anything like that is changed. In order to avoid the row instantiation
19345 * time, and to preserve state across invocations, we hold a cache of the rows that we created
19346 * last time, and we use them again this time if we can.
19348 * By default rows are collapsed, which means all data rows have their visible property
19349 * set to false, and only level 0 group rows are set to visible.
19354 * <div doc-module-components="ui.grid.grouping"></div>
19357 var module = angular.module('ui.grid.grouping', ['ui.grid', 'ui.grid.treeBase']);
19361 * @name ui.grid.grouping.constant:uiGridGroupingConstants
19363 * @description constants available in grouping module, this includes
19364 * all the constants declared in the treeBase module (these are manually copied
19365 * as there isn't an easy way to include constants in another constants file, and
19366 * we don't want to make users include treeBase)
19369 module.constant('uiGridGroupingConstants', {
19370 featureName: "grouping",
19371 rowHeaderColName: 'treeBaseRowHeaderCol',
19372 EXPANDED: 'expanded',
19373 COLLAPSED: 'collapsed',
19385 * @name ui.grid.grouping.service:uiGridGroupingService
19387 * @description Services for grouping features
19389 module.service('uiGridGroupingService', ['$q', 'uiGridGroupingConstants', 'gridUtil', 'rowSorter', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'uiGridTreeBaseService',
19390 function ($q, uiGridGroupingConstants, gridUtil, rowSorter, GridRow, gridClassFactory, i18nService, uiGridConstants, uiGridTreeBaseService) {
19394 initializeGrid: function (grid, $scope) {
19395 uiGridTreeBaseService.initializeGrid( grid, $scope );
19397 //add feature namespace and any properties to grid for needed
19400 * @name ui.grid.grouping.grid:grouping
19402 * @description Grid properties and functions added for grouping
19404 grid.grouping = {};
19408 * @propertyOf ui.grid.grouping.grid:grouping
19409 * @name groupHeaderCache
19411 * @description Cache that holds the group header rows we created last time, we'll
19412 * reuse these next time, not least because they hold our expanded states.
19414 * We need to take care with these that they don't become a memory leak, we
19415 * create a new cache each time using the values from the old cache. This works
19416 * so long as we're creating group rows for invisible rows as well.
19418 * The cache is a nested hash, indexed on the value we grouped by. So if we
19419 * grouped by gender then age, we'd maybe have something like:
19423 * row: <pointer to the old row>,
19425 * 22: { row: <pointer to the old row> },
19426 * 31: { row: <pointer to the old row> }
19429 * row: <pointer to the old row>,
19431 * 28: { row: <pointer to the old row> },
19432 * 55: { row: <pointer to the old row> }
19437 * We create new rows for any missing rows, this means that they come in as collapsed.
19440 grid.grouping.groupHeaderCache = {};
19442 service.defaultGridOptions(grid.options);
19444 grid.registerRowsProcessor(service.groupRows, 400);
19446 grid.registerColumnBuilder( service.groupingColumnBuilder);
19448 grid.registerColumnsProcessor(service.groupingColumnProcessor, 400);
19452 * @name ui.grid.grouping.api:PublicApi
19454 * @description Public Api for grouping feature
19461 * @eventOf ui.grid.grouping.api:PublicApi
19462 * @name aggregationChanged
19463 * @description raised whenever aggregation is changed, added or removed from a column
19466 * gridApi.grouping.on.aggregationChanged(scope,function(col){})
19468 * @param {gridCol} col the column which on which aggregation changed. The aggregation
19469 * type is available as `col.treeAggregation.type`
19471 aggregationChanged: {},
19474 * @eventOf ui.grid.grouping.api:PublicApi
19475 * @name groupingChanged
19476 * @description raised whenever the grouped columns changes
19479 * gridApi.grouping.on.groupingChanged(scope,function(col){})
19481 * @param {gridCol} col the column which on which grouping changed. The new grouping is
19482 * available as `col.grouping`
19484 groupingChanged: {}
19491 * @name getGrouping
19492 * @methodOf ui.grid.grouping.api:PublicApi
19493 * @description Get the grouping configuration for this grid,
19494 * used by the saveState feature. Adds expandedState to the information
19495 * provided by the internal getGrouping, and removes any aggregations that have a source
19496 * of grouping (i.e. will be automatically reapplied when we regroup the column)
19497 * Returned grouping is an object
19498 * `{ grouping: groupArray, treeAggregations: aggregateArray, expandedState: hash }`
19499 * where grouping contains an array of objects:
19500 * `{ field: column.field, colName: column.name, groupPriority: column.grouping.groupPriority }`
19501 * and aggregations contains an array of objects:
19502 * `{ field: column.field, colName: column.name, aggregation: column.grouping.aggregation }`
19503 * and expandedState is a hash of the currently expanded nodes
19505 * The groupArray will be sorted by groupPriority.
19507 * @param {boolean} getExpanded whether or not to return the expanded state
19508 * @returns {object} grouping configuration
19510 getGrouping: function ( getExpanded ) {
19511 var grouping = service.getGrouping(grid);
19513 grouping.grouping.forEach( function( group ) {
19514 group.colName = group.col.name;
19518 grouping.aggregations.forEach( function( aggregation ) {
19519 aggregation.colName = aggregation.col.name;
19520 delete aggregation.col;
19523 grouping.aggregations = grouping.aggregations.filter( function( aggregation ){
19524 return !aggregation.aggregation.source || aggregation.aggregation.source !== 'grouping';
19527 if ( getExpanded ){
19528 grouping.rowExpandedStates = service.getRowExpandedStates( grid.grouping.groupingHeaderCache );
19536 * @name setGrouping
19537 * @methodOf ui.grid.grouping.api:PublicApi
19538 * @description Set the grouping configuration for this grid,
19539 * used by the saveState feature, but can also be used by any
19540 * user to specify a combined grouping and aggregation configuration
19541 * @param {object} config the config you want to apply, in the format
19542 * provided out by getGrouping
19544 setGrouping: function ( config ) {
19545 service.setGrouping(grid, config);
19550 * @name groupColumn
19551 * @methodOf ui.grid.grouping.api:PublicApi
19552 * @description Adds this column to the existing grouping, at the end of the priority order.
19553 * If the column doesn't have a sort, adds one, by default ASC
19555 * This column will move to the left of any non-group columns, the
19556 * move is handled in a columnProcessor, so gets called as part of refresh
19558 * @param {string} columnName the name of the column we want to group
19560 groupColumn: function( columnName ) {
19561 var column = grid.getColumn(columnName);
19562 service.groupColumn(grid, column);
19567 * @name ungroupColumn
19568 * @methodOf ui.grid.grouping.api:PublicApi
19569 * @description Removes the groupPriority from this column. If the
19570 * column was previously aggregated the aggregation will come back.
19571 * The sort will remain.
19573 * This column will move to the right of any other group columns, the
19574 * move is handled in a columnProcessor, so gets called as part of refresh
19576 * @param {string} columnName the name of the column we want to ungroup
19578 ungroupColumn: function( columnName ) {
19579 var column = grid.getColumn(columnName);
19580 service.ungroupColumn(grid, column);
19585 * @name clearGrouping
19586 * @methodOf ui.grid.grouping.api:PublicApi
19587 * @description Clear any grouped columns and any aggregations. Doesn't remove sorting,
19588 * as we don't know whether that sorting was added by grouping or was there beforehand
19591 clearGrouping: function() {
19592 service.clearGrouping(grid);
19597 * @name aggregateColumn
19598 * @methodOf ui.grid.grouping.api:PublicApi
19599 * @description Sets the aggregation type on a column, if the
19600 * column is currently grouped then it removes the grouping first.
19601 * If the aggregationDef is null then will result in the aggregation
19604 * @param {string} columnName the column we want to aggregate
19605 * @param {string} or {function} aggregationDef one of the recognised types
19606 * from uiGridGroupingConstants or a custom aggregation function.
19607 * @param {string} aggregationLabel (optional) The label to use for this aggregation.
19609 aggregateColumn: function( columnName, aggregationDef, aggregationLabel){
19610 var column = grid.getColumn(columnName);
19611 service.aggregateColumn( grid, column, aggregationDef, aggregationLabel);
19618 grid.api.registerEventsFromObject(publicApi.events);
19620 grid.api.registerMethodsFromObject(publicApi.methods);
19622 grid.api.core.on.sortChanged( $scope, service.tidyPriorities);
19626 defaultGridOptions: function (gridOptions) {
19627 //default option to true unless it was explicitly set to false
19630 * @name ui.grid.grouping.api:GridOptions
19632 * @description GridOptions for grouping feature, these are available to be
19633 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
19638 * @name enableGrouping
19639 * @propertyOf ui.grid.grouping.api:GridOptions
19640 * @description Enable row grouping for entire grid.
19641 * <br/>Defaults to true
19643 gridOptions.enableGrouping = gridOptions.enableGrouping !== false;
19647 * @name groupingShowCounts
19648 * @propertyOf ui.grid.grouping.api:GridOptions
19649 * @description shows counts on the groupHeader rows. Not that if you are using a cellFilter or a
19650 * sortingAlgorithm which relies on a specific format or data type, showing counts may cause that
19651 * to break, since the group header rows will always be a string with groupingShowCounts enabled.
19652 * <br/>Defaults to true except on columns of types 'date' and 'object'
19654 gridOptions.groupingShowCounts = gridOptions.groupingShowCounts !== false;
19658 * @name groupingNullLabel
19659 * @propertyOf ui.grid.grouping.api:GridOptions
19660 * @description The string to use for the grouping header row label on rows which contain a null or undefined value in the grouped column.
19661 * <br/>Defaults to "Null"
19663 gridOptions.groupingNullLabel = typeof(gridOptions.groupingNullLabel) === 'undefined' ? 'Null' : gridOptions.groupingNullLabel;
19667 * @name enableGroupHeaderSelection
19668 * @propertyOf ui.grid.grouping.api:GridOptions
19669 * @description Allows group header rows to be selected.
19670 * <br/>Defaults to false
19672 gridOptions.enableGroupHeaderSelection = gridOptions.enableGroupHeaderSelection === true;
19678 * @name groupingColumnBuilder
19679 * @methodOf ui.grid.grouping.service:uiGridGroupingService
19680 * @description Sets the grouping defaults based on the columnDefs
19682 * @param {object} colDef columnDef we're basing on
19683 * @param {GridCol} col the column we're to update
19684 * @param {object} gridOptions the options we should use
19685 * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
19687 groupingColumnBuilder: function (colDef, col, gridOptions) {
19690 * @name ui.grid.grouping.api:ColumnDef
19692 * @description ColumnDef for grouping feature, these are available to be
19693 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
19698 * @name enableGrouping
19699 * @propertyOf ui.grid.grouping.api:ColumnDef
19700 * @description Enable grouping on this column
19701 * <br/>Defaults to true.
19703 if (colDef.enableGrouping === false){
19710 * @propertyOf ui.grid.grouping.api:ColumnDef
19711 * @description Set the grouping for a column. Format is:
19714 * groupPriority: <number, starts at 0, if less than 0 or undefined then we're aggregating in this column>
19718 * **Note that aggregation used to be included in grouping, but is now separately set on the column via treeAggregation
19719 * setting in treeBase**
19721 * We group in the priority order given, this will also put these columns to the high order of the sort irrespective
19722 * of the sort priority given them. If there is no sort defined then we sort ascending, if there is a sort defined then
19723 * we use that sort.
19725 * If the groupPriority is undefined or less than 0, then we expect to be aggregating, and we look at the
19726 * aggregation types to determine what sort of aggregation we can do. Values are in the constants file, but
19727 * include SUM, COUNT, MAX, MIN
19729 * groupPriorities should generally be sequential, if they're not then the next time getGrouping is called
19730 * we'll renumber them to be sequential.
19731 * <br/>Defaults to undefined.
19734 if ( typeof(col.grouping) === 'undefined' && typeof(colDef.grouping) !== 'undefined') {
19735 col.grouping = angular.copy(colDef.grouping);
19736 if ( typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority > -1 ){
19737 col.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
19738 col.treeAggregationFinalizerFn = service.groupedFinalizerFn;
19740 } else if (typeof(col.grouping) === 'undefined'){
19744 if (typeof(col.grouping) !== 'undefined' && typeof(col.grouping.groupPriority) !== 'undefined' && col.grouping.groupPriority >= 0){
19745 col.suppressRemoveSort = true;
19748 var groupColumn = {
19749 name: 'ui.grid.grouping.group',
19750 title: i18nService.get().grouping.group,
19751 icon: 'ui-grid-icon-indent-right',
19752 shown: function () {
19753 return typeof(this.context.col.grouping) === 'undefined' ||
19754 typeof(this.context.col.grouping.groupPriority) === 'undefined' ||
19755 this.context.col.grouping.groupPriority < 0;
19757 action: function () {
19758 service.groupColumn( this.context.col.grid, this.context.col );
19762 var ungroupColumn = {
19763 name: 'ui.grid.grouping.ungroup',
19764 title: i18nService.get().grouping.ungroup,
19765 icon: 'ui-grid-icon-indent-left',
19766 shown: function () {
19767 return typeof(this.context.col.grouping) !== 'undefined' &&
19768 typeof(this.context.col.grouping.groupPriority) !== 'undefined' &&
19769 this.context.col.grouping.groupPriority >= 0;
19771 action: function () {
19772 service.ungroupColumn( this.context.col.grid, this.context.col );
19776 var aggregateRemove = {
19777 name: 'ui.grid.grouping.aggregateRemove',
19778 title: i18nService.get().grouping.aggregate_remove,
19779 shown: function () {
19780 return typeof(this.context.col.treeAggregationFn) !== 'undefined';
19782 action: function () {
19783 service.aggregateColumn( this.context.col.grid, this.context.col, null);
19787 // generic adder for the aggregation menus, which follow a pattern
19788 var addAggregationMenu = function(type, title){
19789 title = title || i18nService.get().grouping['aggregate_' + type] || type;
19791 name: 'ui.grid.grouping.aggregate' + type,
19793 shown: function () {
19794 return typeof(this.context.col.treeAggregation) === 'undefined' ||
19795 typeof(this.context.col.treeAggregation.type) === 'undefined' ||
19796 this.context.col.treeAggregation.type !== type;
19798 action: function () {
19799 service.aggregateColumn( this.context.col.grid, this.context.col, type);
19803 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregate' + type)) {
19804 col.menuItems.push(menuItem);
19810 * @name groupingShowGroupingMenu
19811 * @propertyOf ui.grid.grouping.api:ColumnDef
19812 * @description Show the grouping (group and ungroup items) menu on this column
19813 * <br/>Defaults to true.
19815 if ( col.colDef.groupingShowGroupingMenu !== false ){
19816 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.group')) {
19817 col.menuItems.push(groupColumn);
19820 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.ungroup')) {
19821 col.menuItems.push(ungroupColumn);
19828 * @name groupingShowAggregationMenu
19829 * @propertyOf ui.grid.grouping.api:ColumnDef
19830 * @description Show the aggregation menu on this column
19831 * <br/>Defaults to true.
19833 if ( col.colDef.groupingShowAggregationMenu !== false ){
19834 angular.forEach(uiGridTreeBaseService.nativeAggregations(), function(aggregationDef, name){
19835 addAggregationMenu(name);
19837 angular.forEach(gridOptions.treeCustomAggregations, function(aggregationDef, name){
19838 addAggregationMenu(name, aggregationDef.menuTitle);
19841 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.grouping.aggregateRemove')) {
19842 col.menuItems.push(aggregateRemove);
19852 * @name groupingColumnProcessor
19853 * @methodOf ui.grid.grouping.service:uiGridGroupingService
19854 * @description Moves the columns around based on which are grouped
19856 * @param {array} columns the columns to consider rendering
19857 * @param {array} rows the grid rows, which we don't use but are passed to us
19858 * @returns {array} updated columns array
19860 groupingColumnProcessor: function( columns, rows ) {
19863 columns = service.moveGroupColumns(this, columns, rows);
19869 * @name groupedFinalizerFn
19870 * @methodOf ui.grid.grouping.service:uiGridGroupingService
19871 * @description Used on group columns to display the rendered value and optionally
19872 * display the count of rows.
19874 * @param {aggregation} the aggregation entity for a grouped column
19876 groupedFinalizerFn: function( aggregation ){
19879 if ( typeof(aggregation.groupVal) !== 'undefined') {
19880 aggregation.rendered = aggregation.groupVal;
19881 if ( col.grid.options.groupingShowCounts && col.colDef.type !== 'date' && col.colDef.type !== 'object' ){
19882 aggregation.rendered += (' (' + aggregation.value + ')');
19885 aggregation.rendered = null;
19891 * @name moveGroupColumns
19892 * @methodOf ui.grid.grouping.service:uiGridGroupingService
19893 * @description Moves the column order so that the grouped columns are lined up
19894 * to the left (well, unless you're RTL, then it's the right). By doing this in
19895 * the columnsProcessor, we make it transient - when the column is ungrouped it'll
19896 * go back to where it was.
19898 * Does nothing if the option `moveGroupColumns` is set to false.
19900 * @param {Grid} grid grid object
19901 * @param {array} columns the columns that we should process/move
19902 * @param {array} rows the grid rows
19903 * @returns {array} updated columns
19905 moveGroupColumns: function( grid, columns, rows ){
19906 if ( grid.options.moveGroupColumns === false){
19910 columns.forEach( function(column, index){
19911 // position used to make stable sort in moveGroupColumns
19912 column.groupingPosition = index;
19915 columns.sort(function(a, b){
19916 var a_group, b_group;
19917 if (a.isRowHeader){
19918 a_group = a.headerPriority;
19920 else if ( typeof(a.grouping) === 'undefined' || typeof(a.grouping.groupPriority) === 'undefined' || a.grouping.groupPriority < 0){
19923 a_group = a.grouping.groupPriority;
19926 if (b.isRowHeader){
19927 b_group = b.headerPriority;
19929 else if ( typeof(b.grouping) === 'undefined' || typeof(b.grouping.groupPriority) === 'undefined' || b.grouping.groupPriority < 0){
19932 b_group = b.grouping.groupPriority;
19935 // groups get sorted to the top
19936 if ( a_group !== null && b_group === null) { return -1; }
19937 if ( b_group !== null && a_group === null) { return 1; }
19938 if ( a_group !== null && b_group !== null) {return a_group - b_group; }
19940 return a.groupingPosition - b.groupingPosition;
19943 columns.forEach( function(column, index) {
19944 delete column.groupingPosition;
19953 * @name groupColumn
19954 * @methodOf ui.grid.grouping.service:uiGridGroupingService
19955 * @description Adds this column to the existing grouping, at the end of the priority order.
19956 * If the column doesn't have a sort, adds one, by default ASC
19958 * This column will move to the left of any non-group columns, the
19959 * move is handled in a columnProcessor, so gets called as part of refresh
19961 * @param {Grid} grid grid object
19962 * @param {GridCol} column the column we want to group
19964 groupColumn: function( grid, column){
19965 if ( typeof(column.grouping) === 'undefined' ){
19966 column.grouping = {};
19969 // set the group priority to the next number in the hierarchy
19970 var existingGrouping = service.getGrouping( grid );
19971 column.grouping.groupPriority = existingGrouping.grouping.length;
19973 // save sort in order to restore it when column is ungrouped
19974 column.previousSort = angular.copy(column.sort);
19976 // add sort if not present
19977 if ( !column.sort ){
19978 column.sort = { direction: uiGridConstants.ASC };
19979 } else if ( typeof(column.sort.direction) === 'undefined' || column.sort.direction === null ){
19980 column.sort.direction = uiGridConstants.ASC;
19983 column.treeAggregation = { type: uiGridGroupingConstants.aggregation.COUNT, source: 'grouping' };
19984 column.treeAggregationFn = uiGridTreeBaseService.nativeAggregations()[uiGridGroupingConstants.aggregation.COUNT].aggregationFn;
19985 column.treeAggregationFinalizerFn = service.groupedFinalizerFn;
19987 grid.api.grouping.raise.groupingChanged(column);
19988 // This indirectly calls service.tidyPriorities( grid );
19989 grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
19991 grid.queueGridRefresh();
19997 * @name ungroupColumn
19998 * @methodOf ui.grid.grouping.service:uiGridGroupingService
19999 * @description Removes the groupPriority from this column. If the
20000 * column was previously aggregated the aggregation will come back.
20001 * The sort will remain.
20003 * This column will move to the right of any other group columns, the
20004 * move is handled in a columnProcessor, so gets called as part of refresh
20006 * @param {Grid} grid grid object
20007 * @param {GridCol} column the column we want to ungroup
20009 ungroupColumn: function( grid, column){
20010 if ( typeof(column.grouping) === 'undefined' ){
20014 delete column.grouping.groupPriority;
20015 delete column.treeAggregation;
20016 delete column.customTreeAggregationFinalizer;
20018 if (column.previousSort) {
20019 column.sort = column.previousSort;
20020 delete column.previousSort;
20023 service.tidyPriorities( grid );
20025 grid.api.grouping.raise.groupingChanged(column);
20026 grid.api.core.raise.sortChanged(grid, grid.getColumnSorting());
20028 grid.queueGridRefresh();
20033 * @name aggregateColumn
20034 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20035 * @description Sets the aggregation type on a column, if the
20036 * column is currently grouped then it removes the grouping first.
20038 * @param {Grid} grid grid object
20039 * @param {GridCol} column the column we want to aggregate
20040 * @param {string} one of the recognised types from uiGridGroupingConstants or one of the custom aggregations from gridOptions
20042 aggregateColumn: function( grid, column, aggregationType){
20044 if (typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
20045 service.ungroupColumn( grid, column );
20048 var aggregationDef = {};
20049 if ( typeof(grid.options.treeCustomAggregations[aggregationType]) !== 'undefined' ){
20050 aggregationDef = grid.options.treeCustomAggregations[aggregationType];
20051 } else if ( typeof(uiGridTreeBaseService.nativeAggregations()[aggregationType]) !== 'undefined' ){
20052 aggregationDef = uiGridTreeBaseService.nativeAggregations()[aggregationType];
20055 column.treeAggregation = { type: aggregationType, label: i18nService.get().aggregation[aggregationDef.label] || aggregationDef.label };
20056 column.treeAggregationFn = aggregationDef.aggregationFn;
20057 column.treeAggregationFinalizerFn = aggregationDef.finalizerFn;
20059 grid.api.grouping.raise.aggregationChanged(column);
20061 grid.queueGridRefresh();
20067 * @name setGrouping
20068 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20069 * @description Set the grouping based on a config object, used by the save state feature
20070 * (more specifically, by the restore function in that feature )
20072 * @param {Grid} grid grid object
20073 * @param {object} config the config we want to set, same format as that returned by getGrouping
20075 setGrouping: function ( grid, config ){
20076 if ( typeof(config) === 'undefined' ){
20080 // first remove any existing grouping
20081 service.clearGrouping(grid);
20083 if ( config.grouping && config.grouping.length && config.grouping.length > 0 ){
20084 config.grouping.forEach( function( group ) {
20085 var col = grid.getColumn(group.colName);
20088 service.groupColumn( grid, col );
20093 if ( config.aggregations && config.aggregations.length ){
20094 config.aggregations.forEach( function( aggregation ) {
20095 var col = grid.getColumn(aggregation.colName);
20098 service.aggregateColumn( grid, col, aggregation.aggregation.type );
20103 if ( config.rowExpandedStates ){
20104 service.applyRowExpandedStates( grid.grouping.groupingHeaderCache, config.rowExpandedStates );
20111 * @name clearGrouping
20112 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20113 * @description Clear any grouped columns and any aggregations. Doesn't remove sorting,
20114 * as we don't know whether that sorting was added by grouping or was there beforehand
20116 * @param {Grid} grid grid object
20118 clearGrouping: function( grid ) {
20119 var currentGrouping = service.getGrouping(grid);
20121 if ( currentGrouping.grouping.length > 0 ){
20122 currentGrouping.grouping.forEach( function( group ) {
20124 // should have a group.colName if there's no col
20125 group.col = grid.getColumn(group.colName);
20127 service.ungroupColumn(grid, group.col);
20131 if ( currentGrouping.aggregations.length > 0 ){
20132 currentGrouping.aggregations.forEach( function( aggregation ){
20133 if (!aggregation.col){
20134 // should have a group.colName if there's no col
20135 aggregation.col = grid.getColumn(aggregation.colName);
20137 service.aggregateColumn(grid, aggregation.col, null);
20145 * @name tidyPriorities
20146 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20147 * @description Renumbers groupPriority and sortPriority such that
20148 * groupPriority is contiguous, and sortPriority either matches
20149 * groupPriority (for group columns), and otherwise is contiguous and
20150 * higher than groupPriority.
20152 * @param {Grid} grid grid object
20154 tidyPriorities: function( grid ){
20155 // if we're called from sortChanged, grid is in this, not passed as param, the param can be a column or undefined
20156 if ( ( typeof(grid) === 'undefined' || typeof(grid.grid) !== 'undefined' ) && typeof(this.grid) !== 'undefined' ) {
20160 var groupArray = [];
20161 var sortArray = [];
20163 grid.columns.forEach( function(column, index){
20164 if ( typeof(column.grouping) !== 'undefined' && typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
20165 groupArray.push(column);
20166 } else if ( typeof(column.sort) !== 'undefined' && typeof(column.sort.priority) !== 'undefined' && column.sort.priority >= 0){
20167 sortArray.push(column);
20171 groupArray.sort(function(a, b){ return a.grouping.groupPriority - b.grouping.groupPriority; });
20172 groupArray.forEach( function(column, index){
20173 column.grouping.groupPriority = index;
20174 column.suppressRemoveSort = true;
20175 if ( typeof(column.sort) === 'undefined'){
20178 column.sort.priority = index;
20181 var i = groupArray.length;
20182 sortArray.sort(function(a, b){ return a.sort.priority - b.sort.priority; });
20183 sortArray.forEach( function(column, index){
20184 column.sort.priority = i;
20185 column.suppressRemoveSort = column.colDef.suppressRemoveSort;
20194 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20195 * @description The rowProcessor that creates the groupHeaders (i.e. does
20196 * the actual grouping).
20198 * Assumes it is always called after the sorting processor, guaranteed by the priority setting
20200 * Processes all the rows in order, inserting a groupHeader row whenever there is a change
20201 * in value of a grouped row, based on the sortAlgorithm used for the column. The group header row
20202 * is looked up in the groupHeaderCache, and used from there if there is one. The entity is reset
20203 * to {} if one is found.
20205 * As it processes it maintains a `processingState` array. This records, for each level of grouping we're
20206 * working with, the following information:
20211 * initialised: boolean,
20212 * currentValue: value,
20213 * currentRow: gridRow,
20216 * We look for changes in the currentValue at any of the levels. Where we find a change we:
20218 * - create a new groupHeader row in the array
20220 * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
20221 * @returns {array} the updated rows, including our new group rows
20223 groupRows: function( renderableRows ) {
20224 if (renderableRows.length === 0){
20225 return renderableRows;
20229 grid.grouping.oldGroupingHeaderCache = grid.grouping.groupingHeaderCache || {};
20230 grid.grouping.groupingHeaderCache = {};
20232 var processingState = service.initialiseProcessingState( grid );
20234 // processes each of the fields we are grouping by, checks if the value has changed and inserts a groupHeader
20235 // Broken out as shouldn't create functions in a loop.
20236 var updateProcessingState = function( groupFieldState, stateIndex ) {
20237 var fieldValue = grid.getCellValue(row, groupFieldState.col);
20239 // look for change of value - and insert a header
20240 if ( !groupFieldState.initialised || rowSorter.getSortFn(grid, groupFieldState.col, renderableRows)(fieldValue, groupFieldState.currentValue) !== 0 ){
20241 service.insertGroupHeader( grid, renderableRows, i, processingState, stateIndex );
20246 // use a for loop because it's tolerant of the array length changing whilst we go - we can
20247 // manipulate the iterator when we insert groupHeader rows
20248 for (var i = 0; i < renderableRows.length; i++ ){
20249 var row = renderableRows[i];
20251 if ( row.visible ){
20252 processingState.forEach( updateProcessingState );
20256 delete grid.grouping.oldGroupingHeaderCache;
20257 return renderableRows;
20263 * @name initialiseProcessingState
20264 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20265 * @description Creates the processing state array that is used
20268 * @param {Grid} grid grid object
20269 * @returns {array} an array in the format described in the groupRows method,
20270 * initialised with blank values
20272 initialiseProcessingState: function( grid ){
20273 var processingState = [];
20274 var columnSettings = service.getGrouping( grid );
20276 columnSettings.grouping.forEach( function( groupItem, index){
20277 processingState.push({
20278 fieldName: groupItem.field,
20279 col: groupItem.col,
20280 initialised: false,
20281 currentValue: null,
20286 return processingState;
20292 * @name getGrouping
20293 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20294 * @description Get the grouping settings from the columns. As a side effect
20295 * this always renumbers the grouping starting at 0
20296 * @param {Grid} grid grid object
20297 * @returns {array} an array of the group fields, in order of priority
20299 getGrouping: function( grid ){
20300 var groupArray = [];
20301 var aggregateArray = [];
20303 // get all the grouping
20304 grid.columns.forEach( function(column, columnIndex){
20305 if ( column.grouping ){
20306 if ( typeof(column.grouping.groupPriority) !== 'undefined' && column.grouping.groupPriority >= 0){
20307 groupArray.push({ field: column.field, col: column, groupPriority: column.grouping.groupPriority, grouping: column.grouping });
20310 if ( column.treeAggregation && column.treeAggregation.type ){
20311 aggregateArray.push({ field: column.field, col: column, aggregation: column.treeAggregation });
20315 // sort grouping into priority order
20316 groupArray.sort( function(a, b){
20317 return a.groupPriority - b.groupPriority;
20320 // renumber the priority in case it was somewhat messed up, then remove the grouping reference
20321 groupArray.forEach( function( group, index) {
20322 group.grouping.groupPriority = index;
20323 group.groupPriority = index;
20324 delete group.grouping;
20327 return { grouping: groupArray, aggregations: aggregateArray };
20333 * @name insertGroupHeader
20334 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20335 * @description Create a group header row, and link it to the various configuration
20336 * items that we use.
20338 * Look for the row in the oldGroupingHeaderCache, write the row into the new groupingHeaderCache.
20340 * @param {Grid} grid grid object
20341 * @param {array} renderableRows the rows that we are processing
20342 * @param {number} rowIndex the row we were up to processing
20343 * @param {array} processingState the current processing state
20344 * @param {number} stateIndex the processing state item that we were on when we triggered a new group header -
20345 * i.e. the column that we want to create a header for
20347 insertGroupHeader: function( grid, renderableRows, rowIndex, processingState, stateIndex ) {
20348 // set the value that caused the end of a group into the header row and the processing state
20349 var fieldName = processingState[stateIndex].fieldName;
20350 var col = processingState[stateIndex].col;
20352 var newValue = grid.getCellValue(renderableRows[rowIndex], col);
20353 var newDisplayValue = newValue;
20354 if ( typeof(newValue) === 'undefined' || newValue === null ) {
20355 newDisplayValue = grid.options.groupingNullLabel;
20358 var getKeyAsValueForCacheMap = function(key) {
20359 if (angular.isObject(key)) {
20360 return JSON.stringify(key);
20366 var cacheItem = grid.grouping.oldGroupingHeaderCache;
20367 for ( var i = 0; i < stateIndex; i++ ){
20368 if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)] ){
20369 cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
20374 if ( cacheItem && cacheItem[getKeyAsValueForCacheMap(newValue)]){
20375 headerRow = cacheItem[getKeyAsValueForCacheMap(newValue)].row;
20376 headerRow.entity = {};
20378 headerRow = new GridRow( {}, null, grid );
20379 gridClassFactory.rowTemplateAssigner.call(grid, headerRow);
20382 headerRow.entity['$$' + processingState[stateIndex].col.uid] = { groupVal: newDisplayValue };
20383 headerRow.treeLevel = stateIndex;
20384 headerRow.groupHeader = true;
20385 headerRow.internalRow = true;
20386 headerRow.enableCellEdit = false;
20387 headerRow.enableSelection = grid.options.enableGroupHeaderSelection;
20388 processingState[stateIndex].initialised = true;
20389 processingState[stateIndex].currentValue = newValue;
20390 processingState[stateIndex].currentRow = headerRow;
20392 // set all processing states below this one to not be initialised - change of this state
20393 // means all those need to start again
20394 service.finaliseProcessingState( processingState, stateIndex + 1);
20396 // insert our new header row
20397 renderableRows.splice(rowIndex, 0, headerRow);
20399 // add our new header row to the cache
20400 cacheItem = grid.grouping.groupingHeaderCache;
20401 for ( i = 0; i < stateIndex; i++ ){
20402 cacheItem = cacheItem[getKeyAsValueForCacheMap(processingState[i].currentValue)].children;
20404 cacheItem[getKeyAsValueForCacheMap(newValue)] = { row: headerRow, children: {} };
20410 * @name finaliseProcessingState
20411 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20412 * @description Set all processing states lower than the one that had a break in value to
20413 * no longer be initialised. Render the counts into the entity ready for display.
20415 * @param {Grid} grid grid object
20416 * @param {array} processingState the current processing state
20417 * @param {number} stateIndex the processing state item that we were on when we triggered a new group header, all
20418 * processing states after this need to be finalised
20420 finaliseProcessingState: function( processingState, stateIndex ){
20421 for ( var i = stateIndex; i < processingState.length; i++){
20422 processingState[i].initialised = false;
20423 processingState[i].currentRow = null;
20424 processingState[i].currentValue = null;
20431 * @name getRowExpandedStates
20432 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20433 * @description Extract the groupHeaderCache hash, pulling out only the states.
20435 * The example below shows a grid that is grouped by gender then age
20440 * state: 'expanded',
20442 * 22: { state: 'expanded' },
20443 * 30: { state: 'collapsed' }
20447 * state: 'expanded',
20449 * 28: { state: 'expanded' },
20450 * 55: { state: 'collapsed' }
20456 * @param {Grid} grid grid object
20457 * @returns {hash} the expanded states as a hash
20459 getRowExpandedStates: function(treeChildren){
20460 if ( typeof(treeChildren) === 'undefined' ){
20464 var newChildren = {};
20466 angular.forEach( treeChildren, function( value, key ){
20467 newChildren[key] = { state: value.row.treeNode.state };
20468 if ( value.children ){
20469 newChildren[key].children = service.getRowExpandedStates( value.children );
20471 newChildren[key].children = {};
20475 return newChildren;
20481 * @name applyRowExpandedStates
20482 * @methodOf ui.grid.grouping.service:uiGridGroupingService
20483 * @description Take a hash in the format as created by getRowExpandedStates,
20484 * and apply it to the grid.grouping.groupHeaderCache.
20486 * Takes a treeSubset, and applies to a treeSubset - so can be called
20489 * @param {object} currentNode can be grid.grouping.groupHeaderCache, or any of
20490 * the children of that hash
20491 * @returns {hash} expandedStates can be the full expanded states, or children
20492 * of that expanded states (which hopefully matches the subset of the groupHeaderCache)
20494 applyRowExpandedStates: function( currentNode, expandedStates ){
20495 if ( typeof(expandedStates) === 'undefined' ){
20499 angular.forEach(expandedStates, function( value, key ) {
20500 if ( currentNode[key] ){
20501 currentNode[key].row.treeNode.state = value.state;
20503 if (value.children && currentNode[key].children){
20504 service.applyRowExpandedStates( currentNode[key].children, value.children );
20520 * @name ui.grid.grouping.directive:uiGridGrouping
20524 * @description Adds grouping features to grid
20527 <example module="app">
20528 <file name="app.js">
20529 var app = angular.module('app', ['ui.grid', 'ui.grid.grouping']);
20531 app.controller('MainCtrl', ['$scope', function ($scope) {
20533 { name: 'Bob', title: 'CEO' },
20534 { name: 'Frank', title: 'Lowly Developer' }
20537 $scope.columnDefs = [
20538 {name: 'name', enableCellEdit: true},
20539 {name: 'title', enableCellEdit: true}
20542 $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
20545 <file name="index.html">
20546 <div ng-controller="MainCtrl">
20547 <div ui-grid="gridOptions" ui-grid-grouping></div>
20552 module.directive('uiGridGrouping', ['uiGridGroupingConstants', 'uiGridGroupingService', '$templateCache',
20553 function (uiGridGroupingConstants, uiGridGroupingService, $templateCache) {
20557 require: '^uiGrid',
20559 compile: function () {
20561 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
20562 if (uiGridCtrl.grid.options.enableGrouping !== false){
20563 uiGridGroupingService.initializeGrid(uiGridCtrl.grid, $scope);
20566 post: function ($scope, $elm, $attrs, uiGridCtrl) {
20580 * @name ui.grid.importer
20583 * # ui.grid.importer
20585 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
20587 * This module provides the ability to import data into the grid. It
20588 * uses the column defs to work out which data belongs in which column,
20589 * and creates entities from a configured class (typically a $resource).
20591 * If the rowEdit feature is enabled, it also calls save on those newly
20592 * created objects, and then displays any errors in the imported data.
20594 * Currently the importer imports only CSV and json files, although provision has been
20595 * made to process other file formats, and these can be added over time.
20597 * For json files, the properties within each object in the json must match the column names
20598 * (to put it another way, the importer doesn't process the json, it just copies the objects
20599 * within the json into a new instance of the specified object type)
20601 * For CSV import, the default column identification relies on each column in the
20602 * header row matching a column.name or column.displayName. Optionally, a column identification
20603 * callback can be used. This allows matching using other attributes, which is particularly
20604 * useful if your application has internationalised column headings (i.e. the headings that
20605 * the user sees don't match the column names).
20607 * The importer makes use of the grid menu as the UI for requesting an
20610 * <div ui-grid-importer></div>
20613 var module = angular.module('ui.grid.importer', ['ui.grid']);
20617 * @name ui.grid.importer.constant:uiGridImporterConstants
20619 * @description constants available in importer module
20622 module.constant('uiGridImporterConstants', {
20623 featureName: 'importer'
20628 * @name ui.grid.importer.service:uiGridImporterService
20630 * @description Services for importer feature
20632 module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window',
20633 function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) {
20637 initializeGrid: function ($scope, grid) {
20639 //add feature namespace and any properties to grid for needed state
20644 this.defaultGridOptions(grid.options);
20648 * @name ui.grid.importer.api:PublicApi
20650 * @description Public Api for importer feature
20662 * @methodOf ui.grid.importer.api:PublicApi
20663 * @description Imports a file into the grid using the file object
20664 * provided. Bypasses the grid menu
20665 * @param {File} fileObject the file we want to import, as a javascript
20668 importFile: function ( fileObject ) {
20669 service.importThisFile( grid, fileObject );
20675 grid.api.registerEventsFromObject(publicApi.events);
20677 grid.api.registerMethodsFromObject(publicApi.methods);
20679 if ( grid.options.enableImporter && grid.options.importerShowMenu ){
20680 if ( grid.api.core.addToGridMenu ){
20681 service.addToMenu( grid );
20683 // order of registration is not guaranteed, register in a little while
20684 $interval( function() {
20685 if (grid.api.core.addToGridMenu){
20686 service.addToMenu( grid );
20694 defaultGridOptions: function (gridOptions) {
20695 //default option to true unless it was explicitly set to false
20698 * @name ui.grid.importer.api:GridOptions
20700 * @description GridOptions for importer feature, these are available to be
20701 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
20706 * @propertyOf ui.grid.importer.api:GridOptions
20707 * @name enableImporter
20708 * @description Whether or not importer is enabled. Automatically set
20709 * to false if the user's browser does not support the required fileApi.
20710 * Otherwise defaults to true.
20713 if (gridOptions.enableImporter || gridOptions.enableImporter === undefined) {
20714 if ( !($window.hasOwnProperty('File') && $window.hasOwnProperty('FileReader') && $window.hasOwnProperty('FileList') && $window.hasOwnProperty('Blob')) ) {
20715 gridUtil.logError('The File APIs are not fully supported in this browser, grid importer cannot be used.');
20716 gridOptions.enableImporter = false;
20718 gridOptions.enableImporter = true;
20721 gridOptions.enableImporter = false;
20726 * @name importerProcessHeaders
20727 * @methodOf ui.grid.importer.api:GridOptions
20728 * @description A callback function that will process headers using custom
20729 * logic. Set this callback function if the headers that your user will provide in their
20730 * import file don't necessarily match the grid header or field names. This might commonly
20731 * occur where your application is internationalised, and therefore the field names
20732 * that the user recognises are in a different language than the field names that
20733 * ui-grid knows about.
20735 * Defaults to the internal `processHeaders` method, which seeks to match using both
20736 * displayName and column.name. Any non-matching columns are discarded.
20738 * Your callback routine should respond by processing the header array, and returning an array
20739 * of matching column names. A null value in any given position means "don't import this column"
20742 * gridOptions.importerProcessHeaders: function( headerArray ) {
20743 * var myHeaderColumns = [];
20745 * headerArray.forEach( function( value, index ) {
20746 * thisCol = mySpecialLookupFunction( value );
20747 * myHeaderColumns.push( thisCol.name );
20750 * return myHeaderCols;
20753 * @param {Grid} grid the grid we're importing into
20754 * @param {array} headerArray an array of the text from the first row of the csv file,
20755 * which you need to match to column.names
20756 * @returns {array} array of matching column names, in the same order as the headerArray
20759 gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders;
20763 * @name importerHeaderFilter
20764 * @methodOf ui.grid.importer.api:GridOptions
20765 * @description A callback function that will filter (usually translate) a single
20766 * header. Used when you want to match the passed in column names to the column
20767 * displayName after the header filter.
20769 * Your callback routine needs to return the filtered header value.
20771 * gridOptions.importerHeaderFilter: function( displayName ) {
20772 * return $translate.instant( displayName );
20778 * gridOptions.importerHeaderFilter: $translate.instant
20780 * @param {string} displayName the displayName that we'd like to translate
20781 * @returns {string} the translated name
20784 gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; };
20788 * @name importerErrorCallback
20789 * @methodOf ui.grid.importer.api:GridOptions
20790 * @description A callback function that provides custom error handling, rather
20791 * than the standard grid behaviour of an alert box and a console message. You
20792 * might use this to internationalise the console log messages, or to write to a
20793 * custom logging routine that returned errors to the server.
20796 * gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) {
20797 * myUserDisplayRoutine( errorKey );
20798 * myLoggingRoutine( consoleMessage, context );
20801 * @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages
20803 * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders,
20804 * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
20805 * @param {string} consoleMessage the English console message that importer would have written
20806 * @param {object} context the context data that importer would have appended to that console message,
20807 * often the file content itself or the element that is in error
20810 if ( !gridOptions.importerErrorCallback || typeof(gridOptions.importerErrorCallback) !== 'function' ){
20811 delete gridOptions.importerErrorCallback;
20816 * @name importerDataAddCallback
20817 * @methodOf ui.grid.importer.api:GridOptions
20818 * @description A mandatory callback function that adds data to the source data array. The grid
20819 * generally doesn't add rows to the source data array, it is tidier to handle this through a user
20823 * gridOptions.importerDataAddCallback: function( grid, newObjects ) {
20824 * $scope.myData = $scope.myData.concat( newObjects );
20827 * @param {Grid} grid the grid we're importing into, may be useful in some way
20828 * @param {array} newObjects an array of new objects that you should add to your data
20831 if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) {
20832 gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled");
20833 gridOptions.enableImporter = false;
20838 * @name importerNewObject
20839 * @propertyOf ui.grid.importer.api:GridOptions
20840 * @description An object on which we call `new` to create each new row before inserting it into
20841 * the data array. Typically this would be a $resource entity, which means that if you're using
20842 * the rowEdit feature, you can directly call save on this entity when the save event is triggered.
20844 * Defaults to a vanilla javascript object
20848 * gridOptions.importerNewObject = MyRes;
20855 * @propertyOf ui.grid.importer.api:GridOptions
20856 * @name importerShowMenu
20857 * @description Whether or not to show an item in the grid menu. Defaults to true.
20860 gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false;
20864 * @methodOf ui.grid.importer.api:GridOptions
20865 * @name importerObjectCallback
20866 * @description A callback that massages the data for each object. For example,
20867 * you might have data stored as a code value, but display the decode. This callback
20868 * can be used to change the decoded value back into a code. Defaults to doing nothing.
20869 * @param {Grid} grid in case you need it
20870 * @param {object} newObject the new object as importer has created it, modify it
20871 * then return the modified version
20872 * @returns {object} the modified object
20875 * gridOptions.importerObjectCallback = function ( grid, newObject ) {
20876 * switch newObject.status {
20878 * newObject.status = 1;
20881 * newObject.status = 2;
20884 * return newObject;
20888 gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; };
20895 * @methodOf ui.grid.importer.service:uiGridImporterService
20896 * @description Adds import menu item to the grid menu,
20897 * allowing the user to request import of a file
20898 * @param {Grid} grid the grid into which data should be imported
20900 addToMenu: function ( grid ) {
20901 grid.api.core.addToGridMenu( grid, [
20903 title: i18nService.getSafeText('gridMenu.importerTitle'),
20907 templateUrl: 'ui-grid/importerMenuItemContainer',
20908 action: function ($event) {
20909 this.grid.api.importer.importAFile( grid );
20919 * @name importThisFile
20920 * @methodOf ui.grid.importer.service:uiGridImporterService
20921 * @description Imports the provided file into the grid using the file object
20922 * provided. Bypasses the grid menu
20923 * @param {Grid} grid the grid we're importing into
20924 * @param {File} fileObject the file we want to import, as returned from the File
20925 * javascript object
20927 importThisFile: function ( grid, fileObject ) {
20929 gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
20933 var reader = new FileReader();
20935 switch ( fileObject.type ){
20936 case 'application/json':
20937 reader.onload = service.importJsonClosure( grid );
20940 reader.onload = service.importCsvClosure( grid );
20944 reader.readAsText( fileObject );
20951 * @methodOf ui.grid.importer.service:uiGridImporterService
20952 * @description Creates a function that imports a json file into the grid.
20953 * The json data is imported into new objects of type `gridOptions.importerNewObject`,
20954 * and if the rowEdit feature is enabled the rows are marked as dirty
20955 * @param {Grid} grid the grid we want to import into
20956 * @param {FileObject} importFile the file that we want to import, as
20959 importJsonClosure: function( grid ) {
20960 return function( importFile ){
20961 var newObjects = [];
20964 var importArray = service.parseJson( grid, importFile );
20965 if (importArray === null){
20968 importArray.forEach( function( value, index ) {
20969 newObject = service.newObject( grid );
20970 angular.extend( newObject, value );
20971 newObject = grid.options.importerObjectCallback( grid, newObject );
20972 newObjects.push( newObject );
20975 service.addObjects( grid, newObjects );
20984 * @methodOf ui.grid.importer.service:uiGridImporterService
20985 * @description Parses a json file, returns the parsed data.
20986 * Displays an error if file doesn't parse
20987 * @param {Grid} grid the grid that we want to import into
20988 * @param {FileObject} importFile the file that we want to import, as
20990 * @returns {array} array of objects from the imported json
20992 parseJson: function( grid, importFile ){
20995 loadedObjects = JSON.parse( importFile.target.result );
20997 service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result );
21001 if ( !Array.isArray( loadedObjects ) ){
21002 service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
21005 return loadedObjects;
21013 * @name importCsvClosure
21014 * @methodOf ui.grid.importer.service:uiGridImporterService
21015 * @description Creates a function that imports a csv file into the grid
21016 * (allowing it to be used in the reader.onload event)
21017 * @param {Grid} grid the grid that we want to import into
21018 * @param {FileObject} importFile the file that we want to import, as
21021 importCsvClosure: function( grid ) {
21022 return function( importFile ){
21023 var importArray = service.parseCsv( importFile );
21024 if ( !importArray || importArray.length < 1 ){
21025 service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
21029 var newObjects = service.createCsvObjects( grid, importArray );
21030 if ( !newObjects || newObjects.length === 0 ){
21031 service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
21035 service.addObjects( grid, newObjects );
21043 * @methodOf ui.grid.importer.service:uiGridImporterService
21044 * @description Parses a csv file into an array of arrays, with the first
21045 * array being the headers, and the remaining arrays being the data.
21046 * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js,
21047 * which is noted as being under the MIT license. The code is modified to pass the jscs yoda condition
21049 * @param {FileObject} importFile the file that we want to import, as a
21052 parseCsv: function( importFile ) {
21053 var csv = importFile.target.result;
21055 // use the CSV-JS library to parse
21056 return CSV.parse(csv);
21062 * @name createCsvObjects
21063 * @methodOf ui.grid.importer.service:uiGridImporterService
21064 * @description Converts an array of arrays (representing the csv file)
21065 * into a set of objects. Uses the provided `gridOptions.importerNewObject`
21066 * to create the objects, and maps the header row into the individual columns
21067 * using either `gridOptions.importerProcessHeaders`, or by using a native method
21068 * of matching to either the displayName, column name or column field of
21069 * the columns in the column defs. The resulting objects will have attributes
21070 * that are named based on the column.field or column.name, in that order.
21071 * @param {Grid} grid the grid that we want to import into
21072 * @param {Array} importArray the data that we want to import, as an array
21074 createCsvObjects: function( grid, importArray ){
21075 // pull off header row and turn into headers
21076 var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
21077 if ( !headerMapping || headerMapping.length === 0 ){
21078 service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
21082 var newObjects = [];
21084 importArray.forEach( function( row, index ) {
21085 newObject = service.newObject( grid );
21086 if ( row !== null ){
21087 row.forEach( function( field, index ){
21088 if ( headerMapping[index] !== null ){
21089 newObject[ headerMapping[index] ] = field;
21093 newObject = grid.options.importerObjectCallback( grid, newObject );
21094 newObjects.push( newObject );
21103 * @name processHeaders
21104 * @methodOf ui.grid.importer.service:uiGridImporterService
21105 * @description Determines the columns that the header row from
21106 * a csv (or other) file represents.
21107 * @param {Grid} grid the grid we're importing into
21108 * @param {array} headerRow the header row that we wish to match against
21109 * the column definitions
21110 * @returns {array} an array of the attribute names that should be used
21111 * for that column, based on matching the headers or creating the headers
21114 processHeaders: function( grid, headerRow ) {
21116 if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
21117 // we are going to create new columnDefs for all these columns, so just remove
21118 // spaces from the names to create fields
21119 headerRow.forEach( function( value, index ) {
21120 headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
21124 var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
21125 headerRow.forEach( function( value, index ) {
21126 if ( lookupHash[value] ) {
21127 headers.push( lookupHash[value] );
21128 } else if ( lookupHash[ value.toLowerCase() ] ) {
21129 headers.push( lookupHash[ value.toLowerCase() ] );
21131 headers.push( null );
21140 * @name flattenColumnDefs
21141 * @methodOf ui.grid.importer.service:uiGridImporterService
21142 * @description Runs through the column defs and creates a hash of
21143 * the displayName, name and field, and of each of those values forced to lower case,
21144 * with each pointing to the field or name
21145 * (whichever is present). Used to lookup column headers and decide what
21146 * attribute name to give to the resulting field.
21147 * @param {Grid} grid the grid we're importing into
21148 * @param {array} columnDefs the columnDefs that we should flatten
21149 * @returns {hash} the flattened version of the column def information, allowing
21150 * us to look up a value by `flattenedHash[ headerValue ]`
21152 flattenColumnDefs: function( grid, columnDefs ){
21153 var flattenedHash = {};
21154 columnDefs.forEach( function( columnDef, index) {
21155 if ( columnDef.name ){
21156 flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
21157 flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
21160 if ( columnDef.field ){
21161 flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
21162 flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
21165 if ( columnDef.displayName ){
21166 flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
21167 flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
21170 if ( columnDef.displayName && grid.options.importerHeaderFilter ){
21171 flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
21172 flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
21176 return flattenedHash;
21183 * @methodOf ui.grid.importer.service:uiGridImporterService
21184 * @description Inserts our new objects into the grid data, and
21185 * sets the rows to dirty if the rowEdit feature is being used
21187 * Does this by registering a watch on dataChanges, which essentially
21188 * is waiting on the result of the grid data watch, and downstream processing.
21190 * When the callback is called, it deregisters itself - we don't want to run
21191 * again next time data is added.
21193 * If we never get called, we deregister on destroy.
21195 * @param {Grid} grid the grid we're importing into
21196 * @param {array} newObjects the objects we want to insert into the grid data
21197 * @returns {object} the new object
21199 addObjects: function( grid, newObjects, $scope ){
21200 if ( grid.api.rowEdit ){
21201 var dataChangeDereg = grid.registerDataChangeCallback( function() {
21202 grid.api.rowEdit.setRowsDirty( newObjects );
21204 }, [uiGridConstants.dataChange.ROW] );
21206 grid.importer.$scope.$on( '$destroy', dataChangeDereg );
21209 grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) );
21217 * @methodOf ui.grid.importer.service:uiGridImporterService
21218 * @description Makes a new object based on `gridOptions.importerNewObject`,
21219 * or based on an empty object if not present
21220 * @param {Grid} grid the grid we're importing into
21221 * @returns {object} the new object
21223 newObject: function( grid ){
21224 if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
21225 return new grid.options.importerNewObject();
21235 * @methodOf ui.grid.importer.service:uiGridImporterService
21236 * @description Provides an internationalised user alert for the failure,
21237 * and logs a console message including diagnostic content.
21238 * Optionally, if the the `gridOptions.importerErrorCallback` routine
21239 * is defined, then calls that instead, allowing user specified error routines
21240 * @param {Grid} grid the grid we're importing into
21241 * @param {array} headerRow the header row that we wish to match against
21242 * the column definitions
21244 alertError: function( grid, alertI18nToken, consoleMessage, context ){
21245 if ( grid.options.importerErrorCallback ){
21246 grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
21248 $window.alert(i18nService.getSafeText( alertI18nToken ));
21249 gridUtil.logError(consoleMessage + context );
21261 * @name ui.grid.importer.directive:uiGridImporter
21265 * @description Adds importer features to grid
21268 module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
21269 function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
21273 require: '^uiGrid',
21275 link: function ($scope, $elm, $attrs, uiGridCtrl) {
21276 uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid);
21284 * @name ui.grid.importer.directive:uiGridImporterMenuItem
21288 * @description Handles the processing from the importer menu item - once a file is
21292 module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
21293 function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
21297 require: '^uiGrid',
21299 templateUrl: 'ui-grid/importerMenuItem',
21300 link: function ($scope, $elm, $attrs, uiGridCtrl) {
21301 var handleFileSelect = function( event ){
21302 var target = event.srcElement || event.target;
21304 if (target && target.files && target.files.length === 1) {
21305 var fileObject = target.files[0];
21306 uiGridImporterService.importThisFile( grid, fileObject );
21307 target.form.reset();
21311 var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
21312 var grid = uiGridCtrl.grid;
21314 if ( fileChooser.length !== 1 ){
21315 gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
21317 fileChooser[0].addEventListener('change', handleFileSelect, false); // TODO: why the false on the end? Google
21329 * @name ui.grid.infiniteScroll
21333 * #ui.grid.infiniteScroll
21335 * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
21337 * This module provides infinite scroll functionality to ui-grid
21340 var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
21343 * @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21345 * @description Service for infinite scroll features
21347 module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $timeout, uiGridConstants, ScrollEvent, $q) {
21353 * @name initializeGrid
21354 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21355 * @description This method register events and methods into grid public API
21358 initializeGrid: function(grid, $scope) {
21359 service.defaultGridOptions(grid.options);
21361 if (!grid.options.enableInfiniteScroll){
21365 grid.infiniteScroll = { dataLoading: false };
21366 service.setScrollDirections( grid, grid.options.infiniteScrollUp, grid.options.infiniteScrollDown );
21367 grid.api.core.on.scrollEnd($scope, service.handleScroll);
21371 * @name ui.grid.infiniteScroll.api:PublicAPI
21373 * @description Public API for infinite scroll feature
21381 * @name needLoadMoreData
21382 * @eventOf ui.grid.infiniteScroll.api:PublicAPI
21383 * @description This event fires when scroll reaches bottom percentage of grid
21384 * and needs to load data
21387 needLoadMoreData: function ($scope, fn) {
21392 * @name needLoadMoreDataTop
21393 * @eventOf ui.grid.infiniteScroll.api:PublicAPI
21394 * @description This event fires when scroll reaches top percentage of grid
21395 * and needs to load data
21398 needLoadMoreDataTop: function ($scope, fn) {
21408 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21409 * @description Call this function when you have loaded the additional data
21410 * requested. You should set scrollUp and scrollDown to indicate
21411 * whether there are still more pages in each direction.
21413 * If you call dataLoaded without first calling `saveScrollPercentage` then we will
21414 * scroll the user to the start of the newly loaded data, which usually gives a smooth scroll
21415 * experience, but can give a jumpy experience with large `infiniteScrollRowsFromEnd` values, and
21416 * on variable speed internet connections. Using `saveScrollPercentage` as demonstrated in the tutorial
21417 * should give a smoother scrolling experience for users.
21419 * See infinite_scroll tutorial for example of usage
21420 * @param {boolean} scrollUp if set to false flags that there are no more pages upwards, so don't fire
21421 * any more infinite scroll events upward
21422 * @param {boolean} scrollDown if set to false flags that there are no more pages downwards, so don't
21423 * fire any more infinite scroll events downward
21424 * @returns {promise} a promise that is resolved when the grid scrolling is fully adjusted. If you're
21425 * planning to remove pages, you should wait on this promise first, or you'll break the scroll positioning
21427 dataLoaded: function( scrollUp, scrollDown ) {
21428 service.setScrollDirections(grid, scrollUp, scrollDown);
21430 var promise = service.adjustScroll(grid).then(function() {
21431 grid.infiniteScroll.dataLoading = false;
21439 * @name resetScroll
21440 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21441 * @description Call this function when you have taken some action that makes the current
21442 * scroll position invalid. For example, if you're using external sorting and you've resorted
21443 * then you might reset the scroll, or if you've otherwise substantially changed the data, perhaps
21444 * you've reused an existing grid for a new data set
21446 * You must tell us whether there is data upwards or downwards after the reset
21448 * @param {boolean} scrollUp flag that there are pages upwards, fire
21449 * infinite scroll events upward
21450 * @param {boolean} scrollDown flag that there are pages downwards, so
21451 * fire infinite scroll events downward
21453 resetScroll: function( scrollUp, scrollDown ) {
21454 service.setScrollDirections( grid, scrollUp, scrollDown);
21456 service.adjustInfiniteScrollPosition(grid, 0);
21462 * @name saveScrollPercentage
21463 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21464 * @description Saves the scroll percentage and number of visible rows before you adjust the data,
21465 * used if you're subsequently going to call `dataRemovedTop` or `dataRemovedBottom`
21467 saveScrollPercentage: function() {
21468 grid.infiniteScroll.prevScrollTop = grid.renderContainers.body.prevScrollTop;
21469 grid.infiniteScroll.previousVisibleRows = grid.getVisibleRowCount();
21475 * @name dataRemovedTop
21476 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21477 * @description Adjusts the scroll position after you've removed data at the top
21478 * @param {boolean} scrollUp flag that there are pages upwards, fire
21479 * infinite scroll events upward
21480 * @param {boolean} scrollDown flag that there are pages downwards, so
21481 * fire infinite scroll events downward
21483 dataRemovedTop: function( scrollUp, scrollDown ) {
21484 service.dataRemovedTop( grid, scrollUp, scrollDown );
21489 * @name dataRemovedBottom
21490 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21491 * @description Adjusts the scroll position after you've removed data at the bottom
21492 * @param {boolean} scrollUp flag that there are pages upwards, fire
21493 * infinite scroll events upward
21494 * @param {boolean} scrollDown flag that there are pages downwards, so
21495 * fire infinite scroll events downward
21497 dataRemovedBottom: function( scrollUp, scrollDown ) {
21498 service.dataRemovedBottom( grid, scrollUp, scrollDown );
21503 * @name setScrollDirections
21504 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21505 * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
21506 * and also sets the grid.suppressParentScroll
21507 * @param {boolean} scrollUp whether there are pages available up - defaults to false
21508 * @param {boolean} scrollDown whether there are pages available down - defaults to true
21510 setScrollDirections: function ( scrollUp, scrollDown ) {
21511 service.setScrollDirections( grid, scrollUp, scrollDown );
21517 grid.api.registerEventsFromObject(publicApi.events);
21518 grid.api.registerMethodsFromObject(publicApi.methods);
21522 defaultGridOptions: function (gridOptions) {
21523 //default option to true unless it was explicitly set to false
21526 * @name ui.grid.infiniteScroll.api:GridOptions
21528 * @description GridOptions for infinite scroll feature, these are available to be
21529 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21534 * @name enableInfiniteScroll
21535 * @propertyOf ui.grid.infiniteScroll.api:GridOptions
21536 * @description Enable infinite scrolling for this grid
21537 * <br/>Defaults to true
21539 gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
21543 * @name infiniteScrollRowsFromEnd
21544 * @propertyOf ui.grid.class:GridOptions
21545 * @description This setting controls how close to the end of the dataset a user gets before
21546 * more data is requested by the infinite scroll, whether scrolling up or down. This allows you to
21547 * 'prefetch' rows before the user actually runs out of scrolling.
21549 * Note that if you set this value too high it may give jumpy scrolling behaviour, if you're getting
21550 * this behaviour you could use the `saveScrollPercentageMethod` right before loading your data, and we'll
21551 * preserve that scroll position
21553 * <br> Defaults to 20
21555 gridOptions.infiniteScrollRowsFromEnd = gridOptions.infiniteScrollRowsFromEnd || 20;
21559 * @name infiniteScrollUp
21560 * @propertyOf ui.grid.class:GridOptions
21561 * @description Whether you allow infinite scroll up, implying that the first page of data
21562 * you have displayed is in the middle of your data set. If set to true then we trigger the
21563 * needMoreDataTop event when the user hits the top of the scrollbar.
21564 * <br> Defaults to false
21566 gridOptions.infiniteScrollUp = gridOptions.infiniteScrollUp === true;
21570 * @name infiniteScrollDown
21571 * @propertyOf ui.grid.class:GridOptions
21572 * @description Whether you allow infinite scroll down, implying that the first page of data
21573 * you have displayed is in the middle of your data set. If set to true then we trigger the
21574 * needMoreData event when the user hits the bottom of the scrollbar.
21575 * <br> Defaults to true
21577 gridOptions.infiniteScrollDown = gridOptions.infiniteScrollDown !== false;
21583 * @name setScrollDirections
21584 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21585 * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
21586 * and also sets the grid.suppressParentScroll
21587 * @param {grid} grid the grid we're operating on
21588 * @param {boolean} scrollUp whether there are pages available up - defaults to false
21589 * @param {boolean} scrollDown whether there are pages available down - defaults to true
21591 setScrollDirections: function ( grid, scrollUp, scrollDown ) {
21592 grid.infiniteScroll.scrollUp = ( scrollUp === true );
21593 grid.suppressParentScrollUp = ( scrollUp === true );
21595 grid.infiniteScroll.scrollDown = ( scrollDown !== false);
21596 grid.suppressParentScrollDown = ( scrollDown !== false);
21602 * @name handleScroll
21603 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21604 * @description Called whenever the grid scrolls, determines whether the scroll should
21605 * trigger an infinite scroll request for more data
21606 * @param {object} args the args from the event
21608 handleScroll: function (args) {
21609 // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function
21610 if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){
21616 // If the user is scrolling very quickly all the way to the top/bottom, the scroll handler can get confused
21617 // about the direction. First we check if they've gone all the way, and data always is loaded in this case.
21618 if (args.y.percentage === 0) {
21619 args.grid.scrollDirection = uiGridConstants.scrollDirection.UP;
21620 service.loadData(args.grid);
21621 } else if (args.y.percentage === 1) {
21622 args.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN;
21623 service.loadData(args.grid);
21624 } else { // Scroll position is somewhere in between top/bottom, so determine whether it's far enough to load more data.
21626 var targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
21627 if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
21628 percentage = args.y.percentage;
21629 if (percentage <= targetPercentage){
21630 service.loadData(args.grid);
21632 } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
21633 percentage = 1 - args.y.percentage;
21634 if (percentage <= targetPercentage){
21635 service.loadData(args.grid);
21646 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21647 * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
21648 * and whether there are more pages upwards or downwards. It also stores the number of rows that we had previously,
21649 * and clears out any saved scroll position so that we know whether or not the user calls `saveScrollPercentage`
21650 * @param {Grid} grid the grid we're working on
21652 loadData: function (grid) {
21653 // save number of currently visible rows to calculate new scroll position later - we know that we want
21654 // to be at approximately the row we're currently at
21655 grid.infiniteScroll.previousVisibleRows = grid.renderContainers.body.visibleRowCache.length;
21656 grid.infiniteScroll.direction = grid.scrollDirection;
21657 delete grid.infiniteScroll.prevScrollTop;
21659 if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) {
21660 grid.infiniteScroll.dataLoading = true;
21661 grid.api.infiniteScroll.raise.needLoadMoreDataTop();
21662 } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
21663 grid.infiniteScroll.dataLoading = true;
21664 grid.api.infiniteScroll.raise.needLoadMoreData();
21671 * @name adjustScroll
21672 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21673 * @description Once we are informed that data has been loaded, adjust the scroll position to account for that
21674 * addition and to make things look clean.
21676 * If we're scrolling up we scroll to the first row of the old data set -
21677 * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by
21678 * the time the data comes back. If we're scrolling down we scoll to the last row of the old data set - so we're
21679 * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time
21680 * the data comes back.
21682 * Neither of these are good assumptions, but making this a smoother experience really requires
21683 * that trigger to not be a percentage, and to be much closer to the end of the data (say, 5 rows off the end). Even then
21684 * it'd be better still to actually run into the end. But if the data takes a while to come back, they may have scrolled
21685 * somewhere else in the mean-time, in which case they'll get a jump back to the new data. Anyway, this will do for
21686 * now, until someone wants to do better.
21687 * @param {Grid} grid the grid we're working on
21688 * @returns {promise} a promise that is resolved when scrolling has finished
21690 adjustScroll: function(grid){
21691 var promise = $q.defer();
21692 $timeout(function () {
21693 var newPercentage, viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
21695 viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight;
21696 rowHeight = grid.options.rowHeight;
21698 if ( grid.infiniteScroll.direction === undefined ){
21699 // called from initialize, tweak our scroll up a little
21700 service.adjustInfiniteScrollPosition(grid, 0);
21703 newVisibleRows = grid.getVisibleRowCount();
21705 // in case not enough data is loaded to enable scroller - load more data
21706 var canvasHeight = rowHeight * newVisibleRows;
21707 if (grid.infiniteScroll.scrollDown && (viewportHeight > canvasHeight)) {
21708 grid.api.infiniteScroll.raise.needLoadMoreData();
21711 if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){
21712 oldTop = grid.infiniteScroll.prevScrollTop || 0;
21713 newTop = oldTop + (newVisibleRows - grid.infiniteScroll.previousVisibleRows)*rowHeight;
21714 service.adjustInfiniteScrollPosition(grid, newTop);
21715 $timeout( function() {
21720 if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){
21721 newTop = grid.infiniteScroll.prevScrollTop || (grid.infiniteScroll.previousVisibleRows*rowHeight - viewportHeight);
21722 service.adjustInfiniteScrollPosition(grid, newTop);
21723 $timeout( function() {
21729 return promise.promise;
21735 * @name adjustInfiniteScrollPosition
21736 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
21737 * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
21738 * @param {Grid} grid the grid we're working on
21739 * @param {number} scrollTop the position through the grid that we want to scroll to
21741 adjustInfiniteScrollPosition: function (grid, scrollTop) {
21742 var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'),
21743 visibleRows = grid.getVisibleRowCount(),
21744 viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight,
21745 rowHeight = grid.options.rowHeight,
21746 scrollHeight = visibleRows*rowHeight-viewportHeight;
21748 //for infinite scroll, if there are pages upwards then never allow it to be at the zero position so the up button can be active
21749 if (scrollTop === 0 && grid.infiniteScroll.scrollUp) {
21750 // using pixels results in a relative scroll, hence we have to use percentage
21751 scrollEvent.y = {percentage: 1/scrollHeight};
21754 scrollEvent.y = {percentage: scrollTop/scrollHeight};
21756 grid.scrollContainers('', scrollEvent);
21762 * @name dataRemovedTop
21763 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21764 * @description Adjusts the scroll position after you've removed data at the top. You should
21765 * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
21766 * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
21767 * before you start removing data
21768 * @param {Grid} grid the grid we're working on
21769 * @param {boolean} scrollUp flag that there are pages upwards, fire
21770 * infinite scroll events upward
21771 * @param {boolean} scrollDown flag that there are pages downwards, so
21772 * fire infinite scroll events downward
21774 dataRemovedTop: function( grid, scrollUp, scrollDown ) {
21775 var newVisibleRows, oldTop, newTop, rowHeight;
21776 service.setScrollDirections( grid, scrollUp, scrollDown );
21778 newVisibleRows = grid.renderContainers.body.visibleRowCache.length;
21779 oldTop = grid.infiniteScroll.prevScrollTop;
21780 rowHeight = grid.options.rowHeight;
21782 // since we removed from the top, our new scroll row will be the old scroll row less the number
21784 newTop = oldTop - ( grid.infiniteScroll.previousVisibleRows - newVisibleRows )*rowHeight;
21786 service.adjustInfiniteScrollPosition( grid, newTop );
21791 * @name dataRemovedBottom
21792 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
21793 * @description Adjusts the scroll position after you've removed data at the bottom. You should
21794 * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
21795 * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
21796 * before you start removing data
21797 * @param {Grid} grid the grid we're working on
21798 * @param {boolean} scrollUp flag that there are pages upwards, fire
21799 * infinite scroll events upward
21800 * @param {boolean} scrollDown flag that there are pages downwards, so
21801 * fire infinite scroll events downward
21803 dataRemovedBottom: function( grid, scrollUp, scrollDown ) {
21805 service.setScrollDirections( grid, scrollUp, scrollDown );
21807 newTop = grid.infiniteScroll.prevScrollTop;
21809 service.adjustInfiniteScrollPosition( grid, newTop );
21816 * @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
21820 * @description Adds infinite scroll features to grid
21823 <example module="app">
21824 <file name="app.js">
21825 var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
21827 app.controller('MainCtrl', ['$scope', function ($scope) {
21829 { name: 'Alex', car: 'Toyota' },
21830 { name: 'Sam', car: 'Lexus' }
21833 $scope.columnDefs = [
21839 <file name="index.html">
21840 <div ng-controller="MainCtrl">
21841 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
21847 module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
21848 function (uiGridInfiniteScrollService) {
21852 require: '^uiGrid',
21853 compile: function($scope, $elm, $attr){
21855 pre: function($scope, $elm, $attr, uiGridCtrl) {
21856 uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope);
21858 post: function($scope, $elm, $attr) {
21872 * @name ui.grid.moveColumns
21875 * # ui.grid.moveColumns
21877 * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
21879 * This module provides column moving capability to ui.grid. It enables to change the position of columns.
21880 * <div doc-module-components="ui.grid.moveColumns"></div>
21882 var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
21886 * @name ui.grid.moveColumns.service:uiGridMoveColumnService
21887 * @description Service for column moving feature.
21889 module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $timeout, $log, ScrollEvent, uiGridConstants, gridUtil) {
21892 initializeGrid: function (grid) {
21894 this.registerPublicApi(grid);
21895 this.defaultGridOptions(grid.options);
21896 grid.moveColumns = {orderCache: []}; // Used to cache the order before columns are rebuilt
21897 grid.registerColumnBuilder(self.movableColumnBuilder);
21898 grid.registerDataChangeCallback(self.verifyColumnOrder, [uiGridConstants.dataChange.COLUMN]);
21900 registerPublicApi: function (grid) {
21904 * @name ui.grid.moveColumns.api:PublicApi
21905 * @description Public Api for column moving feature.
21911 * @name columnPositionChanged
21912 * @eventOf ui.grid.moveColumns.api:PublicApi
21913 * @description raised when column is moved
21915 * gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
21917 * @param {object} colDef the column that was moved
21918 * @param {integer} originalPosition of the column
21919 * @param {integer} finalPosition of the column
21922 columnPositionChanged: function (colDef, originalPosition, newPosition) {
21930 * @methodOf ui.grid.moveColumns.api:PublicApi
21931 * @description Method can be used to change column position.
21933 * gridApi.colMovable.moveColumn(oldPosition, newPosition)
21935 * @param {integer} originalPosition of the column
21936 * @param {integer} finalPosition of the column
21939 moveColumn: function (originalPosition, finalPosition) {
21940 var columns = grid.columns;
21941 if (!angular.isNumber(originalPosition) || !angular.isNumber(finalPosition)) {
21942 gridUtil.logError('MoveColumn: Please provide valid values for originalPosition and finalPosition');
21945 var nonMovableColumns = 0;
21946 for (var i = 0; i < columns.length; i++) {
21947 if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true) {
21948 nonMovableColumns++;
21951 if (originalPosition >= (columns.length - nonMovableColumns) || finalPosition >= (columns.length - nonMovableColumns)) {
21952 gridUtil.logError('MoveColumn: Invalid values for originalPosition, finalPosition');
21955 var findPositionForRenderIndex = function (index) {
21956 var position = index;
21957 for (var i = 0; i <= position; i++) {
21958 if (angular.isDefined(columns[i]) && ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true)) {
21964 self.redrawColumnAtPosition(grid, findPositionForRenderIndex(originalPosition), findPositionForRenderIndex(finalPosition));
21969 grid.api.registerEventsFromObject(publicApi.events);
21970 grid.api.registerMethodsFromObject(publicApi.methods);
21972 defaultGridOptions: function (gridOptions) {
21975 * @name ui.grid.moveColumns.api:GridOptions
21977 * @description Options for configuring the move column feature, these are available to be
21978 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
21982 * @name enableColumnMoving
21983 * @propertyOf ui.grid.moveColumns.api:GridOptions
21984 * @description If defined, sets the default value for the colMovable flag on each individual colDefs
21985 * if their individual enableColumnMoving configuration is not defined. Defaults to true.
21987 gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
21989 movableColumnBuilder: function (colDef, col, gridOptions) {
21993 * @name ui.grid.moveColumns.api:ColumnDef
21995 * @description Column Definition for move column feature, these are available to be
21996 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
22000 * @name enableColumnMoving
22001 * @propertyOf ui.grid.moveColumns.api:ColumnDef
22002 * @description Enable column moving for the column.
22004 colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
22005 : colDef.enableColumnMoving;
22006 return $q.all(promises);
22010 * @name updateColumnCache
22011 * @methodOf ui.grid.moveColumns
22012 * @description Cache the current order of columns, so we can restore them after new columnDefs are defined
22014 updateColumnCache: function(grid){
22015 grid.moveColumns.orderCache = grid.getOnlyDataColumns();
22019 * @name verifyColumnOrder
22020 * @methodOf ui.grid.moveColumns
22021 * @description dataChangeCallback which uses the cached column order to restore the column order
22022 * when it is reset by altering the columnDefs array.
22024 verifyColumnOrder: function(grid){
22025 var headerRowOffset = grid.rowHeaderColumns.length;
22028 angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex){
22029 newIndex = grid.columns.indexOf(cacheCol);
22030 if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ){
22031 var column = grid.columns.splice(newIndex, 1)[0];
22032 grid.columns.splice(cacheIndex + headerRowOffset, 0, column);
22036 redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
22037 var columns = grid.columns;
22039 if (originalPosition === newPosition) {
22043 //check columns in between move-range to make sure they are visible columns
22044 var pos = (originalPosition < newPosition) ? originalPosition + 1 : originalPosition - 1;
22045 var i0 = Math.min(pos, newPosition);
22046 for (i0; i0 <= Math.max(pos, newPosition); i0++) {
22047 if (columns[i0].visible) {
22051 if (i0 > Math.max(pos, newPosition)) {
22052 //no visible column found, column did not visibly move
22056 var originalColumn = columns[originalPosition];
22057 if (originalColumn.colDef.enableColumnMoving) {
22058 if (originalPosition > newPosition) {
22059 for (var i1 = originalPosition; i1 > newPosition; i1--) {
22060 columns[i1] = columns[i1 - 1];
22063 else if (newPosition > originalPosition) {
22064 for (var i2 = originalPosition; i2 < newPosition; i2++) {
22065 columns[i2] = columns[i2 + 1];
22068 columns[newPosition] = originalColumn;
22069 service.updateColumnCache(grid);
22070 grid.queueGridRefresh();
22071 $timeout(function () {
22072 grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
22073 grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
22083 * @name ui.grid.moveColumns.directive:uiGridMoveColumns
22086 * @description Adds column moving features to the ui-grid directive.
22088 <example module="app">
22089 <file name="app.js">
22090 var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
22091 app.controller('MainCtrl', ['$scope', function ($scope) {
22093 { name: 'Bob', title: 'CEO', age: 45 },
22094 { name: 'Frank', title: 'Lowly Developer', age: 25 },
22095 { name: 'Jenny', title: 'Highly Developer', age: 35 }
22097 $scope.columnDefs = [
22104 <file name="main.css">
22110 <file name="index.html">
22111 <div ng-controller="MainCtrl">
22112 <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
22117 module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
22121 require: '^uiGrid',
22123 compile: function () {
22125 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
22126 uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
22128 post: function ($scope, $elm, $attrs, uiGridCtrl) {
22137 * @name ui.grid.moveColumns.directive:uiGridHeaderCell
22141 * @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
22143 * On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
22144 * In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
22145 * On mouseUp event column is repositioned at position where mouse is released and cloned header cell is removed.
22147 * Events that invoke cloning of header cell:
22150 * Events that invoke movement of cloned header cell:
22153 * Events that invoke repositioning of column:
22156 module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document', '$log', 'uiGridConstants', 'ScrollEvent',
22157 function ($q, gridUtil, uiGridMoveColumnService, $document, $log, uiGridConstants, ScrollEvent) {
22160 require: '^uiGrid',
22161 compile: function () {
22163 post: function ($scope, $elm, $attrs, uiGridCtrl) {
22165 if ($scope.col.colDef.enableColumnMoving) {
22168 * Our general approach to column move is that we listen to a touchstart or mousedown
22169 * event over the column header. When we hear one, then we wait for a move of the same type
22170 * - if we are a touchstart then we listen for a touchmove, if we are a mousedown we listen for
22171 * a mousemove (i.e. a drag) before we decide that there's a move underway. If there's never a move,
22172 * and we instead get a mouseup or a touchend, then we just drop out again and do nothing.
22175 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
22178 var previousMouseX;
22179 var totalMouseMovement;
22180 var rightMoveLimit;
22181 var elmCloned = false;
22184 var moveOccurred = false;
22186 var downFn = function( event ){
22187 //Setting some variables required for calculations.
22188 gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
22189 if ( $scope.grid.hasLeftContainer() ){
22190 gridLeft += $scope.grid.renderContainers.left.header[0].getBoundingClientRect().width;
22193 previousMouseX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
22194 totalMouseMovement = 0;
22195 rightMoveLimit = gridLeft + $scope.grid.getViewportWidth();
22197 if ( event.type === 'mousedown' ){
22198 $document.on('mousemove', moveFn);
22199 $document.on('mouseup', upFn);
22200 } else if ( event.type === 'touchstart' ){
22201 $document.on('touchmove', moveFn);
22202 $document.on('touchend', upFn);
22206 var moveFn = function( event ) {
22207 var pageX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
22208 var changeValue = pageX - previousMouseX;
22209 if ( changeValue === 0 ){ return; }
22210 //Disable text selection in Chrome during column move
22211 document.onselectstart = function() { return false; };
22213 moveOccurred = true;
22218 else if (elmCloned) {
22219 moveElement(changeValue);
22220 previousMouseX = pageX;
22224 var upFn = function( event ){
22225 //Re-enable text selection after column move
22226 document.onselectstart = null;
22228 //Remove the cloned element on mouse up.
22230 movingElm.remove();
22237 if (!moveOccurred){
22241 var columns = $scope.grid.columns;
22242 var columnIndex = 0;
22243 for (var i = 0; i < columns.length; i++) {
22244 if (columns[i].colDef.name !== $scope.col.colDef.name) {
22254 //Case where column should be moved to a position on its left
22255 if (totalMouseMovement < 0) {
22256 var totalColumnsLeftWidth = 0;
22258 if ( $scope.grid.isRTL() ){
22259 for (il = columnIndex + 1; il < columns.length; il++) {
22260 if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
22261 totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
22262 if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
22263 uiGridMoveColumnService.redrawColumnAtPosition
22264 ($scope.grid, columnIndex, il - 1);
22271 for (il = columnIndex - 1; il >= 0; il--) {
22272 if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
22273 totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
22274 if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
22275 uiGridMoveColumnService.redrawColumnAtPosition
22276 ($scope.grid, columnIndex, il + 1);
22283 //Case where column should be moved to beginning (or end in RTL) of the grid.
22284 if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
22286 if ( $scope.grid.isRTL() ){
22287 targetIndex = columns.length - 1;
22289 uiGridMoveColumnService.redrawColumnAtPosition
22290 ($scope.grid, columnIndex, targetIndex);
22294 //Case where column should be moved to a position on its right
22295 else if (totalMouseMovement > 0) {
22296 var totalColumnsRightWidth = 0;
22298 if ( $scope.grid.isRTL() ){
22299 for (ir = columnIndex - 1; ir > 0; ir--) {
22300 if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
22301 totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
22302 if (totalColumnsRightWidth > totalMouseMovement) {
22303 uiGridMoveColumnService.redrawColumnAtPosition
22304 ($scope.grid, columnIndex, ir);
22311 for (ir = columnIndex + 1; ir < columns.length; ir++) {
22312 if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
22313 totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
22314 if (totalColumnsRightWidth > totalMouseMovement) {
22315 uiGridMoveColumnService.redrawColumnAtPosition
22316 ($scope.grid, columnIndex, ir - 1);
22324 //Case where column should be moved to end (or beginning in RTL) of the grid.
22325 if (totalColumnsRightWidth < totalMouseMovement) {
22326 targetIndex = columns.length - 1;
22327 if ( $scope.grid.isRTL() ){
22330 uiGridMoveColumnService.redrawColumnAtPosition
22331 ($scope.grid, columnIndex, targetIndex);
22339 var onDownEvents = function(){
22340 $contentsElm.on('touchstart', downFn);
22341 $contentsElm.on('mousedown', downFn);
22344 var offAllEvents = function() {
22345 $contentsElm.off('touchstart', downFn);
22346 $contentsElm.off('mousedown', downFn);
22348 $document.off('mousemove', moveFn);
22349 $document.off('touchmove', moveFn);
22351 $document.off('mouseup', upFn);
22352 $document.off('touchend', upFn);
22358 var cloneElement = function () {
22361 //Cloning header cell and appending to current header cell.
22362 movingElm = $elm.clone();
22363 $elm.parent().append(movingElm);
22365 //Left of cloned element should be aligned to original header cell.
22366 movingElm.addClass('movingColumn');
22367 var movingElementStyles = {};
22368 movingElementStyles.left = $elm[0].offsetLeft + 'px';
22369 var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
22370 var elmRight = $elm[0].getBoundingClientRect().right;
22371 if (elmRight > gridRight) {
22372 reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
22373 movingElementStyles.width = reducedWidth + 'px';
22375 movingElm.css(movingElementStyles);
22378 var moveElement = function (changeValue) {
22379 //Calculate total column width
22380 var columns = $scope.grid.columns;
22381 var totalColumnWidth = 0;
22382 for (var i = 0; i < columns.length; i++) {
22383 if (angular.isUndefined(columns[i].colDef.visible) || columns[i].colDef.visible === true) {
22384 totalColumnWidth += columns[i].drawnWidth || columns[i].width || columns[i].colDef.width;
22388 //Calculate new position of left of column
22389 var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
22390 var currentElmRight = movingElm[0].getBoundingClientRect().right;
22391 var newElementLeft;
22393 newElementLeft = currentElmLeft - gridLeft + changeValue;
22394 newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
22396 //Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
22397 if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
22398 movingElm.css({visibility: 'visible', 'left': (movingElm[0].offsetLeft +
22399 (newElementLeft < rightMoveLimit ? changeValue : (rightMoveLimit - currentElmLeft))) + 'px'});
22401 else if (totalColumnWidth > Math.ceil(uiGridCtrl.grid.gridWidth)) {
22403 var scrollEvent = new ScrollEvent($scope.col.grid, null, null, 'uiGridHeaderCell.moveElement');
22404 scrollEvent.x = {pixels: changeValue};
22405 scrollEvent.grid.scrollContainers('',scrollEvent);
22408 //Calculate total width of columns on the left of the moving column and the mouse movement
22409 var totalColumnsLeftWidth = 0;
22410 for (var il = 0; il < columns.length; il++) {
22411 if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
22412 if (columns[il].colDef.name !== $scope.col.colDef.name) {
22413 totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
22420 if ($scope.newScrollLeft === undefined) {
22421 totalMouseMovement += changeValue;
22424 totalMouseMovement = $scope.newScrollLeft + newElementLeft - totalColumnsLeftWidth;
22427 //Increase width of moving column, in case the rightmost column was moved and its width was
22428 //decreased because of overflow
22429 if (reducedWidth < $scope.col.drawnWidth) {
22430 reducedWidth += Math.abs(changeValue);
22431 movingElm.css({'width': reducedWidth + 'px'});
22435 $scope.$on('$destroy', offAllEvents);
22449 * @name ui.grid.pagination
22453 * # ui.grid.pagination
22455 * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
22457 * This module provides pagination support to ui-grid
22459 var module = angular.module('ui.grid.pagination', ['ng', 'ui.grid']);
22463 * @name ui.grid.pagination.service:uiGridPaginationService
22465 * @description Service for the pagination feature
22467 module.service('uiGridPaginationService', ['gridUtil',
22468 function (gridUtil) {
22472 * @name initializeGrid
22473 * @methodOf ui.grid.pagination.service:uiGridPaginationService
22474 * @description Attaches the service to a certain grid
22475 * @param {Grid} grid The grid we want to work with
22477 initializeGrid: function (grid) {
22478 service.defaultGridOptions(grid.options);
22482 * @name ui.grid.pagination.api:PublicAPI
22484 * @description Public API for the pagination feature
22491 * @name paginationChanged
22492 * @eventOf ui.grid.pagination.api:PublicAPI
22493 * @description This event fires when the pageSize or currentPage changes
22494 * @param {int} currentPage requested page number
22495 * @param {int} pageSize requested page size
22497 paginationChanged: function (currentPage, pageSize) { }
22505 * @methodOf ui.grid.pagination.api:PublicAPI
22506 * @description Returns the number of the current page
22508 getPage: function () {
22509 return grid.options.enablePagination ? grid.options.paginationCurrentPage : null;
22513 * @name getFirstRowIndex
22514 * @methodOf ui.grid.pagination.api:PublicAPI
22515 * @description Returns the index of the first row of the current page.
22517 getFirstRowIndex: function () {
22518 if (grid.options.useCustomPagination) {
22519 return grid.options.paginationPageSizes.reduce(function(result, size, index) {
22520 return index < grid.options.paginationCurrentPage - 1 ? result + size : result;
22523 return ((grid.options.paginationCurrentPage - 1) * grid.options.paginationPageSize);
22527 * @name getLastRowIndex
22528 * @methodOf ui.grid.pagination.api:PublicAPI
22529 * @description Returns the index of the last row of the current page.
22531 getLastRowIndex: function () {
22532 if (grid.options.useCustomPagination) {
22533 return publicApi.methods.pagination.getFirstRowIndex() + grid.options.paginationPageSizes[grid.options.paginationCurrentPage - 1];
22535 return Math.min(grid.options.paginationCurrentPage * grid.options.paginationPageSize, grid.options.totalItems);
22539 * @name getTotalPages
22540 * @methodOf ui.grid.pagination.api:PublicAPI
22541 * @description Returns the total number of pages
22543 getTotalPages: function () {
22544 if (!grid.options.enablePagination) {
22548 if (grid.options.useCustomPagination) {
22549 return grid.options.paginationPageSizes.length;
22552 return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
22557 * @methodOf ui.grid.pagination.api:PublicAPI
22558 * @description Moves to the next page, if possible
22560 nextPage: function () {
22561 if (!grid.options.enablePagination) {
22565 if (grid.options.totalItems > 0) {
22566 grid.options.paginationCurrentPage = Math.min(
22567 grid.options.paginationCurrentPage + 1,
22568 publicApi.methods.pagination.getTotalPages()
22571 grid.options.paginationCurrentPage++;
22576 * @name previousPage
22577 * @methodOf ui.grid.pagination.api:PublicAPI
22578 * @description Moves to the previous page, if we're not on the first page
22580 previousPage: function () {
22581 if (!grid.options.enablePagination) {
22585 grid.options.paginationCurrentPage = Math.max(grid.options.paginationCurrentPage - 1, 1);
22590 * @methodOf ui.grid.pagination.api:PublicAPI
22591 * @description Moves to the requested page
22592 * @param {int} page The number of the page that should be displayed
22594 seek: function (page) {
22595 if (!grid.options.enablePagination) {
22598 if (!angular.isNumber(page) || page < 1) {
22599 throw 'Invalid page number: ' + page;
22602 grid.options.paginationCurrentPage = Math.min(page, publicApi.methods.pagination.getTotalPages());
22608 grid.api.registerEventsFromObject(publicApi.events);
22609 grid.api.registerMethodsFromObject(publicApi.methods);
22611 var processPagination = function( renderableRows ){
22612 if (grid.options.useExternalPagination || !grid.options.enablePagination) {
22613 return renderableRows;
22615 //client side pagination
22616 var pageSize = parseInt(grid.options.paginationPageSize, 10);
22617 var currentPage = parseInt(grid.options.paginationCurrentPage, 10);
22619 var visibleRows = renderableRows.filter(function (row) { return row.visible; });
22620 grid.options.totalItems = visibleRows.length;
22622 var firstRow = publicApi.methods.pagination.getFirstRowIndex();
22623 var lastRow = publicApi.methods.pagination.getLastRowIndex();
22625 if (firstRow > visibleRows.length) {
22626 currentPage = grid.options.paginationCurrentPage = 1;
22627 firstRow = (currentPage - 1) * pageSize;
22629 return visibleRows.slice(firstRow, lastRow);
22632 grid.registerRowsProcessor(processPagination, 900 );
22635 defaultGridOptions: function (gridOptions) {
22638 * @name ui.grid.pagination.api:GridOptions
22640 * @description GridOptions for the pagination feature, these are available to be
22641 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
22646 * @name enablePagination
22647 * @propertyOf ui.grid.pagination.api:GridOptions
22648 * @description Enables pagination. Defaults to true.
22650 gridOptions.enablePagination = gridOptions.enablePagination !== false;
22653 * @name enablePaginationControls
22654 * @propertyOf ui.grid.pagination.api:GridOptions
22655 * @description Enables the paginator at the bottom of the grid. Turn this off if you want to implement your
22656 * own controls outside the grid.
22658 gridOptions.enablePaginationControls = gridOptions.enablePaginationControls !== false;
22661 * @name useExternalPagination
22662 * @propertyOf ui.grid.pagination.api:GridOptions
22663 * @description Disables client side pagination. When true, handle the paginationChanged event and set data
22664 * and totalItems. Defaults to `false`
22666 gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
22670 * @name useCustomPagination
22671 * @propertyOf ui.grid.pagination.api:GridOptions
22672 * @description Disables client-side pagination. When true, handle the `paginationChanged` event and set `data`,
22673 * `firstRowIndex`, `lastRowIndex`, and `totalItems`. Defaults to `false`.
22675 gridOptions.useCustomPagination = gridOptions.useCustomPagination === true;
22680 * @propertyOf ui.grid.pagination.api:GridOptions
22681 * @description Total number of items, set automatically when using client side pagination, but needs set by user
22682 * for server side pagination
22684 if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
22685 gridOptions.totalItems = 0;
22689 * @name paginationPageSizes
22690 * @propertyOf ui.grid.pagination.api:GridOptions
22691 * @description Array of page sizes, defaults to `[250, 500, 1000]`
22693 if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSizes)) {
22694 gridOptions.paginationPageSizes = [250, 500, 1000];
22698 * @name paginationPageSize
22699 * @propertyOf ui.grid.pagination.api:GridOptions
22700 * @description Page size, defaults to the first item in paginationPageSizes, or 0 if paginationPageSizes is empty
22702 if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSize)) {
22703 if (gridOptions.paginationPageSizes.length > 0) {
22704 gridOptions.paginationPageSize = gridOptions.paginationPageSizes[0];
22706 gridOptions.paginationPageSize = 0;
22711 * @name paginationCurrentPage
22712 * @propertyOf ui.grid.pagination.api:GridOptions
22713 * @description Current page number, defaults to 1
22715 if (gridUtil.isNullOrUndefined(gridOptions.paginationCurrentPage)) {
22716 gridOptions.paginationCurrentPage = 1;
22721 * @name paginationTemplate
22722 * @propertyOf ui.grid.pagination.api:GridOptions
22723 * @description A custom template for the pager, defaults to `ui-grid/pagination`
22725 if (gridUtil.isNullOrUndefined(gridOptions.paginationTemplate)) {
22726 gridOptions.paginationTemplate = 'ui-grid/pagination';
22731 * @methodOf ui.grid.pagination.service:uiGridPaginationService
22732 * @name uiGridPaginationService
22733 * @description Raises paginationChanged and calls refresh for client side pagination
22734 * @param {Grid} grid the grid for which the pagination changed
22735 * @param {int} currentPage requested page number
22736 * @param {int} pageSize requested page size
22738 onPaginationChanged: function (grid, currentPage, pageSize) {
22739 grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
22740 if (!grid.options.useExternalPagination) {
22741 grid.queueGridRefresh(); //client side pagination
22751 * @name ui.grid.pagination.directive:uiGridPagination
22755 * @description Adds pagination features to grid
22757 <example module="app">
22758 <file name="app.js">
22759 var app = angular.module('app', ['ui.grid', 'ui.grid.pagination']);
22761 app.controller('MainCtrl', ['$scope', function ($scope) {
22763 { name: 'Alex', car: 'Toyota' },
22764 { name: 'Sam', car: 'Lexus' },
22765 { name: 'Joe', car: 'Dodge' },
22766 { name: 'Bob', car: 'Buick' },
22767 { name: 'Cindy', car: 'Ford' },
22768 { name: 'Brian', car: 'Audi' },
22769 { name: 'Malcom', car: 'Mercedes Benz' },
22770 { name: 'Dave', car: 'Ford' },
22771 { name: 'Stacey', car: 'Audi' },
22772 { name: 'Amy', car: 'Acura' },
22773 { name: 'Scott', car: 'Toyota' },
22774 { name: 'Ryan', car: 'BMW' },
22777 $scope.gridOptions = {
22779 paginationPageSizes: [5, 10, 25],
22780 paginationPageSize: 5,
22788 <file name="index.html">
22789 <div ng-controller="MainCtrl">
22790 <div ui-grid="gridOptions" ui-grid-pagination></div>
22795 module.directive('uiGridPagination', ['gridUtil', 'uiGridPaginationService',
22796 function (gridUtil, uiGridPaginationService) {
22802 pre: function ($scope, $elm, $attr, uiGridCtrl) {
22803 uiGridPaginationService.initializeGrid(uiGridCtrl.grid);
22805 gridUtil.getTemplate(uiGridCtrl.grid.options.paginationTemplate)
22806 .then(function (contents) {
22807 var template = angular.element(contents);
22808 $elm.append(template);
22809 uiGridCtrl.innerCompile(template);
22819 * @name ui.grid.pagination.directive:uiGridPager
22822 * @description Panel for handling pagination
22824 module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService',
22825 function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService) {
22829 require: '^uiGrid',
22830 link: function ($scope, $elm, $attr, uiGridCtrl) {
22831 var defaultFocusElementSelector = '.ui-grid-pager-control-input';
22832 $scope.aria = i18nService.getSafeText('pagination.aria'); //Returns an object with all of the aria labels
22834 $scope.paginationApi = uiGridCtrl.grid.api.pagination;
22835 $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
22836 $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');
22837 $scope.paginationOf = i18nService.getSafeText('pagination.of');
22838 $scope.paginationThrough = i18nService.getSafeText('pagination.through');
22840 var options = uiGridCtrl.grid.options;
22842 uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
22843 adjustment.height = adjustment.height - gridUtil.elementHeight($elm, "padding");
22847 var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
22848 if (!grid.options.useExternalPagination) {
22849 grid.options.totalItems = grid.rows.length;
22851 }, [uiGridConstants.dataChange.ROW]);
22853 $scope.$on('$destroy', dataChangeDereg);
22855 var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
22856 if (newValues === oldValues || oldValues === undefined) {
22860 if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
22861 options.paginationCurrentPage = 1;
22865 if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
22866 options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
22870 uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
22874 $scope.$on('$destroy', function() {
22878 $scope.cantPageForward = function () {
22879 if ($scope.paginationApi.getTotalPages()) {
22880 return $scope.cantPageToLast();
22882 return options.data.length < 1;
22886 $scope.cantPageToLast = function () {
22887 var totalPages = $scope.paginationApi.getTotalPages();
22888 return !totalPages || options.paginationCurrentPage >= totalPages;
22891 $scope.cantPageBackward = function () {
22892 return options.paginationCurrentPage <= 1;
22895 var focusToInputIf = function(condition){
22897 gridUtil.focus.bySelector($elm, defaultFocusElementSelector);
22901 //Takes care of setting focus to the middle element when focus is lost
22902 $scope.pageFirstPageClick = function () {
22903 $scope.paginationApi.seek(1);
22904 focusToInputIf($scope.cantPageBackward());
22907 $scope.pagePreviousPageClick = function () {
22908 $scope.paginationApi.previousPage();
22909 focusToInputIf($scope.cantPageBackward());
22912 $scope.pageNextPageClick = function () {
22913 $scope.paginationApi.nextPage();
22914 focusToInputIf($scope.cantPageForward());
22917 $scope.pageLastPageClick = function () {
22918 $scope.paginationApi.seek($scope.paginationApi.getTotalPages());
22919 focusToInputIf($scope.cantPageToLast());
22933 * @name ui.grid.pinning
22936 * # ui.grid.pinning
22938 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
22940 * This module provides column pinning to the end user via menu options in the column header
22942 * <div doc-module-components="ui.grid.pinning"></div>
22945 var module = angular.module('ui.grid.pinning', ['ui.grid']);
22947 module.constant('uiGridPinningConstants', {
22955 module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', 'uiGridPinningConstants', function (gridUtil, GridRenderContainer, i18nService, uiGridPinningConstants) {
22958 initializeGrid: function (grid) {
22959 service.defaultGridOptions(grid.options);
22961 // Register a column builder to add new menu items for pinning left and right
22962 grid.registerColumnBuilder(service.pinningColumnBuilder);
22966 * @name ui.grid.pinning.api:PublicApi
22968 * @description Public Api for pinning feature
22976 * @eventOf ui.grid.pinning.api:PublicApi
22977 * @description raised when column pin state has changed
22979 * gridApi.pinning.on.columnPinned(scope, function(colDef){})
22981 * @param {object} colDef the column that was changed
22982 * @param {string} container the render container the column is in ('left', 'right', '')
22984 columnPinned: function(colDef, container) {
22993 * @methodOf ui.grid.pinning.api:PublicApi
22994 * @description pin column left, right, or none
22996 * gridApi.pinning.pinColumn(col, uiGridPinningConstants.container.LEFT)
22998 * @param {gridColumn} col the column being pinned
22999 * @param {string} container one of the recognised types
23000 * from uiGridPinningConstants
23002 pinColumn: function(col, container) {
23003 service.pinColumn(grid, col, container);
23009 grid.api.registerEventsFromObject(publicApi.events);
23010 grid.api.registerMethodsFromObject(publicApi.methods);
23013 defaultGridOptions: function (gridOptions) {
23014 //default option to true unless it was explicitly set to false
23017 * @name ui.grid.pinning.api:GridOptions
23019 * @description GridOptions for pinning feature, these are available to be
23020 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23025 * @name enablePinning
23026 * @propertyOf ui.grid.pinning.api:GridOptions
23027 * @description Enable pinning for the entire grid.
23028 * <br/>Defaults to true
23030 gridOptions.enablePinning = gridOptions.enablePinning !== false;
23034 pinningColumnBuilder: function (colDef, col, gridOptions) {
23035 //default to true unless gridOptions or colDef is explicitly false
23039 * @name ui.grid.pinning.api:ColumnDef
23041 * @description ColumnDef for pinning feature, these are available to be
23042 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
23047 * @name enablePinning
23048 * @propertyOf ui.grid.pinning.api:ColumnDef
23049 * @description Enable pinning for the individual column.
23050 * <br/>Defaults to true
23052 colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
23058 * @propertyOf ui.grid.pinning.api:ColumnDef
23059 * @description Column is pinned left when grid is rendered
23060 * <br/>Defaults to false
23065 * @name pinnedRight
23066 * @propertyOf ui.grid.pinning.api:ColumnDef
23067 * @description Column is pinned right when grid is rendered
23068 * <br/>Defaults to false
23070 if (colDef.pinnedLeft) {
23071 col.renderContainer = 'left';
23072 col.grid.createLeftContainer();
23074 else if (colDef.pinnedRight) {
23075 col.renderContainer = 'right';
23076 col.grid.createRightContainer();
23079 if (!colDef.enablePinning) {
23083 var pinColumnLeftAction = {
23084 name: 'ui.grid.pinning.pinLeft',
23085 title: i18nService.get().pinning.pinLeft,
23086 icon: 'ui-grid-icon-left-open',
23087 shown: function () {
23088 return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
23090 action: function () {
23091 service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.LEFT);
23095 var pinColumnRightAction = {
23096 name: 'ui.grid.pinning.pinRight',
23097 title: i18nService.get().pinning.pinRight,
23098 icon: 'ui-grid-icon-right-open',
23099 shown: function () {
23100 return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
23102 action: function () {
23103 service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.RIGHT);
23107 var removePinAction = {
23108 name: 'ui.grid.pinning.unpin',
23109 title: i18nService.get().pinning.unpin,
23110 icon: 'ui-grid-icon-cancel',
23111 shown: function () {
23112 return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
23114 action: function () {
23115 service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.NONE);
23119 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
23120 col.menuItems.push(pinColumnLeftAction);
23122 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
23123 col.menuItems.push(pinColumnRightAction);
23125 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
23126 col.menuItems.push(removePinAction);
23130 pinColumn: function(grid, col, container) {
23131 if (container === uiGridPinningConstants.container.NONE) {
23132 col.renderContainer = null;
23133 col.colDef.pinnedLeft = col.colDef.pinnedRight = false;
23136 col.renderContainer = container;
23137 if (container === uiGridPinningConstants.container.LEFT) {
23138 grid.createLeftContainer();
23140 else if (container === uiGridPinningConstants.container.RIGHT) {
23141 grid.createRightContainer();
23147 grid.api.pinning.raise.columnPinned( col.colDef, container );
23155 module.directive('uiGridPinning', ['gridUtil', 'uiGridPinningService',
23156 function (gridUtil, uiGridPinningService) {
23160 compile: function () {
23162 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
23163 uiGridPinningService.initializeGrid(uiGridCtrl.grid);
23165 post: function ($scope, $elm, $attrs, uiGridCtrl) {
23180 * @name ui.grid.resizeColumns
23183 * # ui.grid.resizeColumns
23185 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
23187 * This module allows columns to be resized.
23189 var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
23191 module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
23192 function (gridUtil, $q, $timeout) {
23195 defaultGridOptions: function(gridOptions){
23196 //default option to true unless it was explicitly set to false
23199 * @name ui.grid.resizeColumns.api:GridOptions
23201 * @description GridOptions for resizeColumns feature, these are available to be
23202 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23207 * @name enableColumnResizing
23208 * @propertyOf ui.grid.resizeColumns.api:GridOptions
23209 * @description Enable column resizing on the entire grid
23210 * <br/>Defaults to true
23212 gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
23215 //use old name if it is explicitly false
23216 if (gridOptions.enableColumnResize === false){
23217 gridOptions.enableColumnResizing = false;
23221 colResizerColumnBuilder: function (colDef, col, gridOptions) {
23226 * @name ui.grid.resizeColumns.api:ColumnDef
23228 * @description ColumnDef for resizeColumns feature, these are available to be
23229 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
23234 * @name enableColumnResizing
23235 * @propertyOf ui.grid.resizeColumns.api:ColumnDef
23236 * @description Enable column resizing on an individual column
23237 * <br/>Defaults to GridOptions.enableColumnResizing
23239 //default to true unless gridOptions or colDef is explicitly false
23240 colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
23243 //legacy support of old option name
23244 if (colDef.enableColumnResize === false){
23245 colDef.enableColumnResizing = false;
23248 return $q.all(promises);
23251 registerPublicApi: function (grid) {
23254 * @name ui.grid.resizeColumns.api:PublicApi
23255 * @description Public Api for column resize feature.
23261 * @name columnSizeChanged
23262 * @eventOf ui.grid.resizeColumns.api:PublicApi
23263 * @description raised when column is resized
23265 * gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
23267 * @param {object} colDef the column that was resized
23268 * @param {integer} delta of the column size change
23271 columnSizeChanged: function (colDef, deltaChange) {
23276 grid.api.registerEventsFromObject(publicApi.events);
23279 fireColumnSizeChanged: function (grid, colDef, deltaChange) {
23280 $timeout(function () {
23281 if ( grid.api.colResizable ){
23282 grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
23284 gridUtil.logError("The resizeable api is not registered, this may indicate that you've included the module but not added the 'ui-grid-resize-columns' directive to your grid definition. Cannot raise any events.");
23289 // get either this column, or the column next to this column, to resize,
23290 // returns the column we're going to resize
23291 findTargetCol: function(col, position, rtlMultiplier){
23292 var renderContainer = col.getRenderContainer();
23294 if (position === 'left') {
23295 // Get the column to the left of this one
23296 var colIndex = renderContainer.visibleColumnCache.indexOf(col);
23297 return renderContainer.visibleColumnCache[colIndex - 1 * rtlMultiplier];
23312 * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
23316 * Enables resizing for all columns on the grid. If, for some reason, you want to use the ui-grid-resize-columns directive, but not allow column resizing, you can explicitly set the
23317 * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
23320 <doc:example module="app">
23323 var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
23325 app.controller('MainCtrl', ['$scope', function ($scope) {
23326 $scope.gridOpts = {
23328 { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
23329 { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
23330 { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
23331 { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
23337 <div ng-controller="MainCtrl">
23338 <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
23346 module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
23350 require: '^uiGrid',
23352 compile: function () {
23354 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
23355 uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
23356 uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
23357 uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
23359 post: function ($scope, $elm, $attrs, uiGridCtrl) {
23366 // Extend the uiGridHeaderCell directive
23367 module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', '$timeout', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants, $timeout) {
23369 // Run after the original uiGridHeaderCell
23371 require: '^uiGrid',
23373 compile: function() {
23375 post: function ($scope, $elm, $attrs, uiGridCtrl) {
23376 var grid = uiGridCtrl.grid;
23378 if (grid.options.enableColumnResizing) {
23379 var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
23381 var rtlMultiplier = 1;
23382 //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
23383 if (grid.isRTL()) {
23384 $scope.position = 'left';
23385 rtlMultiplier = -1;
23388 var displayResizers = function(){
23390 // remove any existing resizers.
23391 var resizers = $elm[0].getElementsByClassName('ui-grid-column-resizer');
23392 for ( var i = 0; i < resizers.length; i++ ){
23393 angular.element(resizers[i]).remove();
23396 // get the target column for the left resizer
23397 var otherCol = uiGridResizeColumnsService.findTargetCol($scope.col, 'left', rtlMultiplier);
23398 var renderContainer = $scope.col.getRenderContainer();
23400 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
23401 if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
23402 var resizerLeft = angular.element(columnResizerElm).clone();
23403 resizerLeft.attr('position', 'left');
23405 $elm.prepend(resizerLeft);
23406 $compile(resizerLeft)($scope);
23409 // Don't append the right resizer if this column has resizing disabled
23410 if ($scope.col.colDef.enableColumnResizing !== false) {
23411 var resizerRight = angular.element(columnResizerElm).clone();
23412 resizerRight.attr('position', 'right');
23414 $elm.append(resizerRight);
23415 $compile(resizerRight)($scope);
23421 var waitDisplay = function(){
23422 $timeout(displayResizers);
23425 var dataChangeDereg = grid.registerDataChangeCallback( waitDisplay, [uiGridConstants.dataChange.COLUMN] );
23427 $scope.$on( '$destroy', dataChangeDereg );
23439 * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
23444 * Draggable handle that controls column resizing.
23447 <doc:example module="app">
23450 var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
23452 app.controller('MainCtrl', ['$scope', function ($scope) {
23453 $scope.gridOpts = {
23454 enableColumnResizing: true,
23456 { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
23457 { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
23458 { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
23459 { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
23465 <div ng-controller="MainCtrl">
23466 <div class="testGrid" ui-grid="gridOpts"></div>
23470 // TODO: e2e specs?
23472 // TODO: post-resize a horizontal scroll event should be fired
23476 module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, uiGridResizeColumnsService) {
23477 var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
23486 require: '?^uiGrid',
23487 link: function ($scope, $elm, $attrs, uiGridCtrl) {
23493 //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
23494 if (uiGridCtrl.grid.isRTL()) {
23495 $scope.position = 'left';
23496 rtlMultiplier = -1;
23499 if ($scope.position === 'left') {
23500 $elm.addClass('left');
23502 else if ($scope.position === 'right') {
23503 $elm.addClass('right');
23506 // Refresh the grid canvas
23507 // takes an argument representing the diff along the X-axis that the resize had
23508 function refreshCanvas(xDiff) {
23509 // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
23510 uiGridCtrl.grid.refreshCanvas(true).then( function() {
23511 uiGridCtrl.grid.queueGridRefresh();
23515 // Check that the requested width isn't wider than the maxWidth, or narrower than the minWidth
23516 // Returns the new recommended with, after constraints applied
23517 function constrainWidth(col, width){
23518 var newWidth = width;
23520 // If the new width would be less than the column's allowably minimum width, don't allow it
23521 if (col.minWidth && newWidth < col.minWidth) {
23522 newWidth = col.minWidth;
23524 else if (col.maxWidth && newWidth > col.maxWidth) {
23525 newWidth = col.maxWidth;
23533 * Our approach to event handling aims to deal with both touch devices and mouse devices
23534 * We register down handlers on both touch and mouse. When a touchstart or mousedown event
23535 * occurs, we register the corresponding touchmove/touchend, or mousemove/mouseend events.
23537 * This way we can listen for both without worrying about the fact many touch devices also emulate
23538 * mouse events - basically whichever one we hear first is what we'll go with.
23540 function moveFunction(event, args) {
23541 if (event.originalEvent) { event = event.originalEvent; }
23542 event.preventDefault();
23544 x = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
23546 if (x < 0) { x = 0; }
23547 else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
23549 var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
23551 // Don't resize if it's disabled on this column
23552 if (col.colDef.enableColumnResizing === false) {
23556 if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
23557 uiGridCtrl.grid.element.addClass('column-resizing');
23560 // Get the diff along the X axis
23561 var xDiff = x - startX;
23563 // Get the width that this mouse would give the column
23564 var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
23566 // check we're not outside the allowable bounds for this column
23567 x = x + ( constrainWidth(col, newWidth) - newWidth ) * rtlMultiplier;
23569 resizeOverlay.css({ left: x + 'px' });
23571 uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
23575 function upFunction(event, args) {
23576 if (event.originalEvent) { event = event.originalEvent; }
23577 event.preventDefault();
23579 uiGridCtrl.grid.element.removeClass('column-resizing');
23581 resizeOverlay.remove();
23583 // Resize the column
23584 x = (event.changedTouches ? event.changedTouches[0] : event).clientX - gridLeft;
23585 var xDiff = x - startX;
23588 // no movement, so just reset event handlers, including turning back on both
23589 // down events - we turned one off when this event started
23595 var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
23597 // Don't resize if it's disabled on this column
23598 if (col.colDef.enableColumnResizing === false) {
23602 // Get the new width
23603 var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
23605 // check we're not outside the allowable bounds for this column
23606 col.width = constrainWidth(col, newWidth);
23607 col.hasCustomWidth = true;
23609 refreshCanvas(xDiff);
23611 uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
23613 // stop listening of up and move events - wait for next down
23614 // reset the down events - we will have turned one off when this event started
23620 var downFunction = function(event, args) {
23621 if (event.originalEvent) { event = event.originalEvent; }
23622 event.stopPropagation();
23624 // Get the left offset of the grid
23625 // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
23626 gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
23628 // Get the starting X position, which is the X coordinate of the click minus the grid's offset
23629 startX = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
23631 // Append the resizer overlay
23632 uiGridCtrl.grid.element.append(resizeOverlay);
23634 // Place the resizer overlay at the start position
23635 resizeOverlay.css({ left: startX });
23637 // Add handlers for move and up events - if we were mousedown then we listen for mousemove and mouseup, if
23638 // we were touchdown then we listen for touchmove and touchup. Also remove the handler for the equivalent
23639 // down event - so if we're touchdown, then remove the mousedown handler until this event is over, if we're
23640 // mousedown then remove the touchdown handler until this event is over, this avoids processing duplicate events
23641 if ( event.type === 'touchstart' ){
23642 $document.on('touchend', upFunction);
23643 $document.on('touchmove', moveFunction);
23644 $elm.off('mousedown', downFunction);
23646 $document.on('mouseup', upFunction);
23647 $document.on('mousemove', moveFunction);
23648 $elm.off('touchstart', downFunction);
23652 var onDownEvents = function() {
23653 $elm.on('mousedown', downFunction);
23654 $elm.on('touchstart', downFunction);
23657 var offAllEvents = function() {
23658 $document.off('mouseup', upFunction);
23659 $document.off('touchend', upFunction);
23660 $document.off('mousemove', moveFunction);
23661 $document.off('touchmove', moveFunction);
23662 $elm.off('mousedown', downFunction);
23663 $elm.off('touchstart', downFunction);
23669 // On doubleclick, resize to fit all rendered cells
23670 var dblClickFn = function(event, args){
23671 event.stopPropagation();
23673 var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
23675 // Don't resize if it's disabled on this column
23676 if (col.colDef.enableColumnResizing === false) {
23680 // Go through the rendered rows and find out the max size for the data in this column
23684 // Get the parent render container element
23685 var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
23687 // Get the cell contents so we measure correctly. For the header cell we have to account for the sort icon and the menu buttons, if present
23688 var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
23689 Array.prototype.forEach.call(cells, function (cell) {
23690 // Get the cell width
23691 // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
23693 // Account for the menu button if it exists
23695 if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
23696 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
23699 gridUtil.fakeElement(cell, {}, function(newElm) {
23700 // Make the element float since it's a div and can expand to fill its container
23701 var e = angular.element(newElm);
23702 e.attr('style', 'float: left');
23704 var width = gridUtil.elementWidth(e);
23707 var menuButtonWidth = gridUtil.elementWidth(menuButton);
23708 width = width + menuButtonWidth;
23711 if (width > maxWidth) {
23713 xDiff = maxWidth - width;
23718 // check we're not outside the allowable bounds for this column
23719 col.width = constrainWidth(col, maxWidth);
23720 col.hasCustomWidth = true;
23722 refreshCanvas(xDiff);
23724 uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff); };
23725 $elm.on('dblclick', dblClickFn);
23727 $elm.on('$destroy', function() {
23728 $elm.off('dblclick', dblClickFn);
23744 * @name ui.grid.rowEdit
23747 * # ui.grid.rowEdit
23749 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
23751 * This module extends the edit feature to provide tracking and saving of rows
23752 * of data. The tutorial provides more information on how this feature is best
23753 * used {@link tutorial/205_row_editable here}.
23755 * This feature depends on usage of the ui-grid-edit feature, and also benefits
23756 * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
23761 var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
23765 * @name ui.grid.rowEdit.constant:uiGridRowEditConstants
23767 * @description constants available in row edit module
23769 module.constant('uiGridRowEditConstants', {
23774 * @name ui.grid.rowEdit.service:uiGridRowEditService
23776 * @description Services for row editing features
23778 module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
23779 function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
23783 initializeGrid: function (scope, grid) {
23786 * @name ui.grid.rowEdit.api:PublicApi
23788 * @description Public Api for rowEdit feature
23798 * @eventOf ui.grid.rowEdit.api:PublicApi
23800 * @description raised when a row is ready for saving. Once your
23801 * row has saved you may need to use angular.extend to update the
23802 * data entity with any changed data from your save (for example,
23803 * lock version information if you're using optimistic locking,
23804 * or last update time/user information).
23806 * Your method should call setSavePromise somewhere in the body before
23807 * returning control. The feature will then wait, with the gridRow greyed out
23808 * whilst this promise is being resolved.
23811 * gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
23813 * and somewhere within the event handler:
23815 * gridApi.rowEdit.setSavePromise( rowEntity, savePromise)
23817 * @param {object} rowEntity the options.data element that was edited
23818 * @returns {promise} Your saveRow method should return a promise, the
23819 * promise should either be resolved (implying successful save), or
23820 * rejected (implying an error).
23822 saveRow: function (rowEntity) {
23830 * @methodOf ui.grid.rowEdit.api:PublicApi
23831 * @name setSavePromise
23832 * @description Sets the promise associated with the row save, mandatory that
23833 * the saveRow event handler calls this method somewhere before returning.
23835 * gridApi.rowEdit.setSavePromise(rowEntity, savePromise)
23837 * @param {object} rowEntity a data row from the grid for which a save has
23839 * @param {promise} savePromise the promise that will be resolved when the
23840 * save is successful, or rejected if the save fails
23843 setSavePromise: function ( rowEntity, savePromise) {
23844 service.setSavePromise(grid, rowEntity, savePromise);
23848 * @methodOf ui.grid.rowEdit.api:PublicApi
23849 * @name getDirtyRows
23850 * @description Returns all currently dirty rows
23852 * gridApi.rowEdit.getDirtyRows(grid)
23854 * @returns {array} An array of gridRows that are currently dirty
23857 getDirtyRows: function () {
23858 return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
23862 * @methodOf ui.grid.rowEdit.api:PublicApi
23863 * @name getErrorRows
23864 * @description Returns all currently errored rows
23866 * gridApi.rowEdit.getErrorRows(grid)
23868 * @returns {array} An array of gridRows that are currently in error
23871 getErrorRows: function () {
23872 return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
23876 * @methodOf ui.grid.rowEdit.api:PublicApi
23877 * @name flushDirtyRows
23878 * @description Triggers a save event for all currently dirty rows, could
23879 * be used where user presses a save button or navigates away from the page
23881 * gridApi.rowEdit.flushDirtyRows(grid)
23883 * @returns {promise} a promise that represents the aggregate of all
23884 * of the individual save promises - i.e. it will be resolved when all
23885 * the individual save promises have been resolved.
23888 flushDirtyRows: function () {
23889 return service.flushDirtyRows(grid);
23894 * @methodOf ui.grid.rowEdit.api:PublicApi
23895 * @name setRowsDirty
23896 * @description Sets each of the rows passed in dataRows
23897 * to be dirty. note that if you have only just inserted the
23898 * rows into your data you will need to wait for a $digest cycle
23899 * before the gridRows are present - so often you would wrap this
23900 * call in a $interval or $timeout
23902 * $interval( function() {
23903 * gridApi.rowEdit.setRowsDirty(myDataRows);
23906 * @param {array} dataRows the data entities for which the gridRows
23907 * should be set dirty.
23910 setRowsDirty: function ( dataRows) {
23911 service.setRowsDirty(grid, dataRows);
23916 * @methodOf ui.grid.rowEdit.api:PublicApi
23917 * @name setRowsClean
23918 * @description Sets each of the rows passed in dataRows
23919 * to be clean, removing them from the dirty cache and the error cache,
23920 * and clearing the error flag and the dirty flag
23922 * var gridRows = $scope.gridApi.rowEdit.getDirtyRows();
23923 * var dataRows = gridRows.map( function( gridRow ) { return gridRow.entity; });
23924 * $scope.gridApi.rowEdit.setRowsClean( dataRows );
23926 * @param {array} dataRows the data entities for which the gridRows
23927 * should be set clean.
23930 setRowsClean: function ( dataRows) {
23931 service.setRowsClean(grid, dataRows);
23937 grid.api.registerEventsFromObject(publicApi.events);
23938 grid.api.registerMethodsFromObject(publicApi.methods);
23940 grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
23941 grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
23942 grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
23943 grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
23945 if ( grid.api.cellNav ) {
23946 grid.api.cellNav.on.navigate( scope, service.navigate );
23952 defaultGridOptions: function (gridOptions) {
23956 * @name ui.grid.rowEdit.api:GridOptions
23958 * @description Options for configuring the rowEdit feature, these are available to be
23959 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
23967 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
23969 * @description Returns a function that saves the specified row from the grid,
23970 * and returns a promise
23971 * @param {object} grid the grid for which dirty rows should be flushed
23972 * @param {GridRow} gridRow the row that should be saved
23973 * @returns {function} the saveRow function returns a function. That function
23974 * in turn, when called, returns a promise relating to the save callback
23976 saveRow: function ( grid, gridRow ) {
23979 return function() {
23980 gridRow.isSaving = true;
23982 if ( gridRow.rowEditSavePromise ){
23983 // don't save the row again if it's already saving - that causes stale object exceptions
23984 return gridRow.rowEditSavePromise;
23987 var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
23989 if ( gridRow.rowEditSavePromise ){
23990 gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
23992 gridUtil.logError( 'A promise was not returned when saveRow event was raised, either nobody is listening to event, or event handler did not return a promise' );
24001 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24002 * @name setSavePromise
24003 * @description Sets the promise associated with the row save, mandatory that
24004 * the saveRow event handler calls this method somewhere before returning.
24006 * gridApi.rowEdit.setSavePromise(grid, rowEntity)
24008 * @param {object} grid the grid for which dirty rows should be returned
24009 * @param {object} rowEntity a data row from the grid for which a save has
24011 * @param {promise} savePromise the promise that will be resolved when the
24012 * save is successful, or rejected if the save fails
24015 setSavePromise: function (grid, rowEntity, savePromise) {
24016 var gridRow = grid.getRow( rowEntity );
24017 gridRow.rowEditSavePromise = savePromise;
24023 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24024 * @name processSuccessPromise
24025 * @description Returns a function that processes the successful
24026 * resolution of a save promise
24027 * @param {object} grid the grid for which the promise should be processed
24028 * @param {GridRow} gridRow the row that has been saved
24029 * @returns {function} the success handling function
24031 processSuccessPromise: function ( grid, gridRow ) {
24034 return function() {
24035 delete gridRow.isSaving;
24036 delete gridRow.isDirty;
24037 delete gridRow.isError;
24038 delete gridRow.rowEditSaveTimer;
24039 delete gridRow.rowEditSavePromise;
24040 self.removeRow( grid.rowEdit.errorRows, gridRow );
24041 self.removeRow( grid.rowEdit.dirtyRows, gridRow );
24048 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24049 * @name processErrorPromise
24050 * @description Returns a function that processes the failed
24051 * resolution of a save promise
24052 * @param {object} grid the grid for which the promise should be processed
24053 * @param {GridRow} gridRow the row that is now in error
24054 * @returns {function} the error handling function
24056 processErrorPromise: function ( grid, gridRow ) {
24057 return function() {
24058 delete gridRow.isSaving;
24059 delete gridRow.rowEditSaveTimer;
24060 delete gridRow.rowEditSavePromise;
24062 gridRow.isError = true;
24064 if (!grid.rowEdit.errorRows){
24065 grid.rowEdit.errorRows = [];
24067 if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
24068 grid.rowEdit.errorRows.push( gridRow );
24076 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24078 * @description Removes a row from a cache of rows - either
24079 * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows. If the row
24080 * is not present silently does nothing.
24081 * @param {array} rowArray the array from which to remove the row
24082 * @param {GridRow} gridRow the row that should be removed
24084 removeRow: function( rowArray, removeGridRow ){
24085 if (typeof(rowArray) === 'undefined' || rowArray === null){
24089 rowArray.forEach( function( gridRow, index ){
24090 if ( gridRow.uid === removeGridRow.uid ){
24091 rowArray.splice( index, 1);
24099 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24100 * @name isRowPresent
24101 * @description Checks whether a row is already present
24102 * in the given array
24103 * @param {array} rowArray the array in which to look for the row
24104 * @param {GridRow} gridRow the row that should be looked for
24106 isRowPresent: function( rowArray, removeGridRow ){
24107 var present = false;
24108 rowArray.forEach( function( gridRow, index ){
24109 if ( gridRow.uid === removeGridRow.uid ){
24119 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24120 * @name flushDirtyRows
24121 * @description Triggers a save event for all currently dirty rows, could
24122 * be used where user presses a save button or navigates away from the page
24124 * gridApi.rowEdit.flushDirtyRows(grid)
24126 * @param {object} grid the grid for which dirty rows should be flushed
24127 * @returns {promise} a promise that represents the aggregate of all
24128 * of the individual save promises - i.e. it will be resolved when all
24129 * the individual save promises have been resolved.
24132 flushDirtyRows: function(grid){
24134 grid.api.rowEdit.getDirtyRows().forEach( function( gridRow ){
24135 service.saveRow( grid, gridRow )();
24136 promises.push( gridRow.rowEditSavePromise );
24139 return $q.all( promises );
24145 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24146 * @name endEditCell
24147 * @description Receives an afterCellEdit event from the edit function,
24148 * and sets flags as appropriate. Only the rowEntity parameter
24149 * is processed, although other params are available. Grid
24150 * is automatically provided by the gridApi.
24151 * @param {object} rowEntity the data entity for which the cell
24154 endEditCell: function( rowEntity, colDef, newValue, previousValue ){
24155 var grid = this.grid;
24156 var gridRow = grid.getRow( rowEntity );
24157 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
24159 if ( newValue !== previousValue || gridRow.isDirty ){
24160 if ( !grid.rowEdit.dirtyRows ){
24161 grid.rowEdit.dirtyRows = [];
24164 if ( !gridRow.isDirty ){
24165 gridRow.isDirty = true;
24166 grid.rowEdit.dirtyRows.push( gridRow );
24169 delete gridRow.isError;
24171 service.considerSetTimer( grid, gridRow );
24178 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24179 * @name beginEditCell
24180 * @description Receives a beginCellEdit event from the edit function,
24181 * and cancels any rowEditSaveTimers if present, as the user is still editing
24182 * this row. Only the rowEntity parameter
24183 * is processed, although other params are available. Grid
24184 * is automatically provided by the gridApi.
24185 * @param {object} rowEntity the data entity for which the cell
24186 * editing has commenced
24188 beginEditCell: function( rowEntity, colDef ){
24189 var grid = this.grid;
24190 var gridRow = grid.getRow( rowEntity );
24191 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
24193 service.cancelTimer( grid, gridRow );
24199 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24200 * @name cancelEditCell
24201 * @description Receives a cancelCellEdit event from the edit function,
24202 * and if the row was already dirty, restarts the save timer. If the row
24203 * was not already dirty, then it's not dirty now either and does nothing.
24205 * Only the rowEntity parameter
24206 * is processed, although other params are available. Grid
24207 * is automatically provided by the gridApi.
24209 * @param {object} rowEntity the data entity for which the cell
24210 * editing was cancelled
24212 cancelEditCell: function( rowEntity, colDef ){
24213 var grid = this.grid;
24214 var gridRow = grid.getRow( rowEntity );
24215 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
24217 service.considerSetTimer( grid, gridRow );
24223 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24225 * @description cellNav tells us that the selected cell has changed. If
24226 * the new row had a timer running, then stop it similar to in a beginCellEdit
24227 * call. If the old row is dirty and not the same as the new row, then
24228 * start a timer on it.
24229 * @param {object} newRowCol the row and column that were selected
24230 * @param {object} oldRowCol the row and column that was left
24233 navigate: function( newRowCol, oldRowCol ){
24234 var grid = this.grid;
24235 if ( newRowCol.row.rowEditSaveTimer ){
24236 service.cancelTimer( grid, newRowCol.row );
24239 if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
24240 service.considerSetTimer( grid, oldRowCol.row );
24247 * @propertyOf ui.grid.rowEdit.api:GridOptions
24248 * @name rowEditWaitInterval
24249 * @description How long the grid should wait for another change on this row
24250 * before triggering a save (in milliseconds). If set to -1, then saves are
24251 * never triggered by timer (implying that the user will call flushDirtyRows()
24255 * Setting the wait interval to 4 seconds
24257 * $scope.gridOptions = { rowEditWaitInterval: 4000 }
24263 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24264 * @name considerSetTimer
24265 * @description Consider setting a timer on this row (if it is dirty). if there is a timer running
24266 * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
24267 * dirty and not currently saving then set a new timer
24268 * @param {object} grid the grid for which we are processing
24269 * @param {GridRow} gridRow the row for which the timer should be adjusted
24272 considerSetTimer: function( grid, gridRow ){
24273 service.cancelTimer( grid, gridRow );
24275 if ( gridRow.isDirty && !gridRow.isSaving ){
24276 if ( grid.options.rowEditWaitInterval !== -1 ){
24277 var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
24278 gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
24286 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24287 * @name cancelTimer
24288 * @description cancel the $interval for any timer running on this row
24289 * then delete the timer itself
24290 * @param {object} grid the grid for which we are processing
24291 * @param {GridRow} gridRow the row for which the timer should be adjusted
24294 cancelTimer: function( grid, gridRow ){
24295 if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
24296 $interval.cancel(gridRow.rowEditSaveTimer);
24297 delete gridRow.rowEditSaveTimer;
24304 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24305 * @name setRowsDirty
24306 * @description Sets each of the rows passed in dataRows
24307 * to be dirty. note that if you have only just inserted the
24308 * rows into your data you will need to wait for a $digest cycle
24309 * before the gridRows are present - so often you would wrap this
24310 * call in a $interval or $timeout
24312 * $interval( function() {
24313 * gridApi.rowEdit.setRowsDirty( myDataRows);
24316 * @param {object} grid the grid for which rows should be set dirty
24317 * @param {array} dataRows the data entities for which the gridRows
24318 * should be set dirty.
24321 setRowsDirty: function( grid, myDataRows ) {
24323 myDataRows.forEach( function( value, index ){
24324 gridRow = grid.getRow( value );
24326 if ( !grid.rowEdit.dirtyRows ){
24327 grid.rowEdit.dirtyRows = [];
24330 if ( !gridRow.isDirty ){
24331 gridRow.isDirty = true;
24332 grid.rowEdit.dirtyRows.push( gridRow );
24335 delete gridRow.isError;
24337 service.considerSetTimer( grid, gridRow );
24339 gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
24347 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
24348 * @name setRowsClean
24349 * @description Sets each of the rows passed in dataRows
24350 * to be clean, clearing the dirty flag and the error flag, and removing
24351 * the rows from the dirty and error caches.
24352 * @param {object} grid the grid for which rows should be set clean
24353 * @param {array} dataRows the data entities for which the gridRows
24354 * should be set clean.
24357 setRowsClean: function( grid, myDataRows ) {
24360 myDataRows.forEach( function( value, index ){
24361 gridRow = grid.getRow( value );
24363 delete gridRow.isDirty;
24364 service.removeRow( grid.rowEdit.dirtyRows, gridRow );
24365 service.cancelTimer( grid, gridRow );
24367 delete gridRow.isError;
24368 service.removeRow( grid.rowEdit.errorRows, gridRow );
24370 gridUtil.logError( "requested row not found in rowEdit.setRowsClean, row was: " + value );
24383 * @name ui.grid.rowEdit.directive:uiGridEdit
24387 * @description Adds row editing features to the ui-grid-edit directive.
24390 module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants',
24391 function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
24395 require: '^uiGrid',
24397 compile: function () {
24399 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
24400 uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
24402 post: function ($scope, $elm, $attrs, uiGridCtrl) {
24412 * @name ui.grid.rowEdit.directive:uiGridViewport
24415 * @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
24416 * for the grid row to allow coloring of saving and error rows
24418 module.directive('uiGridViewport',
24419 ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
24420 function ($compile, uiGridConstants, gridUtil, $parse) {
24422 priority: -200, // run after default directive
24424 compile: function ($elm, $attrs) {
24425 var rowRepeatDiv = angular.element($elm.children().children()[0]);
24427 var existingNgClass = rowRepeatDiv.attr("ng-class");
24428 var newNgClass = '';
24429 if ( existingNgClass ) {
24430 newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
24432 newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
24434 rowRepeatDiv.attr("ng-class", newNgClass);
24437 pre: function ($scope, $elm, $attrs, controllers) {
24440 post: function ($scope, $elm, $attrs, controllers) {
24454 * @name ui.grid.saveState
24457 * # ui.grid.saveState
24459 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
24461 * This module provides the ability to save the grid state, and restore
24462 * it when the user returns to the page.
24464 * No UI is provided, the caller should provide their own UI/buttons
24465 * as appropriate. Usually the navigate events would be used to save
24466 * the grid state and restore it.
24471 * <div doc-module-components="ui.grid.save-state"></div>
24474 var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping', 'ui.grid.pinning', 'ui.grid.treeView']);
24478 * @name ui.grid.saveState.constant:uiGridSaveStateConstants
24480 * @description constants available in save state module
24483 module.constant('uiGridSaveStateConstants', {
24484 featureName: 'saveState'
24489 * @name ui.grid.saveState.service:uiGridSaveStateService
24491 * @description Services for saveState feature
24493 module.service('uiGridSaveStateService', ['$q', 'uiGridSaveStateConstants', 'gridUtil', '$compile', '$interval', 'uiGridConstants',
24494 function ($q, uiGridSaveStateConstants, gridUtil, $compile, $interval, uiGridConstants ) {
24498 initializeGrid: function (grid) {
24500 //add feature namespace and any properties to grid for needed state
24501 grid.saveState = {};
24502 this.defaultGridOptions(grid.options);
24506 * @name ui.grid.saveState.api:PublicApi
24508 * @description Public Api for saveState feature
24520 * @methodOf ui.grid.saveState.api:PublicApi
24521 * @description Packages the current state of the grid into
24522 * an object, and provides it to the user for saving
24523 * @returns {object} the state as a javascript object that can be saved
24525 save: function () {
24526 return service.save(grid);
24531 * @methodOf ui.grid.saveState.api:PublicApi
24532 * @description Restores the provided state into the grid
24533 * @param {scope} $scope a scope that we can broadcast on
24534 * @param {object} state the state that should be restored into the grid
24536 restore: function ( $scope, state) {
24537 service.restore(grid, $scope, state);
24543 grid.api.registerEventsFromObject(publicApi.events);
24545 grid.api.registerMethodsFromObject(publicApi.methods);
24549 defaultGridOptions: function (gridOptions) {
24550 //default option to true unless it was explicitly set to false
24553 * @name ui.grid.saveState.api:GridOptions
24555 * @description GridOptions for saveState feature, these are available to be
24556 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
24561 * @propertyOf ui.grid.saveState.api:GridOptions
24562 * @description Save the current column widths. Note that unless
24563 * you've provided the user with some way to resize their columns (say
24564 * the resize columns feature), then this makes little sense.
24565 * <br/>Defaults to true
24567 gridOptions.saveWidths = gridOptions.saveWidths !== false;
24571 * @propertyOf ui.grid.saveState.api:GridOptions
24572 * @description Restore the current column order. Note that unless
24573 * you've provided the user with some way to reorder their columns (for
24574 * example the move columns feature), this makes little sense.
24575 * <br/>Defaults to true
24577 gridOptions.saveOrder = gridOptions.saveOrder !== false;
24581 * @propertyOf ui.grid.saveState.api:GridOptions
24582 * @description Save the current scroll position. Note that this
24583 * is saved as the percentage of the grid scrolled - so if your
24584 * user returns to a grid with a significantly different number of
24585 * rows (perhaps some data has been deleted) then the scroll won't
24586 * actually show the same rows as before. If you want to scroll to
24587 * a specific row then you should instead use the saveFocus option, which
24590 * Note that this element will only be saved if the cellNav feature is
24592 * <br/>Defaults to false
24594 gridOptions.saveScroll = gridOptions.saveScroll === true;
24598 * @propertyOf ui.grid.saveState.api:GridOptions
24599 * @description Save the current focused cell. On returning
24600 * to this focused cell we'll also scroll. This option is
24601 * preferred to the saveScroll option, so is set to true by
24602 * default. If saveScroll is set to true then this option will
24605 * By default this option saves the current row number and column
24606 * number, and returns to that row and column. However, if you define
24607 * a saveRowIdentity function, then it will return you to the currently
24608 * selected column within that row (in a business sense - so if some
24609 * rows have been deleted, it will still find the same data, presuming it
24610 * still exists in the list. If it isn't in the list then it will instead
24611 * return to the same row number - i.e. scroll percentage)
24613 * Note that this option will do nothing if the cellNav
24614 * feature is not enabled.
24616 * <br/>Defaults to true (unless saveScroll is true)
24618 gridOptions.saveFocus = gridOptions.saveScroll !== true && gridOptions.saveFocus !== false;
24621 * @name saveRowIdentity
24622 * @propertyOf ui.grid.saveState.api:GridOptions
24623 * @description A function that can be called, passing in a rowEntity,
24624 * and that will return a unique id for that row. This might simply
24625 * return the `id` field from that row (if you have one), or it might
24626 * concatenate some fields within the row to make a unique value.
24628 * This value will be used to find the same row again and set the focus
24629 * to it, if it exists when we return.
24631 * <br/>Defaults to undefined
24635 * @name saveVisible
24636 * @propertyOf ui.grid.saveState.api:GridOptions
24637 * @description Save whether or not columns are visible.
24639 * <br/>Defaults to true
24641 gridOptions.saveVisible = gridOptions.saveVisible !== false;
24645 * @propertyOf ui.grid.saveState.api:GridOptions
24646 * @description Save the current sort state for each column
24648 * <br/>Defaults to true
24650 gridOptions.saveSort = gridOptions.saveSort !== false;
24654 * @propertyOf ui.grid.saveState.api:GridOptions
24655 * @description Save the current filter state for each column
24657 * <br/>Defaults to true
24659 gridOptions.saveFilter = gridOptions.saveFilter !== false;
24662 * @name saveSelection
24663 * @propertyOf ui.grid.saveState.api:GridOptions
24664 * @description Save the currently selected rows. If the `saveRowIdentity` callback
24665 * is defined, then it will save the id of the row and select that. If not, then
24666 * it will attempt to select the rows by row number, which will give the wrong results
24667 * if the data set has changed in the mean-time.
24669 * Note that this option only does anything
24670 * if the selection feature is enabled.
24672 * <br/>Defaults to true
24674 gridOptions.saveSelection = gridOptions.saveSelection !== false;
24677 * @name saveGrouping
24678 * @propertyOf ui.grid.saveState.api:GridOptions
24679 * @description Save the grouping configuration. If set to true and the
24680 * grouping feature is not enabled then does nothing.
24682 * <br/>Defaults to true
24684 gridOptions.saveGrouping = gridOptions.saveGrouping !== false;
24687 * @name saveGroupingExpandedStates
24688 * @propertyOf ui.grid.saveState.api:GridOptions
24689 * @description Save the grouping row expanded states. If set to true and the
24690 * grouping feature is not enabled then does nothing.
24692 * This can be quite a bit of data, in many cases you wouldn't want to save this
24695 * <br/>Defaults to false
24697 gridOptions.saveGroupingExpandedStates = gridOptions.saveGroupingExpandedStates === true;
24700 * @name savePinning
24701 * @propertyOf ui.grid.saveState.api:GridOptions
24702 * @description Save pinning state for columns.
24704 * <br/>Defaults to true
24706 gridOptions.savePinning = gridOptions.savePinning !== false;
24709 * @name saveTreeView
24710 * @propertyOf ui.grid.saveState.api:GridOptions
24711 * @description Save the treeView configuration. If set to true and the
24712 * treeView feature is not enabled then does nothing.
24714 * <br/>Defaults to true
24716 gridOptions.saveTreeView = gridOptions.saveTreeView !== false;
24724 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24725 * @description Saves the current grid state into an object, and
24726 * passes that object back to the caller
24727 * @param {Grid} grid the grid whose state we'd like to save
24728 * @returns {object} the state ready to be saved
24730 save: function (grid) {
24731 var savedState = {};
24733 savedState.columns = service.saveColumns( grid );
24734 savedState.scrollFocus = service.saveScrollFocus( grid );
24735 savedState.selection = service.saveSelection( grid );
24736 savedState.grouping = service.saveGrouping( grid );
24737 savedState.treeView = service.saveTreeView( grid );
24738 savedState.pagination = service.savePagination( grid );
24747 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24748 * @description Applies the provided state to the grid
24750 * @param {Grid} grid the grid whose state we'd like to restore
24751 * @param {scope} $scope a scope that we can broadcast on
24752 * @param {object} state the state we'd like to restore
24754 restore: function( grid, $scope, state ){
24755 if ( state.columns ) {
24756 service.restoreColumns( grid, state.columns );
24759 if ( state.scrollFocus ){
24760 service.restoreScrollFocus( grid, $scope, state.scrollFocus );
24763 if ( state.selection ){
24764 service.restoreSelection( grid, state.selection );
24767 if ( state.grouping ){
24768 service.restoreGrouping( grid, state.grouping );
24771 if ( state.treeView ){
24772 service.restoreTreeView( grid, state.treeView );
24775 if ( state.pagination ){
24776 service.restorePagination( grid, state.pagination );
24785 * @name saveColumns
24786 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24787 * @description Saves the column setup, including sort, filters, ordering,
24788 * pinning and column widths.
24790 * Works through the current columns, storing them in order. Stores the
24791 * column name, then the visible flag, width, sort and filters for each column.
24793 * @param {Grid} grid the grid whose state we'd like to save
24794 * @returns {array} the columns state ready to be saved
24796 saveColumns: function( grid ) {
24798 grid.getOnlyDataColumns().forEach( function( column ) {
24799 var savedColumn = {};
24800 savedColumn.name = column.name;
24802 if ( grid.options.saveVisible ){
24803 savedColumn.visible = column.visible;
24806 if ( grid.options.saveWidths ){
24807 savedColumn.width = column.width;
24810 // these two must be copied, not just pointed too - otherwise our saved state is pointing to the same object as current state
24811 if ( grid.options.saveSort ){
24812 savedColumn.sort = angular.copy( column.sort );
24815 if ( grid.options.saveFilter ){
24816 savedColumn.filters = [];
24817 column.filters.forEach( function( filter ){
24818 var copiedFilter = {};
24819 angular.forEach( filter, function( value, key) {
24820 if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder'){
24821 copiedFilter[key] = value;
24824 savedColumn.filters.push(copiedFilter);
24828 if ( !!grid.api.pinning && grid.options.savePinning ){
24829 savedColumn.pinned = column.renderContainer ? column.renderContainer : '';
24832 columns.push( savedColumn );
24841 * @name saveScrollFocus
24842 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24843 * @description Saves the currently scroll or focus.
24845 * If cellNav isn't present then does nothing - we can't return
24846 * to the scroll position without cellNav anyway.
24848 * If the cellNav module is present, and saveFocus is true, then
24849 * it saves the currently focused cell. If rowIdentity is present
24850 * then saves using rowIdentity, otherwise saves visibleRowNum.
24852 * If the cellNav module is not present, and saveScroll is true, then
24853 * it approximates the current scroll row and column, and saves that.
24855 * @param {Grid} grid the grid whose state we'd like to save
24856 * @returns {object} the selection state ready to be saved
24858 saveScrollFocus: function( grid ){
24859 if ( !grid.api.cellNav ){
24863 var scrollFocus = {};
24864 if ( grid.options.saveFocus ){
24865 scrollFocus.focus = true;
24866 var rowCol = grid.api.cellNav.getFocusedCell();
24867 if ( rowCol !== null ) {
24868 if ( rowCol.col !== null ){
24869 scrollFocus.colName = rowCol.col.colDef.name;
24871 if ( rowCol.row !== null ){
24872 scrollFocus.rowVal = service.getRowVal( grid, rowCol.row );
24877 if ( grid.options.saveScroll || grid.options.saveFocus && !scrollFocus.colName && !scrollFocus.rowVal ) {
24878 scrollFocus.focus = false;
24879 if ( grid.renderContainers.body.prevRowScrollIndex ){
24880 scrollFocus.rowVal = service.getRowVal( grid, grid.renderContainers.body.visibleRowCache[ grid.renderContainers.body.prevRowScrollIndex ]);
24883 if ( grid.renderContainers.body.prevColScrollIndex ){
24884 scrollFocus.colName = grid.renderContainers.body.visibleColumnCache[ grid.renderContainers.body.prevColScrollIndex ].name;
24888 return scrollFocus;
24894 * @name saveSelection
24895 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24896 * @description Saves the currently selected rows, if the selection feature is enabled
24897 * @param {Grid} grid the grid whose state we'd like to save
24898 * @returns {array} the selection state ready to be saved
24900 saveSelection: function( grid ){
24901 if ( !grid.api.selection || !grid.options.saveSelection ){
24905 var selection = grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
24906 return service.getRowVal( grid, gridRow );
24915 * @name saveGrouping
24916 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24917 * @description Saves the grouping state, if the grouping feature is enabled
24918 * @param {Grid} grid the grid whose state we'd like to save
24919 * @returns {object} the grouping state ready to be saved
24921 saveGrouping: function( grid ){
24922 if ( !grid.api.grouping || !grid.options.saveGrouping ){
24926 return grid.api.grouping.getGrouping( grid.options.saveGroupingExpandedStates );
24932 * @name savePagination
24933 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24934 * @description Saves the pagination state, if the pagination feature is enabled
24935 * @param {Grid} grid the grid whose state we'd like to save
24936 * @returns {object} the pagination state ready to be saved
24938 savePagination: function( grid ) {
24939 if ( !grid.api.pagination || !grid.options.paginationPageSize ){
24944 paginationCurrentPage: grid.options.paginationCurrentPage,
24945 paginationPageSize: grid.options.paginationPageSize
24952 * @name saveTreeView
24953 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24954 * @description Saves the tree view state, if the tree feature is enabled
24955 * @param {Grid} grid the grid whose state we'd like to save
24956 * @returns {object} the tree view state ready to be saved
24958 saveTreeView: function( grid ){
24959 if ( !grid.api.treeView || !grid.options.saveTreeView ){
24963 return grid.api.treeView.getTreeView();
24970 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24971 * @description Helper function that gets either the rowNum or
24972 * the saveRowIdentity, given a gridRow
24973 * @param {Grid} grid the grid the row is in
24974 * @param {GridRow} gridRow the row we want the rowNum for
24975 * @returns {object} an object containing { identity: true/false, row: rowNumber/rowIdentity }
24978 getRowVal: function( grid, gridRow ){
24984 if ( grid.options.saveRowIdentity ){
24985 rowVal.identity = true;
24986 rowVal.row = grid.options.saveRowIdentity( gridRow.entity );
24988 rowVal.identity = false;
24989 rowVal.row = grid.renderContainers.body.visibleRowCache.indexOf( gridRow );
24997 * @name restoreColumns
24998 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
24999 * @description Restores the columns, including order, visible, width,
25000 * pinning, sort and filters.
25002 * @param {Grid} grid the grid whose state we'd like to restore
25003 * @param {object} columnsState the list of columns we had before, with their state
25005 restoreColumns: function( grid, columnsState ){
25006 var isSortChanged = false;
25008 columnsState.forEach( function( columnState, index ) {
25009 var currentCol = grid.getColumn( columnState.name );
25011 if ( currentCol && !grid.isRowHeaderColumn(currentCol) ){
25012 if ( grid.options.saveVisible &&
25013 ( currentCol.visible !== columnState.visible ||
25014 currentCol.colDef.visible !== columnState.visible ) ){
25015 currentCol.visible = columnState.visible;
25016 currentCol.colDef.visible = columnState.visible;
25017 grid.api.core.raise.columnVisibilityChanged(currentCol);
25020 if ( grid.options.saveWidths && currentCol.width !== columnState.width){
25021 currentCol.width = columnState.width;
25022 currentCol.hasCustomWidth = true;
25025 if ( grid.options.saveSort &&
25026 !angular.equals(currentCol.sort, columnState.sort) &&
25027 !( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ){
25028 currentCol.sort = angular.copy( columnState.sort );
25029 isSortChanged = true;
25032 if ( grid.options.saveFilter &&
25033 !angular.equals(currentCol.filters, columnState.filters ) ){
25034 columnState.filters.forEach( function( filter, index ){
25035 angular.extend( currentCol.filters[index], filter );
25036 if ( typeof(filter.term) === 'undefined' || filter.term === null ){
25037 delete currentCol.filters[index].term;
25040 grid.api.core.raise.filterChanged();
25043 if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ){
25044 grid.api.pinning.pinColumn(currentCol, columnState.pinned);
25047 var currentIndex = grid.getOnlyDataColumns().indexOf( currentCol );
25048 if (currentIndex !== -1) {
25049 if (grid.options.saveOrder && currentIndex !== index) {
25050 var column = grid.columns.splice(currentIndex + grid.rowHeaderColumns.length, 1)[0];
25051 grid.columns.splice(index + grid.rowHeaderColumns.length, 0, column);
25057 if ( isSortChanged ) {
25058 grid.api.core.raise.sortChanged( grid, grid.getColumnSorting() );
25065 * @name restoreScrollFocus
25066 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
25067 * @description Scrolls to the position that was saved. If focus is true, then
25068 * sets focus to the specified row/col. If focus is false, then scrolls to the
25069 * specified row/col.
25071 * @param {Grid} grid the grid whose state we'd like to restore
25072 * @param {scope} $scope a scope that we can broadcast on
25073 * @param {object} scrollFocusState the scroll/focus state ready to be restored
25075 restoreScrollFocus: function( grid, $scope, scrollFocusState ){
25076 if ( !grid.api.cellNav ){
25081 if ( scrollFocusState.colName ){
25082 var colDefs = grid.options.columnDefs.filter( function( colDef ) { return colDef.name === scrollFocusState.colName; });
25083 if ( colDefs.length > 0 ){
25084 colDef = colDefs[0];
25088 if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ){
25089 if ( scrollFocusState.rowVal.identity ){
25090 row = service.findRowByIdentity( grid, scrollFocusState.rowVal );
25092 row = grid.renderContainers.body.visibleRowCache[ scrollFocusState.rowVal.row ];
25096 var entity = row && row.entity ? row.entity : null ;
25098 if ( colDef || entity ) {
25099 if (scrollFocusState.focus ){
25100 grid.api.cellNav.scrollToFocus( entity, colDef );
25102 grid.scrollTo( entity, colDef );
25110 * @name restoreSelection
25111 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
25112 * @description Selects the rows that are provided in the selection
25113 * state. If you are using `saveRowIdentity` and more than one row matches the identity
25114 * function then only the first is selected.
25115 * @param {Grid} grid the grid whose state we'd like to restore
25116 * @param {object} selectionState the selection state ready to be restored
25118 restoreSelection: function( grid, selectionState ){
25119 if ( !grid.api.selection ){
25123 grid.api.selection.clearSelectedRows();
25125 selectionState.forEach( function( rowVal ) {
25126 if ( rowVal.identity ){
25127 var foundRow = service.findRowByIdentity( grid, rowVal );
25130 grid.api.selection.selectRow( foundRow.entity );
25134 grid.api.selection.selectRowByVisibleIndex( rowVal.row );
25142 * @name restoreGrouping
25143 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
25144 * @description Restores the grouping configuration, if the grouping feature
25146 * @param {Grid} grid the grid whose state we'd like to restore
25147 * @param {object} groupingState the grouping state ready to be restored
25149 restoreGrouping: function( grid, groupingState ){
25150 if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ){
25154 grid.api.grouping.setGrouping( groupingState );
25159 * @name restoreTreeView
25160 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
25161 * @description Restores the tree view configuration, if the tree view feature
25163 * @param {Grid} grid the grid whose state we'd like to restore
25164 * @param {object} treeViewState the tree view state ready to be restored
25166 restoreTreeView: function( grid, treeViewState ){
25167 if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ){
25171 grid.api.treeView.setTreeView( treeViewState );
25176 * @name restorePagination
25177 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
25178 * @description Restores the pagination information, if pagination is enabled.
25179 * @param {Grid} grid the grid whose state we'd like to restore
25180 * @param {object} pagination the pagination object to be restored
25181 * @param {number} pagination.paginationCurrentPage the page number to restore
25182 * @param {number} pagination.paginationPageSize the number of items displayed per page
25184 restorePagination: function( grid, pagination ){
25185 if ( !grid.api.pagination || !grid.options.paginationPageSize ){
25189 grid.options.paginationCurrentPage = pagination.paginationCurrentPage;
25190 grid.options.paginationPageSize = pagination.paginationPageSize;
25195 * @name findRowByIdentity
25196 * @methodOf ui.grid.saveState.service:uiGridSaveStateService
25197 * @description Finds a row given it's identity value, returns the first found row
25198 * if any are found, otherwise returns null if no rows are found.
25199 * @param {Grid} grid the grid whose state we'd like to restore
25200 * @param {object} rowVal the row we'd like to find
25201 * @returns {gridRow} the found row, or null if none found
25203 findRowByIdentity: function( grid, rowVal ){
25204 if ( !grid.options.saveRowIdentity ){
25208 var filteredRows = grid.rows.filter( function( gridRow ) {
25209 if ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row ){
25216 if ( filteredRows.length > 0 ){
25217 return filteredRows[0];
25231 * @name ui.grid.saveState.directive:uiGridSaveState
25235 * @description Adds saveState features to grid
25238 <example module="app">
25239 <file name="app.js">
25240 var app = angular.module('app', ['ui.grid', 'ui.grid.saveState']);
25242 app.controller('MainCtrl', ['$scope', function ($scope) {
25244 { name: 'Bob', title: 'CEO' },
25245 { name: 'Frank', title: 'Lowly Developer' }
25248 $scope.gridOptions = {
25251 {name: 'title', enableCellEdit: true}
25257 <file name="index.html">
25258 <div ng-controller="MainCtrl">
25259 <div ui-grid="gridOptions" ui-grid-save-state></div>
25264 module.directive('uiGridSaveState', ['uiGridSaveStateConstants', 'uiGridSaveStateService', 'gridUtil', '$compile',
25265 function (uiGridSaveStateConstants, uiGridSaveStateService, gridUtil, $compile) {
25269 require: '^uiGrid',
25271 link: function ($scope, $elm, $attrs, uiGridCtrl) {
25272 uiGridSaveStateService.initializeGrid(uiGridCtrl.grid);
25284 * @name ui.grid.selection
25287 * # ui.grid.selection
25288 * This module provides row selection
25290 * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
25292 * <div doc-module-components="ui.grid.selection"></div>
25295 var module = angular.module('ui.grid.selection', ['ui.grid']);
25299 * @name ui.grid.selection.constant:uiGridSelectionConstants
25301 * @description constants available in selection module
25303 module.constant('uiGridSelectionConstants', {
25304 featureName: "selection",
25305 selectionRowHeaderColName: 'selectionRowHeaderCol'
25308 //add methods to GridRow
25309 angular.module('ui.grid').config(['$provide', function($provide) {
25310 $provide.decorator('GridRow', ['$delegate', function($delegate) {
25314 * @name ui.grid.selection.api:GridRow
25316 * @description GridRow prototype functions added for selection
25321 * @name enableSelection
25322 * @propertyOf ui.grid.selection.api:GridRow
25323 * @description Enable row selection for this row, only settable by internal code.
25325 * The grouping feature, for example, might set group header rows to not be selectable.
25326 * <br/>Defaults to true
25332 * @propertyOf ui.grid.selection.api:GridRow
25333 * @description Selected state of row. Should be readonly. Make any changes to selected state using setSelected().
25334 * <br/>Defaults to false
25340 * @name setSelected
25341 * @methodOf ui.grid.selection.api:GridRow
25342 * @description Sets the isSelected property and updates the selectedCount
25343 * Changes to isSelected state should only be made via this function
25344 * @param {bool} selected value to set
25346 $delegate.prototype.setSelected = function(selected) {
25347 if (selected !== this.isSelected) {
25348 this.isSelected = selected;
25349 this.grid.selection.selectedCount += selected ? 1 : -1;
25359 * @name ui.grid.selection.service:uiGridSelectionService
25361 * @description Services for selection features
25363 module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
25364 function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
25368 initializeGrid: function (grid) {
25370 //add feature namespace and any properties to grid for needed
25373 * @name ui.grid.selection.grid:selection
25375 * @description Grid properties and functions added for selection
25377 grid.selection = {};
25378 grid.selection.lastSelectedRow = null;
25379 grid.selection.selectAll = false;
25384 * @name selectedCount
25385 * @propertyOf ui.grid.selection.grid:selection
25386 * @description Current count of selected rows
25388 * var count = grid.selection.selectedCount
25390 grid.selection.selectedCount = 0;
25392 service.defaultGridOptions(grid.options);
25396 * @name ui.grid.selection.api:PublicApi
25398 * @description Public Api for selection feature
25405 * @name rowSelectionChanged
25406 * @eventOf ui.grid.selection.api:PublicApi
25407 * @description is raised after the row.isSelected state is changed
25408 * @param {GridRow} row the row that was selected/deselected
25409 * @param {Event} event object if raised from an event
25411 rowSelectionChanged: function (scope, row, evt) {
25415 * @name rowSelectionChangedBatch
25416 * @eventOf ui.grid.selection.api:PublicApi
25417 * @description is raised after the row.isSelected state is changed
25418 * in bulk, if the `enableSelectionBatchEvent` option is set to true
25419 * (which it is by default). This allows more efficient processing
25421 * @param {array} rows the rows that were selected/deselected
25422 * @param {Event} event object if raised from an event
25424 rowSelectionChangedBatch: function (scope, rows, evt) {
25432 * @name toggleRowSelection
25433 * @methodOf ui.grid.selection.api:PublicApi
25434 * @description Toggles data row as selected or unselected
25435 * @param {object} rowEntity gridOptions.data[] array instance
25436 * @param {Event} event object if raised from an event
25438 toggleRowSelection: function (rowEntity, evt) {
25439 var row = grid.getRow(rowEntity);
25440 if (row !== null) {
25441 service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25447 * @methodOf ui.grid.selection.api:PublicApi
25448 * @description Select the data row
25449 * @param {object} rowEntity gridOptions.data[] array instance
25450 * @param {Event} event object if raised from an event
25452 selectRow: function (rowEntity, evt) {
25453 var row = grid.getRow(rowEntity);
25454 if (row !== null && !row.isSelected) {
25455 service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25460 * @name selectRowByVisibleIndex
25461 * @methodOf ui.grid.selection.api:PublicApi
25462 * @description Select the specified row by visible index (i.e. if you
25463 * specify row 0 you'll get the first visible row selected). In this context
25464 * visible means of those rows that are theoretically visible (i.e. not filtered),
25465 * rather than rows currently rendered on the screen.
25466 * @param {number} index index within the rowsVisible array
25467 * @param {Event} event object if raised from an event
25469 selectRowByVisibleIndex: function ( rowNum, evt ) {
25470 var row = grid.renderContainers.body.visibleRowCache[rowNum];
25471 if (row !== null && typeof(row) !== 'undefined' && !row.isSelected) {
25472 service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25477 * @name unSelectRow
25478 * @methodOf ui.grid.selection.api:PublicApi
25479 * @description UnSelect the data row
25480 * @param {object} rowEntity gridOptions.data[] array instance
25481 * @param {Event} event object if raised from an event
25483 unSelectRow: function (rowEntity, evt) {
25484 var row = grid.getRow(rowEntity);
25485 if (row !== null && row.isSelected) {
25486 service.toggleRowSelection(grid, row, evt, grid.options.multiSelect, grid.options.noUnselect);
25491 * @name selectAllRows
25492 * @methodOf ui.grid.selection.api:PublicApi
25493 * @description Selects all rows. Does nothing if multiSelect = false
25494 * @param {Event} event object if raised from an event
25496 selectAllRows: function (evt) {
25497 if (grid.options.multiSelect === false) {
25501 var changedRows = [];
25502 grid.rows.forEach(function (row) {
25503 if ( !row.isSelected && row.enableSelection !== false ){
25504 row.setSelected(true);
25505 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25508 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25509 grid.selection.selectAll = true;
25513 * @name selectAllVisibleRows
25514 * @methodOf ui.grid.selection.api:PublicApi
25515 * @description Selects all visible rows. Does nothing if multiSelect = false
25516 * @param {Event} event object if raised from an event
25518 selectAllVisibleRows: function (evt) {
25519 if (grid.options.multiSelect === false) {
25523 var changedRows = [];
25524 grid.rows.forEach(function (row) {
25526 if (!row.isSelected && row.enableSelection !== false){
25527 row.setSelected(true);
25528 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25531 if (row.isSelected){
25532 row.setSelected(false);
25533 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25537 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25538 grid.selection.selectAll = true;
25542 * @name clearSelectedRows
25543 * @methodOf ui.grid.selection.api:PublicApi
25544 * @description Unselects all rows
25545 * @param {Event} event object if raised from an event
25547 clearSelectedRows: function (evt) {
25548 service.clearSelectedRows(grid, evt);
25552 * @name getSelectedRows
25553 * @methodOf ui.grid.selection.api:PublicApi
25554 * @description returns all selectedRow's entity references
25556 getSelectedRows: function () {
25557 return service.getSelectedRows(grid).map(function (gridRow) {
25558 return gridRow.entity;
25563 * @name getSelectedGridRows
25564 * @methodOf ui.grid.selection.api:PublicApi
25565 * @description returns all selectedRow's as gridRows
25567 getSelectedGridRows: function () {
25568 return service.getSelectedRows(grid);
25572 * @name getSelectedCount
25573 * @methodOf ui.grid.selection.api:PublicApi
25574 * @description returns the number of rows selected
25576 getSelectedCount: function () {
25577 return grid.selection.selectedCount;
25581 * @name setMultiSelect
25582 * @methodOf ui.grid.selection.api:PublicApi
25583 * @description Sets the current gridOption.multiSelect to true or false
25584 * @param {bool} multiSelect true to allow multiple rows
25586 setMultiSelect: function (multiSelect) {
25587 grid.options.multiSelect = multiSelect;
25591 * @name setModifierKeysToMultiSelect
25592 * @methodOf ui.grid.selection.api:PublicApi
25593 * @description Sets the current gridOption.modifierKeysToMultiSelect to true or false
25594 * @param {bool} modifierKeysToMultiSelect true to only allow multiple rows when using ctrlKey or shiftKey is used
25596 setModifierKeysToMultiSelect: function (modifierKeysToMultiSelect) {
25597 grid.options.modifierKeysToMultiSelect = modifierKeysToMultiSelect;
25601 * @name getSelectAllState
25602 * @methodOf ui.grid.selection.api:PublicApi
25603 * @description Returns whether or not the selectAll checkbox is currently ticked. The
25604 * grid doesn't automatically select rows when you add extra data - so when you add data
25605 * you need to explicitly check whether the selectAll is set, and then call setVisible rows
25608 getSelectAllState: function () {
25609 return grid.selection.selectAll;
25616 grid.api.registerEventsFromObject(publicApi.events);
25618 grid.api.registerMethodsFromObject(publicApi.methods);
25622 defaultGridOptions: function (gridOptions) {
25623 //default option to true unless it was explicitly set to false
25626 * @name ui.grid.selection.api:GridOptions
25628 * @description GridOptions for selection feature, these are available to be
25629 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
25634 * @name enableRowSelection
25635 * @propertyOf ui.grid.selection.api:GridOptions
25636 * @description Enable row selection for entire grid.
25637 * <br/>Defaults to true
25639 gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
25642 * @name multiSelect
25643 * @propertyOf ui.grid.selection.api:GridOptions
25644 * @description Enable multiple row selection for entire grid
25645 * <br/>Defaults to true
25647 gridOptions.multiSelect = gridOptions.multiSelect !== false;
25651 * @propertyOf ui.grid.selection.api:GridOptions
25652 * @description Prevent a row from being unselected. Works in conjunction
25653 * with `multiselect = false` and `gridApi.selection.selectRow()` to allow
25654 * you to create a single selection only grid - a row is always selected, you
25655 * can only select different rows, you can't unselect the row.
25656 * <br/>Defaults to false
25658 gridOptions.noUnselect = gridOptions.noUnselect === true;
25661 * @name modifierKeysToMultiSelect
25662 * @propertyOf ui.grid.selection.api:GridOptions
25663 * @description Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
25664 * <br/>Defaults to false
25666 gridOptions.modifierKeysToMultiSelect = gridOptions.modifierKeysToMultiSelect === true;
25669 * @name enableRowHeaderSelection
25670 * @propertyOf ui.grid.selection.api:GridOptions
25671 * @description Enable a row header to be used for selection
25672 * <br/>Defaults to true
25674 gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
25677 * @name enableFullRowSelection
25678 * @propertyOf ui.grid.selection.api:GridOptions
25679 * @description Enable selection by clicking anywhere on the row. Defaults to
25680 * false if `enableRowHeaderSelection` is true, otherwise defaults to false.
25682 if ( typeof(gridOptions.enableFullRowSelection) === 'undefined' ){
25683 gridOptions.enableFullRowSelection = !gridOptions.enableRowHeaderSelection;
25687 * @name enableSelectAll
25688 * @propertyOf ui.grid.selection.api:GridOptions
25689 * @description Enable the select all checkbox at the top of the selectionRowHeader
25690 * <br/>Defaults to true
25692 gridOptions.enableSelectAll = gridOptions.enableSelectAll !== false;
25695 * @name enableSelectionBatchEvent
25696 * @propertyOf ui.grid.selection.api:GridOptions
25697 * @description If selected rows are changed in bulk, either via the API or
25698 * via the selectAll checkbox, then a separate event is fired. Setting this
25699 * option to false will cause the rowSelectionChanged event to be called multiple times
25701 * <br/>Defaults to true
25703 gridOptions.enableSelectionBatchEvent = gridOptions.enableSelectionBatchEvent !== false;
25706 * @name selectionRowHeaderWidth
25707 * @propertyOf ui.grid.selection.api:GridOptions
25708 * @description can be used to set a custom width for the row header selection column
25709 * <br/>Defaults to 30px
25711 gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
25715 * @name enableFooterTotalSelected
25716 * @propertyOf ui.grid.selection.api:GridOptions
25717 * @description Shows the total number of selected items in footer if true.
25718 * <br/>Defaults to true.
25719 * <br/>GridOptions.showGridFooter must also be set to true.
25721 gridOptions.enableFooterTotalSelected = gridOptions.enableFooterTotalSelected !== false;
25725 * @name isRowSelectable
25726 * @propertyOf ui.grid.selection.api:GridOptions
25727 * @description Makes it possible to specify a method that evaluates for each row and sets its "enableSelection" property.
25730 gridOptions.isRowSelectable = angular.isDefined(gridOptions.isRowSelectable) ? gridOptions.isRowSelectable : angular.noop;
25735 * @name toggleRowSelection
25736 * @methodOf ui.grid.selection.service:uiGridSelectionService
25737 * @description Toggles row as selected or unselected
25738 * @param {Grid} grid grid object
25739 * @param {GridRow} row row to select or deselect
25740 * @param {Event} event object if resulting from event
25741 * @param {bool} multiSelect if false, only one row at time can be selected
25742 * @param {bool} noUnselect if true then rows cannot be unselected
25744 toggleRowSelection: function (grid, row, evt, multiSelect, noUnselect) {
25745 var selected = row.isSelected;
25747 if ( row.enableSelection === false && !selected ){
25752 if (!multiSelect && !selected) {
25753 service.clearSelectedRows(grid, evt);
25754 } else if (!multiSelect && selected) {
25755 selectedRows = service.getSelectedRows(grid);
25756 if (selectedRows.length > 1) {
25757 selected = false; // Enable reselect of the row
25758 service.clearSelectedRows(grid, evt);
25762 if (selected && noUnselect){
25763 // don't deselect the row
25765 row.setSelected(!selected);
25766 if (row.isSelected === true) {
25767 grid.selection.lastSelectedRow = row;
25770 selectedRows = service.getSelectedRows(grid);
25771 grid.selection.selectAll = grid.rows.length === selectedRows.length;
25773 grid.api.selection.raise.rowSelectionChanged(row, evt);
25778 * @name shiftSelect
25779 * @methodOf ui.grid.selection.service:uiGridSelectionService
25780 * @description selects a group of rows from the last selected row using the shift key
25781 * @param {Grid} grid grid object
25782 * @param {GridRow} clicked row
25783 * @param {Event} event object if raised from an event
25784 * @param {bool} multiSelect if false, does nothing this is for multiSelect only
25786 shiftSelect: function (grid, row, evt, multiSelect) {
25787 if (!multiSelect) {
25790 var selectedRows = service.getSelectedRows(grid);
25791 var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
25792 var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
25793 //reverse select direction
25794 if (fromRow > toRow) {
25800 var changedRows = [];
25801 for (var i = fromRow; i <= toRow; i++) {
25802 var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
25804 if ( !rowToSelect.isSelected && rowToSelect.enableSelection !== false ){
25805 rowToSelect.setSelected(true);
25806 grid.selection.lastSelectedRow = rowToSelect;
25807 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows, evt );
25811 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25815 * @name getSelectedRows
25816 * @methodOf ui.grid.selection.service:uiGridSelectionService
25817 * @description Returns all the selected rows
25818 * @param {Grid} grid grid object
25820 getSelectedRows: function (grid) {
25821 return grid.rows.filter(function (row) {
25822 return row.isSelected;
25828 * @name clearSelectedRows
25829 * @methodOf ui.grid.selection.service:uiGridSelectionService
25830 * @description Clears all selected rows
25831 * @param {Grid} grid grid object
25832 * @param {Event} event object if raised from an event
25834 clearSelectedRows: function (grid, evt) {
25835 var changedRows = [];
25836 service.getSelectedRows(grid).forEach(function (row) {
25837 if ( row.isSelected ){
25838 row.setSelected(false);
25839 service.decideRaiseSelectionEvent( grid, row, changedRows, evt );
25842 service.decideRaiseSelectionBatchEvent( grid, changedRows, evt );
25843 grid.selection.selectAll = false;
25844 grid.selection.selectedCount = 0;
25849 * @name decideRaiseSelectionEvent
25850 * @methodOf ui.grid.selection.service:uiGridSelectionService
25851 * @description Decides whether to raise a single event or a batch event
25852 * @param {Grid} grid grid object
25853 * @param {GridRow} row row that has changed
25854 * @param {array} changedRows an array to which we can append the changed
25855 * @param {Event} event object if raised from an event
25856 * row if we're doing batch events
25858 decideRaiseSelectionEvent: function( grid, row, changedRows, evt ){
25859 if ( !grid.options.enableSelectionBatchEvent ){
25860 grid.api.selection.raise.rowSelectionChanged(row, evt);
25862 changedRows.push(row);
25868 * @name raiseSelectionEvent
25869 * @methodOf ui.grid.selection.service:uiGridSelectionService
25870 * @description Decides whether we need to raise a batch event, and
25871 * raises it if we do.
25872 * @param {Grid} grid grid object
25873 * @param {array} changedRows an array of changed rows, only populated
25874 * @param {Event} event object if raised from an event
25875 * if we're doing batch events
25877 decideRaiseSelectionBatchEvent: function( grid, changedRows, evt ){
25878 if ( changedRows.length > 0 ){
25879 grid.api.selection.raise.rowSelectionChangedBatch(changedRows, evt);
25890 * @name ui.grid.selection.directive:uiGridSelection
25894 * @description Adds selection features to grid
25897 <example module="app">
25898 <file name="app.js">
25899 var app = angular.module('app', ['ui.grid', 'ui.grid.selection']);
25901 app.controller('MainCtrl', ['$scope', function ($scope) {
25903 { name: 'Bob', title: 'CEO' },
25904 { name: 'Frank', title: 'Lowly Developer' }
25907 $scope.columnDefs = [
25908 {name: 'name', enableCellEdit: true},
25909 {name: 'title', enableCellEdit: true}
25913 <file name="index.html">
25914 <div ng-controller="MainCtrl">
25915 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
25920 module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache', 'uiGridConstants',
25921 function (uiGridSelectionConstants, uiGridSelectionService, $templateCache, uiGridConstants) {
25925 require: '^uiGrid',
25927 compile: function () {
25929 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
25930 uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
25931 if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
25932 var selectionRowHeaderDef = {
25933 name: uiGridSelectionConstants.selectionRowHeaderColName,
25935 width: uiGridCtrl.grid.options.selectionRowHeaderWidth,
25937 cellTemplate: 'ui-grid/selectionRowHeader',
25938 headerCellTemplate: 'ui-grid/selectionHeaderCell',
25939 enableColumnResizing: false,
25940 enableColumnMenu: false,
25941 exporterSuppressExport: true,
25942 allowCellFocus: true
25945 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef, 0);
25948 var processorSet = false;
25950 var processSelectableRows = function( rows ){
25951 rows.forEach(function(row){
25952 row.enableSelection = uiGridCtrl.grid.options.isRowSelectable(row);
25957 var updateOptions = function(){
25958 if (uiGridCtrl.grid.options.isRowSelectable !== angular.noop && processorSet !== true) {
25959 uiGridCtrl.grid.registerRowsProcessor(processSelectableRows, 500);
25960 processorSet = true;
25966 var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback( updateOptions, [uiGridConstants.dataChange.OPTIONS] );
25968 $scope.$on( '$destroy', dataChangeDereg);
25970 post: function ($scope, $elm, $attrs, uiGridCtrl) {
25978 module.directive('uiGridSelectionRowHeaderButtons', ['$templateCache', 'uiGridSelectionService', 'gridUtil',
25979 function ($templateCache, uiGridSelectionService, gridUtil) {
25983 template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
25985 require: '^uiGrid',
25986 link: function($scope, $elm, $attrs, uiGridCtrl) {
25987 var self = uiGridCtrl.grid;
25988 $scope.selectButtonClick = selectButtonClick;
25990 // On IE, prevent mousedowns on the select button from starting a selection.
25991 // If this is not done and you shift+click on another row, the browser will select a big chunk of text
25992 if (gridUtil.detectBrowser() === 'ie') {
25993 $elm.on('mousedown', selectButtonMouseDown);
25997 function selectButtonClick(row, evt) {
25998 evt.stopPropagation();
26000 if (evt.shiftKey) {
26001 uiGridSelectionService.shiftSelect(self, row, evt, self.options.multiSelect);
26003 else if (evt.ctrlKey || evt.metaKey) {
26004 uiGridSelectionService.toggleRowSelection(self, row, evt, self.options.multiSelect, self.options.noUnselect);
26007 uiGridSelectionService.toggleRowSelection(self, row, evt, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
26011 function selectButtonMouseDown(evt) {
26012 if (evt.ctrlKey || evt.shiftKey) {
26013 evt.target.onselectstart = function () { return false; };
26014 window.setTimeout(function () { evt.target.onselectstart = null; }, 0);
26018 $scope.$on('$destroy', function unbindEvents() {
26025 module.directive('uiGridSelectionSelectAllButtons', ['$templateCache', 'uiGridSelectionService',
26026 function ($templateCache, uiGridSelectionService) {
26030 template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
26032 link: function($scope, $elm, $attrs, uiGridCtrl) {
26033 var self = $scope.col.grid;
26035 $scope.headerButtonClick = function(row, evt) {
26036 if ( self.selection.selectAll ){
26037 uiGridSelectionService.clearSelectedRows(self, evt);
26038 if ( self.options.noUnselect ){
26039 self.api.selection.selectRowByVisibleIndex(0, evt);
26041 self.selection.selectAll = false;
26043 if ( self.options.multiSelect ){
26044 self.api.selection.selectAllVisibleRows(evt);
26045 self.selection.selectAll = true;
26055 * @name ui.grid.selection.directive:uiGridViewport
26058 * @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
26061 module.directive('uiGridViewport',
26062 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
26063 function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
26065 priority: -200, // run after default directive
26067 compile: function ($elm, $attrs) {
26068 var rowRepeatDiv = angular.element($elm.children().children()[0]);
26070 var existingNgClass = rowRepeatDiv.attr("ng-class");
26071 var newNgClass = '';
26072 if ( existingNgClass ) {
26073 newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
26075 newNgClass = "{'ui-grid-row-selected': row.isSelected}";
26077 rowRepeatDiv.attr("ng-class", newNgClass);
26080 pre: function ($scope, $elm, $attrs, controllers) {
26083 post: function ($scope, $elm, $attrs, controllers) {
26092 * @name ui.grid.selection.directive:uiGridCell
26096 * @description Stacks on top of ui.grid.uiGridCell to provide selection feature
26098 module.directive('uiGridCell',
26099 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService', '$timeout',
26100 function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService, $timeout) {
26102 priority: -200, // run after default uiGridCell directive
26104 require: '?^uiGrid',
26106 link: function ($scope, $elm, $attrs, uiGridCtrl) {
26108 var touchStartTime = 0;
26109 var touchTimeout = 300;
26111 // Bind to keydown events in the render container
26112 if (uiGridCtrl.grid.api.cellNav) {
26114 uiGridCtrl.grid.api.cellNav.on.viewPortKeyDown($scope, function (evt, rowCol) {
26115 if (rowCol === null ||
26116 rowCol.row !== $scope.row ||
26117 rowCol.col !== $scope.col) {
26121 if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
26122 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
26126 // uiGridCellNavService.scrollToIfNecessary(uiGridCtrl.grid, rowCol.row, rowCol.col);
26130 //$elm.bind('keydown', function (evt) {
26131 // if (evt.keyCode === 32 && $scope.col.colDef.name === "selectionRowHeaderCol") {
26132 // uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
26133 // $scope.$apply();
26137 var selectCells = function(evt){
26138 // if you click on expandable icon doesn't trigger selection
26139 if (evt.target.className === "ui-grid-icon-minus-squared" || evt.target.className === "ui-grid-icon-plus-squared") {
26143 // if we get a click, then stop listening for touchend
26144 $elm.off('touchend', touchEnd);
26146 if (evt.shiftKey) {
26147 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect);
26149 else if (evt.ctrlKey || evt.metaKey) {
26150 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
26153 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, evt, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
26157 // don't re-enable the touchend handler for a little while - some devices generate both, and it will
26158 // take a little while to move your hand from the mouse to the screen if you have both modes of input
26159 $timeout(function() {
26160 $elm.on('touchend', touchEnd);
26164 var touchStart = function(evt){
26165 touchStartTime = (new Date()).getTime();
26167 // if we get a touch event, then stop listening for click
26168 $elm.off('click', selectCells);
26171 var touchEnd = function(evt) {
26172 var touchEndTime = (new Date()).getTime();
26173 var touchTime = touchEndTime - touchStartTime;
26175 if (touchTime < touchTimeout ) {
26180 // don't re-enable the click handler for a little while - some devices generate both, and it will
26181 // take a little while to move your hand from the screen to the mouse if you have both modes of input
26182 $timeout(function() {
26183 $elm.on('click', selectCells);
26187 function registerRowSelectionEvents() {
26188 if ($scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection) {
26189 $elm.addClass('ui-grid-disable-selection');
26190 $elm.on('touchstart', touchStart);
26191 $elm.on('touchend', touchEnd);
26192 $elm.on('click', selectCells);
26194 $scope.registered = true;
26198 function deregisterRowSelectionEvents() {
26199 if ($scope.registered){
26200 $elm.removeClass('ui-grid-disable-selection');
26202 $elm.off('touchstart', touchStart);
26203 $elm.off('touchend', touchEnd);
26204 $elm.off('click', selectCells);
26206 $scope.registered = false;
26210 registerRowSelectionEvents();
26211 // register a dataChange callback so that we can change the selection configuration dynamically
26212 // if the user changes the options
26213 var dataChangeDereg = $scope.grid.registerDataChangeCallback( function() {
26214 if ( $scope.grid.options.enableRowSelection && $scope.grid.options.enableFullRowSelection &&
26215 !$scope.registered ){
26216 registerRowSelectionEvents();
26217 } else if ( ( !$scope.grid.options.enableRowSelection || !$scope.grid.options.enableFullRowSelection ) &&
26218 $scope.registered ){
26219 deregisterRowSelectionEvents();
26221 }, [uiGridConstants.dataChange.OPTIONS] );
26223 $elm.on( '$destroy', dataChangeDereg);
26228 module.directive('uiGridGridFooter', ['$compile', 'uiGridConstants', 'gridUtil', function ($compile, uiGridConstants, gridUtil) {
26233 require: '^uiGrid',
26235 compile: function ($elm, $attrs) {
26237 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
26239 if (!uiGridCtrl.grid.options.showGridFooter) {
26244 gridUtil.getTemplate('ui-grid/gridFooterSelectedItems')
26245 .then(function (contents) {
26246 var template = angular.element(contents);
26248 var newElm = $compile(template)($scope);
26250 angular.element($elm[0].getElementsByClassName('ui-grid-grid-footer')[0]).append(newElm);
26254 post: function ($scope, $elm, $attrs, controllers) {
26269 * @name ui.grid.treeBase
26272 * # ui.grid.treeBase
26274 * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
26276 * This module provides base tree handling functions that are shared by other features, notably grouping
26277 * and treeView. It provides a tree view of the data, with nodes in that
26280 * Design information:
26281 * -------------------
26283 * The raw data that is provided must come with a $$treeLevel on any non-leaf node. Grouping will create
26284 * these on all the group header rows, treeView will expect these to be set in the raw data by the user.
26285 * TreeBase will run a rowsProcessor that:
26286 * - builds `treeBase.tree` out of the provided rows
26287 * - permits a recursive sort of the tree
26288 * - maintains the expand/collapse state of each node
26289 * - provides the expand/collapse all button and the expand/collapse buttons
26290 * - maintains the count of children for each node
26292 * Each row is updated with a link to the tree node that represents it. Refer {@link ui.grid.treeBase.grid:treeBase.tree tree documentation}
26295 * TreeBase adds information to the rows
26296 * - treeLevel: if present and > -1 tells us the level (level 0 is the top level)
26297 * - treeNode: pointer to the node in the grid.treeBase.tree that refers
26298 * to this row, allowing us to manipulate the state
26300 * Since the logic is baked into the rowsProcessors, it should get triggered whenever
26301 * row order or filtering or anything like that is changed. We recall the expanded state
26302 * across invocations of the rowsProcessors by the reference to the treeNode on the individual
26303 * rows. We rebuild the tree itself quite frequently, when we do this we use the saved treeNodes to
26304 * get the state, but we overwrite the other data in that treeNode.
26306 * By default rows are collapsed, which means all data rows have their visible property
26307 * set to false, and only level 0 group rows are set to visible.
26309 * We rely on the rowsProcessors to do the actual expanding and collapsing, so we set the flags we want into
26310 * grid.treeBase.tree, then call refresh. This is because we can't easily change the visible
26311 * row cache without calling the processors, and once we've built the logic into the rowProcessors we may as
26312 * well use it all the time.
26314 * Tree base provides sorting (on non-grouped columns).
26316 * Sorting works in two passes. The standard sorting is performed for any columns that are important to building
26317 * the tree (for example, any grouped columns). Then after the tree is built, a recursive tree sort is performed
26318 * for the remaining sort columns (including the original sort) - these columns are sorted within each tree level
26319 * (so all the level 1 nodes are sorted, then all the level 2 nodes within each level 1 node etc).
26321 * To achieve this we make use of the `ignoreSort` property on the sort configuration. The parent feature (treeView or grouping)
26322 * must provide a rowsProcessor that runs with very low priority (typically in the 60-65 range), and that sets
26323 * the `ignoreSort`on any sort that it wants to run on the tree. TreeBase will clear the ignoreSort on all sorts - so it
26324 * will turn on any sorts that haven't run. It will then call a recursive sort on the tree.
26326 * Tree base provides treeAggregation. It checks the treeAggregation configuration on each column, and aggregates based on
26327 * the logic provided as it builds the tree. Footer aggregation from the uiGrid core should not be used with treeBase aggregation,
26328 * since it operates on all visible rows, as opposed to to leaf nodes only. Setting `showColumnFooter: true` will show the
26329 * treeAggregations in the column footer. Aggregation information will be collected in the format:
26335 * label: 'count: ',
26336 * rendered: 'count: 4'
26340 * A callback is provided to format the value once it is finalised (aka a valueFilter).
26345 * <div doc-module-components="ui.grid.treeBase"></div>
26348 var module = angular.module('ui.grid.treeBase', ['ui.grid']);
26352 * @name ui.grid.treeBase.constant:uiGridTreeBaseConstants
26354 * @description constants available in treeBase module.
26356 * These constants are manually copied into grouping and treeView,
26357 * as I haven't found a way to simply include them, and it's not worth
26358 * investing time in for something that changes very infrequently.
26361 module.constant('uiGridTreeBaseConstants', {
26362 featureName: "treeBase",
26363 rowHeaderColName: 'treeBaseRowHeaderCol',
26364 EXPANDED: 'expanded',
26365 COLLAPSED: 'collapsed',
26377 * @name ui.grid.treeBase.service:uiGridTreeBaseService
26379 * @description Services for treeBase feature
26383 * @name ui.grid.treeBase.api:ColumnDef
26385 * @description ColumnDef for tree feature, these are available to be
26386 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
26389 module.service('uiGridTreeBaseService', ['$q', 'uiGridTreeBaseConstants', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'rowSorter',
26390 function ($q, uiGridTreeBaseConstants, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants, rowSorter) {
26394 initializeGrid: function (grid, $scope) {
26396 //add feature namespace and any properties to grid for needed
26399 * @name ui.grid.treeBase.grid:treeBase
26401 * @description Grid properties and functions added for treeBase
26403 grid.treeBase = {};
26407 * @propertyOf ui.grid.treeBase.grid:treeBase
26408 * @name numberLevels
26410 * @description Total number of tree levels currently used, calculated by the rowsProcessor by
26411 * retaining the highest tree level it sees
26413 grid.treeBase.numberLevels = 0;
26417 * @propertyOf ui.grid.treeBase.grid:treeBase
26420 * @description Whether or not the expandAll box is selected
26422 grid.treeBase.expandAll = false;
26426 * @propertyOf ui.grid.treeBase.grid:treeBase
26429 * @description Tree represented as a nested array that holds the state of each node, along with a
26430 * pointer to the row. The array order is material - we will display the children in the order
26431 * they are stored in the array
26433 * Each node stores:
26435 * - the state of this node
26436 * - an array of children of this node
26437 * - a pointer to the parent of this node (reverse pointer, allowing us to walk up the tree)
26438 * - the number of children of this node
26439 * - aggregation information calculated from the nodes
26443 * state: 'expanded',
26444 * row: <reference to row>,
26450 * label: 'count: ',
26451 * rendered: 'count: 2'
26455 * state: 'expanded',
26456 * row: <reference to row>,
26457 * parentRow: <reference to row>,
26462 * label: 'count: ',
26463 * rendered: 'count: 4'
26466 * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
26467 * { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
26468 * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
26469 * { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> }
26473 * state: 'collapsed',
26474 * row: <reference to row>,
26475 * parentRow: <reference to row>,
26480 * label: 'count: ',
26481 * rendered: 'count: 3'
26484 * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
26485 * { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
26486 * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> }
26490 * }, {<another level 0 node maybe>} ]
26492 * Missing state values are false - meaning they aren't expanded.
26494 * This is used because the rowProcessors run every time the grid is refreshed, so
26495 * we'd lose the expanded state every time the grid was refreshed. This instead gives
26496 * us a reliable lookup that persists across rowProcessors.
26498 * This tree is rebuilt every time we run the rowsProcessors. Since each row holds a pointer
26499 * to it's tree node we can persist expand/collapse state across calls to rowsProcessor, we discard
26500 * all transient information on the tree (children, childCount) and recalculate it
26503 grid.treeBase.tree = [];
26505 service.defaultGridOptions(grid.options);
26507 grid.registerRowsProcessor(service.treeRows, 410);
26509 grid.registerColumnBuilder( service.treeBaseColumnBuilder );
26511 service.createRowHeader( grid );
26515 * @name ui.grid.treeBase.api:PublicApi
26517 * @description Public Api for treeBase feature
26524 * @eventOf ui.grid.treeBase.api:PublicApi
26525 * @name rowExpanded
26526 * @description raised whenever a row is expanded. If you are dynamically
26527 * rendering your tree you can listen to this event, and then retrieve
26528 * the children of this row and load them into the grid data.
26530 * When the data is loaded the grid will automatically refresh to show these new rows
26533 * gridApi.treeBase.on.rowExpanded(scope,function(row){})
26535 * @param {gridRow} row the row that was expanded. You can also
26536 * retrieve the grid from this row with row.grid
26542 * @eventOf ui.grid.treeBase.api:PublicApi
26543 * @name rowCollapsed
26544 * @description raised whenever a row is collapsed. Doesn't really have
26545 * a purpose at the moment, included for symmetry
26548 * gridApi.treeBase.on.rowCollapsed(scope,function(row){})
26550 * @param {gridRow} row the row that was collapsed. You can also
26551 * retrieve the grid from this row with row.grid
26561 * @name expandAllRows
26562 * @methodOf ui.grid.treeBase.api:PublicApi
26563 * @description Expands all tree rows
26565 expandAllRows: function () {
26566 service.expandAllRows(grid);
26571 * @name collapseAllRows
26572 * @methodOf ui.grid.treeBase.api:PublicApi
26573 * @description collapse all tree rows
26575 collapseAllRows: function () {
26576 service.collapseAllRows(grid);
26581 * @name toggleRowTreeState
26582 * @methodOf ui.grid.treeBase.api:PublicApi
26583 * @description call expand if the row is collapsed, collapse if it is expanded
26584 * @param {gridRow} row the row you wish to toggle
26586 toggleRowTreeState: function (row) {
26587 service.toggleRowTreeState(grid, row);
26593 * @methodOf ui.grid.treeBase.api:PublicApi
26594 * @description expand the immediate children of the specified row
26595 * @param {gridRow} row the row you wish to expand
26597 expandRow: function (row) {
26598 service.expandRow(grid, row);
26603 * @name expandRowChildren
26604 * @methodOf ui.grid.treeBase.api:PublicApi
26605 * @description expand all children of the specified row
26606 * @param {gridRow} row the row you wish to expand
26608 expandRowChildren: function (row) {
26609 service.expandRowChildren(grid, row);
26614 * @name collapseRow
26615 * @methodOf ui.grid.treeBase.api:PublicApi
26616 * @description collapse the specified row. When
26617 * you expand the row again, all grandchildren will retain their state
26618 * @param {gridRow} row the row you wish to collapse
26620 collapseRow: function ( row ) {
26621 service.collapseRow(grid, row);
26626 * @name collapseRowChildren
26627 * @methodOf ui.grid.treeBase.api:PublicApi
26628 * @description collapse all children of the specified row. When
26629 * you expand the row again, all grandchildren will be collapsed
26630 * @param {gridRow} row the row you wish to collapse children for
26632 collapseRowChildren: function ( row ) {
26633 service.collapseRowChildren(grid, row);
26638 * @name getTreeState
26639 * @methodOf ui.grid.treeBase.api:PublicApi
26640 * @description Get the tree state for this grid,
26641 * used by the saveState feature
26642 * Returned treeState as an object
26643 * `{ expandedState: { uid: 'expanded', uid: 'collapsed' } }`
26644 * where expandedState is a hash of row uid and the current expanded state
26646 * @returns {object} tree state
26648 * TODO - this needs work - we need an identifier that persists across instantiations,
26649 * not uid. This really means we need a row identity defined, but that won't work for
26650 * grouping. Perhaps this needs to be moved up to treeView and grouping, rather than
26653 getTreeExpandedState: function () {
26654 return { expandedState: service.getTreeState(grid) };
26659 * @name setTreeState
26660 * @methodOf ui.grid.treeBase.api:PublicApi
26661 * @description Set the expanded states of the tree
26662 * @param {object} config the config you want to apply, in the format
26663 * provided by getTreeState
26665 setTreeState: function ( config ) {
26666 service.setTreeState( grid, config );
26671 * @name getRowChildren
26672 * @methodOf ui.grid.treeBase.api:PublicApi
26673 * @description Get the children of the specified row
26674 * @param {GridRow} row the row you want the children of
26675 * @returns {Array} array of children of this row, the children
26678 getRowChildren: function ( row ){
26679 return row.treeNode.children.map( function( childNode ){
26680 return childNode.row;
26687 grid.api.registerEventsFromObject(publicApi.events);
26689 grid.api.registerMethodsFromObject(publicApi.methods);
26693 defaultGridOptions: function (gridOptions) {
26694 //default option to true unless it was explicitly set to false
26697 * @name ui.grid.treeBase.api:GridOptions
26699 * @description GridOptions for treeBase feature, these are available to be
26700 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
26705 * @name treeRowHeaderBaseWidth
26706 * @propertyOf ui.grid.treeBase.api:GridOptions
26707 * @description Base width of the tree header, provides for a single level of tree. This
26708 * is incremented by `treeIndent` for each extra level
26709 * <br/>Defaults to 30
26711 gridOptions.treeRowHeaderBaseWidth = gridOptions.treeRowHeaderBaseWidth || 30;
26716 * @propertyOf ui.grid.treeBase.api:GridOptions
26717 * @description Number of pixels of indent for the icon at each tree level, wider indents are visually more pleasing,
26718 * but will make the tree row header wider
26719 * <br/>Defaults to 10
26721 gridOptions.treeIndent = gridOptions.treeIndent || 10;
26725 * @name showTreeRowHeader
26726 * @propertyOf ui.grid.treeBase.api:GridOptions
26727 * @description If set to false, don't create the row header. You'll need to programmatically control the expand
26729 * <br/>Defaults to true
26731 gridOptions.showTreeRowHeader = gridOptions.showTreeRowHeader !== false;
26735 * @name showTreeExpandNoChildren
26736 * @propertyOf ui.grid.treeBase.api:GridOptions
26737 * @description If set to true, show the expand/collapse button even if there are no
26738 * children of a node. You'd use this if you're planning to dynamically load the children
26740 * <br/>Defaults to true, grouping overrides to false
26742 gridOptions.showTreeExpandNoChildren = gridOptions.showTreeExpandNoChildren !== false;
26746 * @name treeRowHeaderAlwaysVisible
26747 * @propertyOf ui.grid.treeBase.api:GridOptions
26748 * @description If set to true, row header even if there are no tree nodes
26750 * <br/>Defaults to true
26752 gridOptions.treeRowHeaderAlwaysVisible = gridOptions.treeRowHeaderAlwaysVisible !== false;
26756 * @name treeCustomAggregations
26757 * @propertyOf ui.grid.treeBase.api:GridOptions
26758 * @description Define custom aggregation functions. The properties of this object will be
26759 * aggregation types available for use on columnDef with {@link ui.grid.treeBase.api:ColumnDef treeAggregationType} or through the column menu.
26760 * If a function defined here uses the same name as one of the native aggregations, this one will take precedence.
26761 * The object format is:
26765 * aggregationName: {
26766 * label: (optional) string,
26767 * aggregationFn: function( aggregation, fieldValue, numValue, row ){...},
26768 * finalizerFn: (optional) function( aggregation ){...}
26772 * aggregationFn: function( aggregation, fieldValue, numValue ){
26773 * aggregation.count = (aggregation.count || 1) + 1;
26774 * aggregation.sum = (aggregation.sum || 0) + numValue;
26776 * finalizerFn: function( aggregation ){
26777 * aggregation.value = aggregation.sum / aggregation.count
26783 * <br/>The `finalizerFn` may be used to manipulate the value before rendering, or to
26784 * apply a custom rendered value. If `aggregation.rendered` is left undefined, the value will be
26785 * rendered. Note that the native aggregation functions use an `finalizerFn` to concatenate
26786 * the label and the value.
26788 * <br/>Defaults to {}
26790 gridOptions.treeCustomAggregations = gridOptions.treeCustomAggregations || {};
26794 * @name enableExpandAll
26795 * @propertyOf ui.grid.treeBase.api:GridOptions
26796 * @description Enable the expand all button at the top of the row header
26798 * <br/>Defaults to true
26800 gridOptions.enableExpandAll = gridOptions.enableExpandAll !== false;
26806 * @name treeBaseColumnBuilder
26807 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
26808 * @description Sets the tree defaults based on the columnDefs
26810 * @param {object} colDef columnDef we're basing on
26811 * @param {GridCol} col the column we're to update
26812 * @param {object} gridOptions the options we should use
26813 * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
26815 treeBaseColumnBuilder: function (colDef, col, gridOptions) {
26820 * @name customTreeAggregationFn
26821 * @propertyOf ui.grid.treeBase.api:ColumnDef
26822 * @description A custom function that aggregates rows into some form of
26823 * total. Aggregations run row-by-row, the function needs to be capable of
26824 * creating a running total.
26826 * The function will be provided the aggregation item (in which you can store running
26827 * totals), the row value that is to be aggregated, and that same row value converted to
26828 * a number (most aggregations work on numbers)
26831 * customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ){
26832 * // calculates the average of the squares of the values
26833 * if ( typeof(aggregation.count) === 'undefined' ){
26834 * aggregation.count = 0;
26836 * aggregation.count++;
26838 * if ( !isNaN(numValue) ){
26839 * if ( typeof(aggregation.total) === 'undefined' ){
26840 * aggregation.total = 0;
26842 * aggregation.total = aggregation.total + numValue * numValue;
26845 * aggregation.value = aggregation.total / aggregation.count;
26848 * <br/>Defaults to undefined. May be overwritten by treeAggregationType, the two options should not be used together.
26850 if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ){
26851 col.treeAggregationFn = colDef.customTreeAggregationFn;
26856 * @name treeAggregationType
26857 * @propertyOf ui.grid.treeBase.api:ColumnDef
26858 * @description Use one of the native or grid-level aggregation methods for calculating aggregations on this column.
26859 * Native method are in the constants file and include: SUM, COUNT, MIN, MAX, AVG. This may also be the property the
26860 * name of an aggregation function defined with {@link ui.grid.treeBase.api:GridOptions treeCustomAggregations}.
26863 * treeAggregationType = uiGridTreeBaseConstants.aggregation.SUM,
26867 * If you are using aggregations you should either:
26869 * - also use grouping, in which case the aggregations are displayed in the group header, OR
26870 * - use treeView, in which case you can set `treeAggregationUpdateEntity: true` in the colDef, and
26871 * treeBase will store the aggregation information in the entity, or you can set `treeAggregationUpdateEntity: false`
26872 * in the colDef, and you need to manual retrieve the calculated aggregations from the row.treeNode.aggregations
26874 * <br/>Takes precendence over a treeAggregationFn, the two options should not be used together.
26875 * <br/>Defaults to undefined.
26877 if ( typeof(colDef.treeAggregationType) !== 'undefined' ){
26878 col.treeAggregation = { type: colDef.treeAggregationType };
26879 if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ){
26880 col.treeAggregationFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].aggregationFn;
26881 col.treeAggregationFinalizerFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].finalizerFn;
26882 col.treeAggregation.label = gridOptions.treeCustomAggregations[colDef.treeAggregationType].label;
26883 } else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ){
26884 col.treeAggregationFn = service.nativeAggregations()[colDef.treeAggregationType].aggregationFn;
26885 col.treeAggregation.label = service.nativeAggregations()[colDef.treeAggregationType].label;
26891 * @name treeAggregationLabel
26892 * @propertyOf ui.grid.treeBase.api:ColumnDef
26893 * @description A custom label to use for this aggregation. If provided we don't use native i18n.
26895 if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ){
26896 if (typeof(col.treeAggregation) === 'undefined' ){
26897 col.treeAggregation = {};
26899 col.treeAggregation.label = colDef.treeAggregationLabel;
26904 * @name treeAggregationUpdateEntity
26905 * @propertyOf ui.grid.treeBase.api:ColumnDef
26906 * @description Store calculated aggregations into the entity, allowing them
26907 * to be displayed in the grid using a standard cellTemplate. This defaults to true,
26908 * if you are using grouping then you shouldn't set it to false, as then the aggregations won't
26911 * If you are using treeView in most cases you'll want to set this to true. This will result in
26912 * getCellValue returning the aggregation rather than whatever was stored in the cell attribute on
26913 * the entity. If you want to render the underlying entity value (and do something else with the aggregation)
26914 * then you could use a custom cellTemplate to display `row.entity.myAttribute`, rather than using getCellValue.
26916 * <br/>Defaults to true
26920 * gridOptions.columns = [{
26922 * treeAggregation: { type: uiGridTreeBaseConstants.aggregation.SUM },
26923 * treeAggregationUpdateEntity: true
26924 * cellTemplate: '<div>{{row.entity.myCol + " " + row.treeNode.aggregations[0].rendered}}</div>'
26928 col.treeAggregationUpdateEntity = colDef.treeAggregationUpdateEntity !== false;
26932 * @name customTreeAggregationFinalizerFn
26933 * @propertyOf ui.grid.treeBase.api:ColumnDef
26934 * @description A custom function that populates aggregation.rendered, this is called when
26935 * a particular aggregation has been fully calculated, and we want to render the value.
26937 * With the native aggregation options we just concatenate `aggregation.label` and
26938 * `aggregation.value`, but if you wanted to apply a filter or otherwise manipulate the label
26939 * or the value, you can do so with this function. This function will be called after the
26940 * the default `finalizerFn`.
26944 * customTreeAggregationFinalizerFn = function ( aggregation ){
26945 * aggregation.rendered = aggregation.label + aggregation.value / 100 + '%';
26948 * <br/>Defaults to undefined.
26950 if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ){
26951 col.customTreeAggregationFinalizerFn = colDef.customTreeAggregationFinalizerFn;
26959 * @name createRowHeader
26960 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
26961 * @description Create the rowHeader. If treeRowHeaderAlwaysVisible then
26962 * set it to visible, otherwise set it to invisible
26964 * @param {Grid} grid grid object
26966 createRowHeader: function( grid ){
26967 var rowHeaderColumnDef = {
26968 name: uiGridTreeBaseConstants.rowHeaderColName,
26970 width: grid.options.treeRowHeaderBaseWidth,
26972 cellTemplate: 'ui-grid/treeBaseRowHeader',
26973 headerCellTemplate: 'ui-grid/treeBaseHeaderCell',
26974 enableColumnResizing: false,
26975 enableColumnMenu: false,
26976 exporterSuppressExport: true,
26977 allowCellFocus: true
26980 rowHeaderColumnDef.visible = grid.options.treeRowHeaderAlwaysVisible;
26981 grid.addRowHeaderColumn( rowHeaderColumnDef, -100 );
26987 * @name expandAllRows
26988 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
26989 * @description Expands all nodes in the tree
26991 * @param {Grid} grid grid object
26993 expandAllRows: function (grid) {
26994 grid.treeBase.tree.forEach( function( node ) {
26995 service.setAllNodes( grid, node, uiGridTreeBaseConstants.EXPANDED);
26997 grid.treeBase.expandAll = true;
26998 grid.queueGridRefresh();
27004 * @name collapseAllRows
27005 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27006 * @description Collapses all nodes in the tree
27008 * @param {Grid} grid grid object
27010 collapseAllRows: function (grid) {
27011 grid.treeBase.tree.forEach( function( node ) {
27012 service.setAllNodes( grid, node, uiGridTreeBaseConstants.COLLAPSED);
27014 grid.treeBase.expandAll = false;
27015 grid.queueGridRefresh();
27021 * @name setAllNodes
27022 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27023 * @description Works through a subset of grid.treeBase.rowExpandedStates, setting
27024 * all child nodes (and their descendents) of the provided node to the given state.
27026 * Calls itself recursively on all nodes so as to achieve this.
27028 * @param {Grid} grid the grid we're operating on (so we can raise events)
27029 * @param {object} treeNode a node in the tree that we want to update
27030 * @param {string} targetState the state we want to set it to
27032 setAllNodes: function (grid, treeNode, targetState) {
27033 if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ){
27034 treeNode.state = targetState;
27036 if ( targetState === uiGridTreeBaseConstants.EXPANDED ){
27037 grid.api.treeBase.raise.rowExpanded(treeNode.row);
27039 grid.api.treeBase.raise.rowCollapsed(treeNode.row);
27043 // set all child nodes
27044 if ( treeNode.children ){
27045 treeNode.children.forEach(function( childNode ){
27046 service.setAllNodes(grid, childNode, targetState);
27054 * @name toggleRowTreeState
27055 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27056 * @description Toggles the expand or collapse state of this grouped row, if
27057 * it's a parent row
27059 * @param {Grid} grid grid object
27060 * @param {GridRow} row the row we want to toggle
27062 toggleRowTreeState: function ( grid, row ){
27063 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27067 if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED){
27068 service.collapseRow(grid, row);
27070 service.expandRow(grid, row);
27073 grid.queueGridRefresh();
27080 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27081 * @description Expands this specific row, showing only immediate children.
27083 * @param {Grid} grid grid object
27084 * @param {GridRow} row the row we want to expand
27086 expandRow: function ( grid, row ){
27087 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27091 if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
27092 row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
27093 grid.api.treeBase.raise.rowExpanded(row);
27094 grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
27095 grid.queueGridRefresh();
27102 * @name expandRowChildren
27103 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27104 * @description Expands this specific row, showing all children.
27106 * @param {Grid} grid grid object
27107 * @param {GridRow} row the row we want to expand
27109 expandRowChildren: function ( grid, row ){
27110 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27114 service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.EXPANDED);
27115 grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
27116 grid.queueGridRefresh();
27122 * @name collapseRow
27123 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27124 * @description Collapses this specific row
27126 * @param {Grid} grid grid object
27127 * @param {GridRow} row the row we want to collapse
27129 collapseRow: function( grid, row ){
27130 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27134 if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ){
27135 row.treeNode.state = uiGridTreeBaseConstants.COLLAPSED;
27136 grid.treeBase.expandAll = false;
27137 grid.api.treeBase.raise.rowCollapsed(row);
27138 grid.queueGridRefresh();
27145 * @name collapseRowChildren
27146 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27147 * @description Collapses this specific row and all children
27149 * @param {Grid} grid grid object
27150 * @param {GridRow} row the row we want to collapse
27152 collapseRowChildren: function( grid, row ){
27153 if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
27157 service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.COLLAPSED);
27158 grid.treeBase.expandAll = false;
27159 grid.queueGridRefresh();
27165 * @name allExpanded
27166 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27167 * @description Returns true if all rows are expanded, false
27168 * if they're not. Walks the tree to determine this. Used
27169 * to set the expandAll state.
27171 * If the node has no children, then return true (it's immaterial
27172 * whether it is expanded). If the node has children, then return
27173 * false if this node is collapsed, or if any child node is not all expanded
27175 * @param {object} tree the grid to check
27176 * @returns {boolean} whether or not the tree is all expanded
27178 allExpanded: function( tree ){
27179 var allExpanded = true;
27180 tree.forEach( function( node ){
27181 if ( !service.allExpandedInternal( node ) ){
27182 allExpanded = false;
27185 return allExpanded;
27188 allExpandedInternal: function( treeNode ){
27189 if ( treeNode.children && treeNode.children.length > 0 ){
27190 if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
27193 var allExpanded = true;
27194 treeNode.children.forEach( function( node ){
27195 if ( !service.allExpandedInternal( node ) ){
27196 allExpanded = false;
27199 return allExpanded;
27209 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27210 * @description The rowProcessor that adds the nodes to the tree, and sets the visible
27211 * state of each row based on it's parent state
27213 * Assumes it is always called after the sorting processor, and the grouping processor if there is one.
27214 * Performs any tree sorts itself after having built the tree
27216 * Processes all the rows in order, setting the group level based on the $$treeLevel in the associated
27217 * entity, and setting the visible state based on the parent's state.
27219 * Calculates the deepest level of tree whilst it goes, and updates that so that the header column can be correctly
27222 * Aggregates if necessary along the way.
27224 * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
27225 * @returns {array} the updated rows
27227 treeRows: function( renderableRows ) {
27228 if (renderableRows.length === 0){
27229 return renderableRows;
27233 var currentLevel = 0;
27234 var currentState = uiGridTreeBaseConstants.EXPANDED;
27237 grid.treeBase.tree = service.createTree( grid, renderableRows );
27238 service.updateRowHeaderWidth( grid );
27240 service.sortTree( grid );
27241 service.fixFilter( grid );
27243 return service.renderTree( grid.treeBase.tree );
27249 * @name createOrUpdateRowHeaderWidth
27250 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27251 * @description Calculates the rowHeader width.
27253 * If rowHeader is always present, updates the width.
27255 * If rowHeader is only sometimes present (`treeRowHeaderAlwaysVisible: false`), determines whether there
27256 * should be one, then creates or removes it as appropriate, with the created rowHeader having the
27259 * If there's never a rowHeader then never creates one: `showTreeRowHeader: false`
27261 * @param {Grid} grid the grid we want to set the row header on
27263 updateRowHeaderWidth: function( grid ){
27264 var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName);
27266 var newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
27267 if ( rowHeader && newWidth !== rowHeader.width ){
27268 rowHeader.width = newWidth;
27269 grid.queueRefresh();
27272 var newVisibility = true;
27273 if ( grid.options.showTreeRowHeader === false ){
27274 newVisibility = false;
27276 if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ){
27277 newVisibility = false;
27279 if ( rowHeader.visible !== newVisibility ) {
27280 rowHeader.visible = newVisibility;
27281 rowHeader.colDef.visible = newVisibility;
27282 grid.queueGridRefresh();
27290 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27291 * @description Creates an array of rows based on the tree, exporting only
27292 * the visible nodes and leaves
27294 * @param {array} nodeList the list of nodes - can be grid.treeBase.tree, or can be node.children when
27295 * we're calling recursively
27296 * @returns {array} renderable rows
27298 renderTree: function( nodeList ){
27299 var renderableRows = [];
27301 nodeList.forEach( function ( node ){
27302 if ( node.row.visible ){
27303 renderableRows.push( node.row );
27305 if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
27306 renderableRows = renderableRows.concat( service.renderTree( node.children ) );
27309 return renderableRows;
27316 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27317 * @description Creates a tree from the renderableRows
27319 * @param {Grid} grid the grid
27320 * @param {array} renderableRows the rows we want to create a tree from
27321 * @returns {object} the tree we've build
27323 createTree: function( grid, renderableRows ) {
27324 var currentLevel = -1;
27327 grid.treeBase.tree = [];
27328 grid.treeBase.numberLevels = 0;
27329 var aggregations = service.getAggregations( grid );
27331 var createNode = function( row ){
27332 if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ){
27333 row.treeLevel = row.entity.$$treeLevel;
27336 if ( row.treeLevel <= currentLevel ){
27337 // pop any levels that aren't parents of this level, formatting the aggregation at the same time
27338 while ( row.treeLevel <= currentLevel ){
27339 var lastParent = parents.pop();
27340 service.finaliseAggregations( lastParent );
27344 // reset our current state based on the new parent, set to expanded if this is a level 0 node
27345 if ( parents.length > 0 ){
27346 currentState = service.setCurrentState(parents);
27348 currentState = uiGridTreeBaseConstants.EXPANDED;
27352 // aggregate if this is a leaf node
27353 if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible ){
27354 service.aggregate( grid, row, parents );
27357 // add this node to the tree
27358 service.addOrUseNode(grid, row, parents, aggregations);
27360 if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ){
27363 currentState = service.setCurrentState(parents);
27366 // update the tree number of levels, so we can set header width if we need to
27367 if ( grid.treeBase.numberLevels < row.treeLevel + 1){
27368 grid.treeBase.numberLevels = row.treeLevel + 1;
27372 renderableRows.forEach( createNode );
27374 // finalise remaining aggregations
27375 while ( parents.length > 0 ){
27376 var lastParent = parents.pop();
27377 service.finaliseAggregations( lastParent );
27380 return grid.treeBase.tree;
27386 * @name addOrUseNode
27387 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27388 * @description Creates a tree node for this row. If this row already has a treeNode
27389 * recorded against it, preserves the state, but otherwise overwrites the data.
27391 * @param {grid} grid the grid we're operating on
27392 * @param {gridRow} row the row we want to set
27393 * @param {array} parents an array of the parents this row should have
27394 * @param {array} aggregationBase empty aggregation information
27395 * @returns {undefined} updates the parents array, updates the row to have a treeNode, and updates the
27396 * grid.treeBase.tree
27398 addOrUseNode: function( grid, row, parents, aggregationBase ){
27399 var newAggregations = [];
27400 aggregationBase.forEach( function(aggregation){
27401 newAggregations.push(service.buildAggregationObject(aggregation.col));
27404 var newNode = { state: uiGridTreeBaseConstants.COLLAPSED, row: row, parentRow: null, aggregations: newAggregations, children: [] };
27405 if ( row.treeNode ){
27406 newNode.state = row.treeNode.state;
27408 if ( parents.length > 0 ){
27409 newNode.parentRow = parents[parents.length - 1];
27411 row.treeNode = newNode;
27413 if ( parents.length === 0 ){
27414 grid.treeBase.tree.push( newNode );
27416 parents[parents.length - 1].treeNode.children.push( newNode );
27423 * @name setCurrentState
27424 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27425 * @description Looks at the parents array to determine our current state.
27426 * If any node in the hierarchy is collapsed, then return collapsed, otherwise return
27429 * @param {array} parents an array of the parents this row should have
27430 * @returns {string} the state we should be setting to any nodes we see
27432 setCurrentState: function( parents ){
27433 var currentState = uiGridTreeBaseConstants.EXPANDED;
27434 parents.forEach( function(parent){
27435 if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
27436 currentState = uiGridTreeBaseConstants.COLLAPSED;
27439 return currentState;
27446 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27447 * @description Performs a recursive sort on the tree nodes, sorting the
27448 * children of each node and putting them back into the children array.
27450 * Before doing this it turns back on all the sortIgnore - things that were previously
27451 * ignored we process now. Since we're sorting within the nodes, presumably anything
27452 * that was already sorted is how we derived the nodes, we can keep those sorts too.
27454 * We only sort tree nodes that are expanded - no point in wasting effort sorting collapsed
27457 * @param {Grid} grid the grid to get the aggregation information from
27458 * @returns {array} the aggregation information
27460 sortTree: function( grid ){
27461 grid.columns.forEach( function( column ) {
27462 if ( column.sort && column.sort.ignoreSort ){
27463 delete column.sort.ignoreSort;
27467 grid.treeBase.tree = service.sortInternal( grid, grid.treeBase.tree );
27470 sortInternal: function( grid, treeList ){
27471 var rows = treeList.map( function( node ){
27475 rows = rowSorter.sort( grid, rows, grid.columns );
27477 var treeNodes = rows.map( function( row ){
27478 return row.treeNode;
27481 treeNodes.forEach( function( node ){
27482 if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
27483 node.children = service.sortInternal( grid, node.children );
27493 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27494 * @description After filtering has run, we need to go back through the tree
27495 * and make sure the parent rows are always visible if any of the child rows
27496 * are visible (filtering may make a child visible, but the parent may not
27497 * match the filter criteria)
27499 * This has a risk of being computationally expensive, we do it by walking
27500 * the tree and remembering whether there are any invisible nodes on the
27503 * @param {Grid} grid the grid to fix filters on
27505 fixFilter: function( grid ){
27506 var parentsVisible;
27508 grid.treeBase.tree.forEach( function( node ){
27509 if ( node.children && node.children.length > 0 ){
27510 parentsVisible = node.row.visible;
27511 service.fixFilterInternal( node.children, parentsVisible );
27516 fixFilterInternal: function( nodes, parentsVisible) {
27517 nodes.forEach( function( node ){
27518 if ( node.row.visible && !parentsVisible ){
27519 service.setParentsVisible( node );
27520 parentsVisible = true;
27523 if ( node.children && node.children.length > 0 ){
27524 if ( service.fixFilterInternal( node.children, ( parentsVisible && node.row.visible ) ) ) {
27525 parentsVisible = true;
27530 return parentsVisible;
27533 setParentsVisible: function( node ){
27534 while ( node.parentRow ){
27535 node.parentRow.visible = true;
27536 node = node.parentRow.treeNode;
27542 * @name buildAggregationObject
27543 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27544 * @description Build the object which is stored on the column for holding meta-data about the aggregation.
27545 * This method should only be called with columns which have an aggregation.
27547 * @param {Column} the column which this object relates to
27548 * @returns {object} {col: Column object, label: string, type: string (optional)}
27550 buildAggregationObject: function( column ){
27551 var newAggregation = { col: column };
27553 if ( column.treeAggregation && column.treeAggregation.type ){
27554 newAggregation.type = column.treeAggregation.type;
27557 if ( column.treeAggregation && column.treeAggregation.label ){
27558 newAggregation.label = column.treeAggregation.label;
27561 return newAggregation;
27566 * @name getAggregations
27567 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27568 * @description Looks through the grid columns to find those with aggregations,
27569 * and collates the aggregation information into an array, returns that array
27571 * @param {Grid} grid the grid to get the aggregation information from
27572 * @returns {array} the aggregation information
27574 getAggregations: function( grid ){
27575 var aggregateArray = [];
27577 grid.columns.forEach( function(column){
27578 if ( typeof(column.treeAggregationFn) !== 'undefined' ){
27579 aggregateArray.push( service.buildAggregationObject(column) );
27581 if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ){
27582 // Add aggregation object for footer
27583 column.treeFooterAggregation = service.buildAggregationObject(column);
27584 column.aggregationType = service.treeFooterAggregationType;
27588 return aggregateArray;
27595 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27596 * @description Accumulate the data from this row onto the aggregations for each parent
27598 * Iterate over the parents, then iterate over the aggregations for each of those parents,
27599 * and perform the aggregation for each individual aggregation
27601 * @param {Grid} grid grid object
27602 * @param {GridRow} row the row we want to set grouping visibility on
27603 * @param {array} parents the parents that we would want to aggregate onto
27605 aggregate: function( grid, row, parents ){
27606 if ( parents.length === 0 && row.treeNode && row.treeNode.aggregations ){
27607 row.treeNode.aggregations.forEach(function(aggregation){
27608 // Calculate aggregations for footer even if there are no grouped rows
27609 if ( typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ) {
27610 var fieldValue = grid.getCellValue(row, aggregation.col);
27611 var numValue = Number(fieldValue);
27612 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
27617 parents.forEach( function( parent, index ){
27618 if ( parent.treeNode.aggregations ){
27619 parent.treeNode.aggregations.forEach( function( aggregation ){
27620 var fieldValue = grid.getCellValue(row, aggregation.col);
27621 var numValue = Number(fieldValue);
27622 aggregation.col.treeAggregationFn(aggregation, fieldValue, numValue, row);
27624 if ( index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ){
27625 aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
27633 // Aggregation routines - no doco needed as self evident
27634 nativeAggregations: function() {
27635 var nativeAggregations = {
27637 label: i18nService.get().aggregation.count,
27638 menuTitle: i18nService.get().grouping.aggregate_count,
27639 aggregationFn: function (aggregation, fieldValue, numValue) {
27640 if (typeof(aggregation.value) === 'undefined') {
27641 aggregation.value = 1;
27643 aggregation.value++;
27649 label: i18nService.get().aggregation.sum,
27650 menuTitle: i18nService.get().grouping.aggregate_sum,
27651 aggregationFn: function( aggregation, fieldValue, numValue ) {
27652 if (!isNaN(numValue)) {
27653 if (typeof(aggregation.value) === 'undefined') {
27654 aggregation.value = numValue;
27656 aggregation.value += numValue;
27663 label: i18nService.get().aggregation.min,
27664 menuTitle: i18nService.get().grouping.aggregate_min,
27665 aggregationFn: function( aggregation, fieldValue, numValue ) {
27666 if (typeof(aggregation.value) === 'undefined') {
27667 aggregation.value = fieldValue;
27669 if (typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue < aggregation.value || aggregation.value === null)) {
27670 aggregation.value = fieldValue;
27677 label: i18nService.get().aggregation.max,
27678 menuTitle: i18nService.get().grouping.aggregate_max,
27679 aggregationFn: function( aggregation, fieldValue, numValue ){
27680 if ( typeof(aggregation.value) === 'undefined' ){
27681 aggregation.value = fieldValue;
27683 if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)){
27684 aggregation.value = fieldValue;
27691 label: i18nService.get().aggregation.avg,
27692 menuTitle: i18nService.get().grouping.aggregate_avg,
27693 aggregationFn: function( aggregation, fieldValue, numValue ){
27694 if ( typeof(aggregation.count) === 'undefined' ){
27695 aggregation.count = 1;
27697 aggregation.count++;
27700 if ( isNaN(numValue) ){
27704 if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ){
27705 aggregation.value = numValue;
27706 aggregation.sum = numValue;
27708 aggregation.sum += numValue;
27709 aggregation.value = aggregation.sum / aggregation.count;
27714 return nativeAggregations;
27719 * @name finaliseAggregation
27720 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27721 * @description Helper function used to finalize aggregation nodes and footer cells
27723 * @param {gridRow} row the parent we're finalising
27724 * @param {aggregation} the aggregation object manipulated by the aggregationFn
27726 finaliseAggregation: function(row, aggregation){
27727 if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ){
27728 angular.extend( aggregation, row.entity[ '$$' + aggregation.col.uid ]);
27731 if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ){
27732 aggregation.col.treeAggregationFinalizerFn( aggregation );
27734 if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ){
27735 aggregation.col.customTreeAggregationFinalizerFn( aggregation );
27737 if ( typeof(aggregation.rendered) === 'undefined' ){
27738 aggregation.rendered = aggregation.label ? aggregation.label + aggregation.value : aggregation.value;
27744 * @name finaliseAggregations
27745 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27746 * @description Format the data from the aggregation into the rendered text
27747 * e.g. if we had label: 'sum: ' and value: 25, we'd create 'sum: 25'.
27749 * As part of this we call any formatting callback routines we've been provided.
27751 * We write our aggregation out to the row.entity if treeAggregationUpdateEntity is
27752 * set on the column - we don't overwrite any information that's already there, we append
27753 * to it so that grouping can have set the groupVal beforehand without us overwriting it.
27755 * We need to copy the data from the row.entity first before we finalise the aggregation,
27756 * we need that information for the finaliserFn
27758 * @param {gridRow} row the parent we're finalising
27760 finaliseAggregations: function( row ){
27761 if ( row == null || typeof(row.treeNode.aggregations) === 'undefined' ){
27765 row.treeNode.aggregations.forEach( function( aggregation ) {
27766 service.finaliseAggregation(row, aggregation);
27768 if ( aggregation.col.treeAggregationUpdateEntity ){
27769 var aggregationCopy = {};
27770 angular.forEach( aggregation, function( value, key ){
27771 if ( aggregation.hasOwnProperty(key) && key !== 'col' ){
27772 aggregationCopy[key] = value;
27776 row.entity[ '$$' + aggregation.col.uid ] = aggregationCopy;
27783 * @name treeFooterAggregationType
27784 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
27785 * @description Uses the tree aggregation functions and finalizers to set the
27786 * column footer aggregations.
27788 * @param {rows} visible rows. not used, but accepted to match signature of GridColumn.aggregationType
27789 * @param {gridColumn} the column we are finalizing
27791 treeFooterAggregationType: function( rows, column ) {
27792 service.finaliseAggregation(undefined, column.treeFooterAggregation);
27793 if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ){
27794 // The was apparently no aggregation performed (perhaps this is a grouped column
27797 return column.treeFooterAggregation.rendered;
27808 * @name ui.grid.treeBase.directive:uiGridTreeRowHeaderButtons
27811 * @description Provides the expand/collapse button on rows
27813 module.directive('uiGridTreeBaseRowHeaderButtons', ['$templateCache', 'uiGridTreeBaseService',
27814 function ($templateCache, uiGridTreeBaseService) {
27818 template: $templateCache.get('ui-grid/treeBaseRowHeaderButtons'),
27820 require: '^uiGrid',
27821 link: function($scope, $elm, $attrs, uiGridCtrl) {
27822 var self = uiGridCtrl.grid;
27823 $scope.treeButtonClick = function(row, evt) {
27824 uiGridTreeBaseService.toggleRowTreeState(self, row, evt);
27833 * @name ui.grid.treeBase.directive:uiGridTreeBaseExpandAllButtons
27836 * @description Provides the expand/collapse all button
27838 module.directive('uiGridTreeBaseExpandAllButtons', ['$templateCache', 'uiGridTreeBaseService',
27839 function ($templateCache, uiGridTreeBaseService) {
27843 template: $templateCache.get('ui-grid/treeBaseExpandAllButtons'),
27845 link: function($scope, $elm, $attrs, uiGridCtrl) {
27846 var self = $scope.col.grid;
27848 $scope.headerButtonClick = function(row, evt) {
27849 if ( self.treeBase.expandAll ){
27850 uiGridTreeBaseService.collapseAllRows(self, evt);
27852 uiGridTreeBaseService.expandAllRows(self, evt);
27862 * @name ui.grid.treeBase.directive:uiGridViewport
27865 * @description Stacks on top of ui.grid.uiGridViewport to set formatting on a tree header row
27867 module.directive('uiGridViewport',
27868 ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
27869 function ($compile, uiGridConstants, gridUtil, $parse) {
27871 priority: -200, // run after default directive
27873 compile: function ($elm, $attrs) {
27874 var rowRepeatDiv = angular.element($elm.children().children()[0]);
27876 var existingNgClass = rowRepeatDiv.attr("ng-class");
27877 var newNgClass = '';
27878 if ( existingNgClass ) {
27879 newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-tree-header-row': row.treeLevel > -1}";
27881 newNgClass = "{'ui-grid-tree-header-row': row.treeLevel > -1}";
27883 rowRepeatDiv.attr("ng-class", newNgClass);
27886 pre: function ($scope, $elm, $attrs, controllers) {
27889 post: function ($scope, $elm, $attrs, controllers) {
27902 * @name ui.grid.treeView
27905 * # ui.grid.treeView
27907 * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
27909 * This module provides a tree view of the data that it is provided, with nodes in that
27910 * tree and leaves. Unlike grouping, the tree is an inherent property of the data and must
27911 * be provided with your data array.
27913 * Design information:
27914 * -------------------
27916 * TreeView uses treeBase for the underlying functionality, and is a very thin wrapper around
27917 * that logic. Most of the design information has now moved to treebase.
27921 * <div doc-module-components="ui.grid.treeView"></div>
27924 var module = angular.module('ui.grid.treeView', ['ui.grid', 'ui.grid.treeBase']);
27928 * @name ui.grid.treeView.constant:uiGridTreeViewConstants
27930 * @description constants available in treeView module, this includes
27931 * all the constants declared in the treeBase module (these are manually copied
27932 * as there isn't an easy way to include constants in another constants file, and
27933 * we don't want to make users include treeBase)
27936 module.constant('uiGridTreeViewConstants', {
27937 featureName: "treeView",
27938 rowHeaderColName: 'treeBaseRowHeaderCol',
27939 EXPANDED: 'expanded',
27940 COLLAPSED: 'collapsed',
27952 * @name ui.grid.treeView.service:uiGridTreeViewService
27954 * @description Services for treeView features
27956 module.service('uiGridTreeViewService', ['$q', 'uiGridTreeViewConstants', 'uiGridTreeBaseConstants', 'uiGridTreeBaseService', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants',
27957 function ($q, uiGridTreeViewConstants, uiGridTreeBaseConstants, uiGridTreeBaseService, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants) {
27961 initializeGrid: function (grid, $scope) {
27962 uiGridTreeBaseService.initializeGrid( grid, $scope );
27966 * @name ui.grid.treeView.grid:treeView
27968 * @description Grid properties and functions added for treeView
27970 grid.treeView = {};
27972 grid.registerRowsProcessor(service.adjustSorting, 60);
27976 * @name ui.grid.treeView.api:PublicApi
27978 * @description Public Api for treeView feature
27991 grid.api.registerEventsFromObject(publicApi.events);
27993 grid.api.registerMethodsFromObject(publicApi.methods);
27997 defaultGridOptions: function (gridOptions) {
27998 //default option to true unless it was explicitly set to false
28001 * @name ui.grid.treeView.api:GridOptions
28003 * @description GridOptions for treeView feature, these are available to be
28004 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
28006 * Many tree options are set on treeBase, make sure to look at that feature in
28007 * conjunction with these options.
28012 * @name enableTreeView
28013 * @propertyOf ui.grid.treeView.api:GridOptions
28014 * @description Enable row tree view for entire grid.
28015 * <br/>Defaults to true
28017 gridOptions.enableTreeView = gridOptions.enableTreeView !== false;
28024 * @name adjustSorting
28025 * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
28026 * @description Trees cannot be sorted the same as flat lists of rows -
28027 * trees are sorted recursively within each level - so the children of each
28028 * node are sorted, but not the full set of rows.
28030 * To achieve this, we suppress the normal sorting by setting ignoreSort on
28031 * each of the sort columns. When the treeBase rowsProcessor runs it will then
28032 * unignore these, and will perform a recursive sort against the tree that it builds.
28034 * @param {array} renderableRows the rows that we need to pass on through
28035 * @returns {array} renderableRows that we passed on through
28037 adjustSorting: function( renderableRows ) {
28040 grid.columns.forEach( function( column ){
28041 if ( column.sort ){
28042 column.sort.ignoreSort = true;
28046 return renderableRows;
28057 * @name ui.grid.treeView.directive:uiGridTreeView
28061 * @description Adds treeView features to grid
28064 <example module="app">
28065 <file name="app.js">
28066 var app = angular.module('app', ['ui.grid', 'ui.grid.treeView']);
28068 app.controller('MainCtrl', ['$scope', function ($scope) {
28070 { name: 'Bob', title: 'CEO' },
28071 { name: 'Frank', title: 'Lowly Developer' }
28074 $scope.columnDefs = [
28075 {name: 'name', enableCellEdit: true},
28076 {name: 'title', enableCellEdit: true}
28079 $scope.gridOptions = { columnDefs: $scope.columnDefs, data: $scope.data };
28082 <file name="index.html">
28083 <div ng-controller="MainCtrl">
28084 <div ui-grid="gridOptions" ui-grid-tree-view></div>
28089 module.directive('uiGridTreeView', ['uiGridTreeViewConstants', 'uiGridTreeViewService', '$templateCache',
28090 function (uiGridTreeViewConstants, uiGridTreeViewService, $templateCache) {
28094 require: '^uiGrid',
28096 compile: function () {
28098 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
28099 if (uiGridCtrl.grid.options.enableTreeView !== false){
28100 uiGridTreeViewService.initializeGrid(uiGridCtrl.grid, $scope);
28103 post: function ($scope, $elm, $attrs, uiGridCtrl) {
28117 * @name ui.grid.validate
28120 * # ui.grid.validate
28122 * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
28124 * This module provides the ability to validate cells upon change.
28126 * Design information:
28127 * -------------------
28129 * Validation is not based on angularjs validation, since it would work only when editing the field.
28131 * Instead it adds custom properties to any field considered as invalid.
28136 * <div doc-module-components="ui.grid.expandable"></div>
28139 var module = angular.module('ui.grid.validate', ['ui.grid']);
28144 * @name ui.grid.validate.service:uiGridValidateService
28146 * @description Services for validation features
28148 module.service('uiGridValidateService', ['$sce', '$q', '$http', 'i18nService', 'uiGridConstants', function ($sce, $q, $http, i18nService, uiGridConstants) {
28154 * @name validatorFactories
28155 * @propertyOf ui.grid.validate.service:uiGridValidateService
28156 * @description object containing all the factories used to validate data.<br/>
28157 * These factories will be in the form <br/>
28160 * validatorFactory: function(argument) {
28161 * return function(newValue, oldValue, rowEntity, colDef) {
28162 * return true || false || promise
28165 * messageFunction: function(argument) {
28171 * Promises should return true or false as result according to the result of validation.
28173 validatorFactories: {},
28178 * @name setExternalFactoryFunction
28179 * @methodOf ui.grid.validate.service:uiGridValidateService
28180 * @description Adds a way to retrieve validators from an external service
28181 * <p>Validators from this external service have a higher priority than default
28183 * @param {function} externalFactoryFunction a function that accepts name and argument to pass to a
28184 * validator factory and that returns an object with the same properties as
28185 * you can see in {@link ui.grid.validate.service:uiGridValidateService#properties_validatorFactories validatorFactories}
28187 setExternalFactoryFunction: function(externalFactoryFunction) {
28188 service.externalFactoryFunction = externalFactoryFunction;
28193 * @name clearExternalFactory
28194 * @methodOf ui.grid.validate.service:uiGridValidateService
28195 * @description Removes any link to external factory from this service
28197 clearExternalFactory: function() {
28198 delete service.externalFactoryFunction;
28203 * @name getValidatorFromExternalFactory
28204 * @methodOf ui.grid.validate.service:uiGridValidateService
28205 * @description Retrieves a validator by executing a validatorFactory
28206 * stored in an external service.
28207 * @param {string} name the name of the validator to retrieve
28208 * @param {object} argument an argument to pass to the validator factory
28210 getValidatorFromExternalFactory: function(name, argument) {
28211 return service.externalFactoryFunction(name, argument).validatorFactory(argument);
28216 * @name getMessageFromExternalFactory
28217 * @methodOf ui.grid.validate.service:uiGridValidateService
28218 * @description Retrieves a message stored in an external service.
28219 * @param {string} name the name of the validator
28220 * @param {object} argument an argument to pass to the message function
28222 getMessageFromExternalFactory: function(name, argument) {
28223 return service.externalFactoryFunction(name, argument).messageFunction(argument);
28228 * @name setValidator
28229 * @methodOf ui.grid.validate.service:uiGridValidateService
28230 * @description Adds a new validator to the service
28231 * @param {string} name the name of the validator, must be unique
28232 * @param {function} validatorFactory a factory that return a validatorFunction
28233 * @param {function} messageFunction a function that return the error message
28235 setValidator: function(name, validatorFactory, messageFunction) {
28236 service.validatorFactories[name] = {
28237 validatorFactory: validatorFactory,
28238 messageFunction: messageFunction
28244 * @name getValidator
28245 * @methodOf ui.grid.validate.service:uiGridValidateService
28246 * @description Returns a validator registered to the service
28247 * or retrieved from the external factory
28248 * @param {string} name the name of the validator to retrieve
28249 * @param {object} argument an argument to pass to the validator factory
28250 * @returns {object} the validator function
28252 getValidator: function(name, argument) {
28253 if (service.externalFactoryFunction) {
28254 var validator = service.getValidatorFromExternalFactory(name, argument);
28259 if (!service.validatorFactories[name]) {
28260 throw ("Invalid validator name: " + name);
28262 return service.validatorFactories[name].validatorFactory(argument);
28268 * @methodOf ui.grid.validate.service:uiGridValidateService
28269 * @description Returns the error message related to the validator
28270 * @param {string} name the name of the validator
28271 * @param {object} argument an argument to pass to the message function
28272 * @returns {string} the error message related to the validator
28274 getMessage: function(name, argument) {
28275 if (service.externalFactoryFunction) {
28276 var message = service.getMessageFromExternalFactory(name, argument);
28281 return service.validatorFactories[name].messageFunction(argument);
28287 * @methodOf ui.grid.validate.service:uiGridValidateService
28288 * @description Returns true if the cell (identified by rowEntity, colDef) is invalid
28289 * @param {object} rowEntity the row entity of the cell
28290 * @param {object} colDef the colDef of the cell
28291 * @returns {boolean} true if the cell is invalid
28293 isInvalid: function (rowEntity, colDef) {
28294 return rowEntity['$$invalid'+colDef.name];
28300 * @methodOf ui.grid.validate.service:uiGridValidateService
28301 * @description Makes the cell invalid by adding the proper field to the entity
28302 * @param {object} rowEntity the row entity of the cell
28303 * @param {object} colDef the colDef of the cell
28305 setInvalid: function (rowEntity, colDef) {
28306 rowEntity['$$invalid'+colDef.name] = true;
28312 * @methodOf ui.grid.validate.service:uiGridValidateService
28313 * @description Makes the cell valid by removing the proper error field from the entity
28314 * @param {object} rowEntity the row entity of the cell
28315 * @param {object} colDef the colDef of the cell
28317 setValid: function (rowEntity, colDef) {
28318 delete rowEntity['$$invalid'+colDef.name];
28324 * @methodOf ui.grid.validate.service:uiGridValidateService
28325 * @description Adds the proper error to the entity errors field
28326 * @param {object} rowEntity the row entity of the cell
28327 * @param {object} colDef the colDef of the cell
28328 * @param {string} validatorName the name of the validator that is failing
28330 setError: function(rowEntity, colDef, validatorName) {
28331 if (!rowEntity['$$errors'+colDef.name]) {
28332 rowEntity['$$errors'+colDef.name] = {};
28334 rowEntity['$$errors'+colDef.name][validatorName] = true;
28340 * @methodOf ui.grid.validate.service:uiGridValidateService
28341 * @description Removes the proper error from the entity errors field
28342 * @param {object} rowEntity the row entity of the cell
28343 * @param {object} colDef the colDef of the cell
28344 * @param {string} validatorName the name of the validator that is failing
28346 clearError: function(rowEntity, colDef, validatorName) {
28347 if (!rowEntity['$$errors'+colDef.name]) {
28350 if (validatorName in rowEntity['$$errors'+colDef.name]) {
28351 delete rowEntity['$$errors'+colDef.name][validatorName];
28357 * @name getErrorMessages
28358 * @methodOf ui.grid.validate.service:uiGridValidateService
28359 * @description returns an array of i18n-ed error messages.
28360 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28361 * @param {object} colDef the column whose errors we are looking for
28362 * @returns {array} An array of strings containing all the error messages for the cell
28364 getErrorMessages: function(rowEntity, colDef) {
28367 if (!rowEntity['$$errors'+colDef.name] || Object.keys(rowEntity['$$errors'+colDef.name]).length === 0) {
28371 Object.keys(rowEntity['$$errors'+colDef.name]).sort().forEach(function(validatorName) {
28372 errors.push(service.getMessage(validatorName, colDef.validators[validatorName]));
28380 * @name getFormattedErrors
28381 * @methodOf ui.grid.validate.service:uiGridValidateService
28382 * @description returns the error i18n-ed and formatted in html to be shown inside the page.
28383 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28384 * @param {object} colDef the column whose errors we are looking for
28385 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28386 * message inside the page (i.e. inside a div)
28388 getFormattedErrors: function(rowEntity, colDef) {
28390 var msgString = "";
28392 var errors = service.getErrorMessages(rowEntity, colDef);
28394 if (!errors.length) {
28398 errors.forEach(function(errorMsg) {
28399 msgString += errorMsg + "<br/>";
28402 return $sce.trustAsHtml('<p><b>' + i18nService.getSafeText('validate.error') + '</b></p>' + msgString );
28407 * @name getTitleFormattedErrors
28408 * @methodOf ui.grid.validate.service:uiGridValidateService
28409 * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html
28411 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28412 * @param {object} colDef the column whose errors we are looking for
28413 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28414 * message inside an html title attribute
28416 getTitleFormattedErrors: function(rowEntity, colDef) {
28418 var newLine = "\n";
28420 var msgString = "";
28422 var errors = service.getErrorMessages(rowEntity, colDef);
28424 if (!errors.length) {
28428 errors.forEach(function(errorMsg) {
28429 msgString += errorMsg + newLine;
28432 return $sce.trustAsHtml(i18nService.getSafeText('validate.error') + newLine + msgString);
28437 * @name getTitleFormattedErrors
28438 * @methodOf ui.grid.validate.service:uiGridValidateService
28439 * @description Executes all validators on a cell (identified by row entity and column definition) and sets or clears errors
28440 * @param {object} rowEntity the row entity of the cell we want to run the validators on
28441 * @param {object} colDef the column definition of the cell we want to run the validators on
28442 * @param {object} newValue the value the user just entered
28443 * @param {object} oldValue the value the field had before
28445 runValidators: function(rowEntity, colDef, newValue, oldValue, grid) {
28447 if (newValue === oldValue) {
28448 // If the value has not changed we perform no validation
28452 if (typeof(colDef.name) === 'undefined' || !colDef.name) {
28453 throw new Error('colDef.name is required to perform validation');
28456 service.setValid(rowEntity, colDef);
28458 var validateClosureFactory = function(rowEntity, colDef, validatorName) {
28459 return function(value) {
28461 service.setInvalid(rowEntity, colDef);
28462 service.setError(rowEntity, colDef, validatorName);
28464 grid.api.validate.raise.validationFailed(rowEntity, colDef, newValue, oldValue);
28472 for (var validatorName in colDef.validators) {
28473 service.clearError(rowEntity, colDef, validatorName);
28475 var validatorFunction = service.getValidator(validatorName, colDef.validators[validatorName]);
28476 // We pass the arguments as oldValue, newValue so they are in the same order
28477 // as ng-model validators (modelValue, viewValue)
28479 .when(validatorFunction(oldValue, newValue, rowEntity, colDef))
28480 .then(validateClosureFactory(rowEntity, colDef, validatorName));
28481 promises.push(promise);
28484 return $q.all(promises);
28489 * @name createDefaultValidators
28490 * @methodOf ui.grid.validate.service:uiGridValidateService
28491 * @description adds the basic validators to the list of service validators
28493 createDefaultValidators: function() {
28494 service.setValidator('minLength',
28495 function (argument) {
28496 return function (oldValue, newValue, rowEntity, colDef) {
28497 if (newValue === undefined || newValue === null || newValue === '') {
28500 return newValue.length >= argument;
28503 function(argument) {
28504 return i18nService.getSafeText('validate.minLength').replace('THRESHOLD', argument);
28507 service.setValidator('maxLength',
28508 function (argument) {
28509 return function (oldValue, newValue, rowEntity, colDef) {
28510 if (newValue === undefined || newValue === null || newValue === '') {
28513 return newValue.length <= argument;
28516 function(threshold) {
28517 return i18nService.getSafeText('validate.maxLength').replace('THRESHOLD', threshold);
28520 service.setValidator('required',
28521 function (argument) {
28522 return function (oldValue, newValue, rowEntity, colDef) {
28524 return !(newValue === undefined || newValue === null || newValue === '');
28529 function(argument) {
28530 return i18nService.getSafeText('validate.required');
28534 initializeGrid: function (scope, grid) {
28537 isInvalid: service.isInvalid,
28539 getFormattedErrors: service.getFormattedErrors,
28541 getTitleFormattedErrors: service.getTitleFormattedErrors,
28543 runValidators: service.runValidators
28548 * @name ui.grid.validate.api:PublicApi
28550 * @description Public Api for validation feature
28557 * @name validationFailed
28558 * @eventOf ui.grid.validate.api:PublicApi
28559 * @description raised when one or more failure happened during validation
28561 * gridApi.validate.on.validationFailed(scope, function(rowEntity, colDef, newValue, oldValue){...})
28563 * @param {object} rowEntity the options.data element whose validation failed
28564 * @param {object} colDef the column whose validation failed
28565 * @param {object} newValue new value
28566 * @param {object} oldValue old value
28568 validationFailed: function (rowEntity, colDef, newValue, oldValue) {
28577 * @methodOf ui.grid.validate.api:PublicApi
28578 * @description checks if a cell (identified by rowEntity, colDef) is invalid
28579 * @param {object} rowEntity gridOptions.data[] array instance we want to check
28580 * @param {object} colDef the column whose errors we want to check
28581 * @returns {boolean} true if the cell value is not valid
28583 isInvalid: function(rowEntity, colDef) {
28584 return grid.validate.isInvalid(rowEntity, colDef);
28588 * @name getErrorMessages
28589 * @methodOf ui.grid.validate.api:PublicApi
28590 * @description returns an array of i18n-ed error messages.
28591 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28592 * @param {object} colDef the column whose errors we are looking for
28593 * @returns {array} An array of strings containing all the error messages for the cell
28595 getErrorMessages: function (rowEntity, colDef) {
28596 return grid.validate.getErrorMessages(rowEntity, colDef);
28600 * @name getFormattedErrors
28601 * @methodOf ui.grid.validate.api:PublicApi
28602 * @description returns the error i18n-ed and formatted in html to be shown inside the page.
28603 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28604 * @param {object} colDef the column whose errors we are looking for
28605 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28606 * message inside the page (i.e. inside a div)
28608 getFormattedErrors: function (rowEntity, colDef) {
28609 return grid.validate.getFormattedErrors(rowEntity, colDef);
28613 * @name getTitleFormattedErrors
28614 * @methodOf ui.grid.validate.api:PublicApi
28615 * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html
28617 * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for
28618 * @param {object} colDef the column whose errors we are looking for
28619 * @returns {object} An object that can be used in a template (like a cellTemplate) to display the
28620 * message inside an html title attribute
28622 getTitleFormattedErrors: function (rowEntity, colDef) {
28623 return grid.validate.getTitleFormattedErrors(rowEntity, colDef);
28629 grid.api.registerEventsFromObject(publicApi.events);
28630 grid.api.registerMethodsFromObject(publicApi.methods);
28633 grid.api.edit.on.afterCellEdit(scope, function(rowEntity, colDef, newValue, oldValue) {
28634 grid.validate.runValidators(rowEntity, colDef, newValue, oldValue, grid);
28638 service.createDefaultValidators();
28649 * @name ui.grid.validate.directive:uiGridValidate
28652 * @description Adds validating features to the ui-grid directive.
28654 <example module="app">
28655 <file name="app.js">
28656 var app = angular.module('app', ['ui.grid', 'ui.grid.edit', 'ui.grid.validate']);
28658 app.controller('MainCtrl', ['$scope', function ($scope) {
28660 { name: 'Bob', title: 'CEO' },
28661 { name: 'Frank', title: 'Lowly Developer' }
28664 $scope.columnDefs = [
28665 {name: 'name', enableCellEdit: true, validators: {minLength: 3, maxLength: 9}, cellTemplate: 'ui-grid/cellTitleValidator'},
28666 {name: 'title', enableCellEdit: true, validators: {required: true}, cellTemplate: 'ui-grid/cellTitleValidator'}
28670 <file name="index.html">
28671 <div ng-controller="MainCtrl">
28672 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit ui-grid-validate></div>
28678 module.directive('uiGridValidate', ['gridUtil', 'uiGridValidateService', function (gridUtil, uiGridValidateService) {
28682 require: '^uiGrid',
28684 compile: function () {
28686 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
28687 uiGridValidateService.initializeGrid($scope, uiGridCtrl.grid);
28689 post: function ($scope, $elm, $attrs, uiGridCtrl) {
28697 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
28700 $templateCache.put('ui-grid/ui-grid-filter',
28701 "<div class=\"ui-grid-filter-container\" ng-style=\"col.extraStyle\" ng-repeat=\"colFilter in col.filters\" ng-class=\"{'ui-grid-filter-cancel-button-hidden' : colFilter.disableCancelFilterButton === true }\"><div ng-if=\"colFilter.type !== 'select'\"><input type=\"text\" class=\"ui-grid-filter-input ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-attr-placeholder=\"{{colFilter.placeholder || ''}}\" aria-label=\"{{colFilter.ariaLabel || aria.defaultFilterLabel}}\"><div role=\"button\" class=\"ui-grid-filter-button\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term !== null && colFilter.term !== ''\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\"> </i></div></div><div ng-if=\"colFilter.type === 'select'\"><select class=\"ui-grid-filter-select ui-grid-filter-input-{{$index}}\" ng-model=\"colFilter.term\" ng-show=\"colFilter.selectOptions.length > 0\" ng-attr-placeholder=\"{{colFilter.placeholder || aria.defaultFilterLabel}}\" aria-label=\"{{colFilter.ariaLabel || ''}}\" ng-options=\"option.value as option.label for option in colFilter.selectOptions\"><option value=\"\"></option></select><div role=\"button\" class=\"ui-grid-filter-button-select\" ng-click=\"removeFilter(colFilter, $index)\" ng-if=\"!colFilter.disableCancelFilterButton\" ng-disabled=\"colFilter.term === undefined || colFilter.term === null || colFilter.term === ''\" ng-show=\"colFilter.term !== undefined && colFilter.term != null\"><i class=\"ui-grid-icon-cancel\" ui-grid-one-bind-aria-label=\"aria.removeFilter\"> </i></div></div></div>"
28705 $templateCache.put('ui-grid/ui-grid-footer',
28706 "<div class=\"ui-grid-footer-panel ui-grid-footer-aggregates-row\"><!-- tfooter --><div class=\"ui-grid-footer ui-grid-footer-viewport\"><div class=\"ui-grid-footer-canvas\"><div class=\"ui-grid-footer-cell-wrapper\" ng-style=\"colContainer.headerCellWrapperStyle()\"><div role=\"row\" class=\"ui-grid-footer-cell-row\"><div ui-grid-footer-cell role=\"gridcell\" ng-repeat=\"col in colContainer.renderedColumns track by col.uid\" col=\"col\" render-index=\"$index\" class=\"ui-grid-footer-cell ui-grid-clearfix\"></div></div></div></div></div></div>"
28710 $templateCache.put('ui-grid/ui-grid-grid-footer',
28711 "<div class=\"ui-grid-footer-info ui-grid-grid-footer\"><span>{{'search.totalItems' | t}} {{grid.rows.length}}</span> <span ng-if=\"grid.renderContainers.body.visibleRowCache.length !== grid.rows.length\" class=\"ngLabel\">({{\"search.showingItems\" | t}} {{grid.renderContainers.body.visibleRowCache.length}})</span></div>"
28715 $templateCache.put('ui-grid/ui-grid-group-panel',
28716 "<div class=\"ui-grid-group-panel\"><div ui-t=\"groupPanel.description\" class=\"description\" ng-show=\"groupings.length == 0\"></div><ul ng-show=\"groupings.length > 0\" class=\"ngGroupList\"><li class=\"ngGroupItem\" ng-repeat=\"group in configGroups\"><span class=\"ngGroupElement\"><span class=\"ngGroupName\">{{group.displayName}} <span ng-click=\"removeGroup($index)\" class=\"ngRemoveGroup\">x</span></span> <span ng-hide=\"$last\" class=\"ngGroupArrow\"></span></span></li></ul></div>"
28720 $templateCache.put('ui-grid/ui-grid-header',
28721 "<div role=\"rowgroup\" class=\"ui-grid-header\"><!-- theader --><div class=\"ui-grid-top-panel\"><div class=\"ui-grid-header-viewport\"><div class=\"ui-grid-header-canvas\"><div class=\"ui-grid-header-cell-wrapper\" ng-style=\"colContainer.headerCellWrapperStyle()\"><div role=\"row\" class=\"ui-grid-header-cell-row\"><div class=\"ui-grid-header-cell ui-grid-clearfix\" ng-repeat=\"col in colContainer.renderedColumns track by col.uid\" ui-grid-header-cell col=\"col\" render-index=\"$index\"></div></div></div></div></div></div></div>"
28725 $templateCache.put('ui-grid/ui-grid-menu-button',
28726 "<div class=\"ui-grid-menu-button\"><div role=\"button\" ui-grid-one-bind-id-grid=\"'grid-menu'\" class=\"ui-grid-icon-container\" ng-click=\"toggleMenu()\" aria-haspopup=\"true\"><i class=\"ui-grid-icon-menu\" ui-grid-one-bind-aria-label=\"i18n.aria.buttonLabel\"> </i></div><div ui-grid-menu menu-items=\"menuItems\"></div></div>"
28730 $templateCache.put('ui-grid/ui-grid-no-header',
28731 "<div class=\"ui-grid-top-panel\"></div>"
28735 $templateCache.put('ui-grid/ui-grid-row',
28736 "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell></div>"
28740 $templateCache.put('ui-grid/ui-grid',
28741 "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
28742 " /* Styles for the grid */\n" +
28745 " .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
28746 " height: {{ grid.options.rowHeight }}px;\n" +
28749 " .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
28750 " border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
28753 " {{ grid.verticalScrollbarStyles }}\n" +
28754 " {{ grid.horizontalScrollbarStyles }}\n" +
28757 " .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
28758 " padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
28762 " {{ grid.customStyles }}</style><div class=\"ui-grid-contents-wrapper\"><div ui-grid-menu-button ng-if=\"grid.options.enableGridMenu\"></div><div ng-if=\"grid.hasLeftContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'left'\"></div><div ui-grid-render-container container-id=\"'body'\" col-container-name=\"'body'\" row-container-name=\"'body'\" bind-scroll-horizontal=\"true\" bind-scroll-vertical=\"true\" enable-horizontal-scrollbar=\"grid.options.enableHorizontalScrollbar\" enable-vertical-scrollbar=\"grid.options.enableVerticalScrollbar\"></div><div ng-if=\"grid.hasRightContainer()\" style=\"width: 0\" ui-grid-pinned-container=\"'right'\"></div><div ui-grid-grid-footer ng-if=\"grid.options.showGridFooter\"></div><div ui-grid-column-menu ng-if=\"grid.options.enableColumnMenus\"></div><div ng-transclude></div></div></div>"
28766 $templateCache.put('ui-grid/uiGridCell',
28767 "<div class=\"ui-grid-cell-contents\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
28771 $templateCache.put('ui-grid/uiGridColumnMenu',
28772 "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
28773 " <div class=\"inner\" ng-show=\"menuShown\">\n" +
28775 " <div ng-show=\"grid.options.enableSorting\">\n" +
28776 " <li ng-click=\"sortColumn($event, asc)\" ng-class=\"{ 'selected' : col.sort.direction == asc }\"><i class=\"ui-grid-icon-sort-alt-up\"></i> Sort Ascending</li>\n" +
28777 " <li ng-click=\"sortColumn($event, desc)\" ng-class=\"{ 'selected' : col.sort.direction == desc }\"><i class=\"ui-grid-icon-sort-alt-down\"></i> Sort Descending</li>\n" +
28778 " <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
28782 " </div> --></div></div>"
28786 $templateCache.put('ui-grid/uiGridFooterCell',
28787 "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationText() + ( col.getAggregationValue() CUSTOM_FILTERS ) }}</div></div>"
28791 $templateCache.put('ui-grid/uiGridHeaderCell',
28792 "<div role=\"columnheader\" ng-class=\"{ 'sortable': sortable }\" ui-grid-one-bind-aria-labelledby-grid=\"col.uid + '-header-text ' + col.uid + '-sortdir-text'\" aria-sort=\"{{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending' : (!col.sort.direction ? 'none' : 'other'))}}\"><div role=\"button\" tabindex=\"0\" class=\"ui-grid-cell-contents ui-grid-header-cell-primary-focus\" col-index=\"renderIndex\" title=\"TOOLTIP\"><span class=\"ui-grid-header-cell-label\" ui-grid-one-bind-id-grid=\"col.uid + '-header-text'\">{{ col.displayName CUSTOM_FILTERS }}</span> <span ui-grid-one-bind-id-grid=\"col.uid + '-sortdir-text'\" ui-grid-visible=\"col.sort.direction\" aria-label=\"{{getSortDirectionAriaLabel()}}\"><i ng-class=\"{ 'ui-grid-icon-up-dir': col.sort.direction == asc, 'ui-grid-icon-down-dir': col.sort.direction == desc, 'ui-grid-icon-blank': !col.sort.direction }\" title=\"{{isSortPriorityVisible() ? i18n.headerCell.priority + ' ' + ( col.sort.priority + 1 ) : null}}\" aria-hidden=\"true\"></i> <sub ui-grid-visible=\"isSortPriorityVisible()\" class=\"ui-grid-sort-priority-number\">{{col.sort.priority + 1}}</sub></span></div><div role=\"button\" tabindex=\"0\" ui-grid-one-bind-id-grid=\"col.uid + '-menu-button'\" class=\"ui-grid-column-menu-button\" ng-if=\"grid.options.enableColumnMenus && !col.isRowHeader && col.colDef.enableColumnMenu !== false\" ng-click=\"toggleMenu($event)\" ng-class=\"{'ui-grid-column-menu-button-last-col': isLastCol}\" ui-grid-one-bind-aria-label=\"i18n.headerCell.aria.columnMenuButtonLabel\" aria-haspopup=\"true\"><i class=\"ui-grid-icon-angle-down\" aria-hidden=\"true\"> </i></div><div ui-grid-filter></div></div>"
28796 $templateCache.put('ui-grid/uiGridMenu',
28797 "<div class=\"ui-grid-menu\" ng-if=\"shown\"><style ui-grid-style>{{dynamicStyles}}</style><div class=\"ui-grid-menu-mid\" ng-show=\"shownMid\"><div class=\"ui-grid-menu-inner\"><ul role=\"menu\" class=\"ui-grid-menu-items\"><li ng-repeat=\"item in menuItems\" role=\"menuitem\" ui-grid-menu-item ui-grid-one-bind-id=\"'menuitem-'+$index\" action=\"item.action\" name=\"item.title\" active=\"item.active\" icon=\"item.icon\" shown=\"item.shown\" context=\"item.context\" template-url=\"item.templateUrl\" leave-open=\"item.leaveOpen\" screen-reader-only=\"item.screenReaderOnly\"></li></ul></div></div></div>"
28801 $templateCache.put('ui-grid/uiGridMenuItem',
28802 "<button type=\"button\" class=\"ui-grid-menu-item\" ng-click=\"itemAction($event, title)\" ng-show=\"itemShown()\" ng-class=\"{ 'ui-grid-menu-item-active': active(), 'ui-grid-sr-only': (!focus && screenReaderOnly) }\" aria-pressed=\"{{active()}}\" tabindex=\"0\" ng-focus=\"focus=true\" ng-blur=\"focus=false\"><i ng-class=\"icon\" aria-hidden=\"true\"> </i> {{ name }}</button>"
28806 $templateCache.put('ui-grid/uiGridRenderContainer',
28807 "<div role=\"grid\" ui-grid-one-bind-id-grid=\"'grid-container'\" class=\"ui-grid-render-container\" ng-style=\"{ 'margin-left': colContainer.getMargin('left') + 'px', 'margin-right': colContainer.getMargin('right') + 'px' }\"><!-- All of these dom elements are replaced in place --><div ui-grid-header></div><div ui-grid-viewport></div><div ng-if=\"colContainer.needsHScrollbarPlaceholder()\" class=\"ui-grid-scrollbar-placeholder\" ng-style=\"{height:colContainer.grid.scrollbarHeight + 'px'}\"></div><ui-grid-footer ng-if=\"grid.options.showColumnFooter\"></ui-grid-footer></div>"
28811 $templateCache.put('ui-grid/uiGridViewport',
28812 "<div role=\"rowgroup\" class=\"ui-grid-viewport\" ng-style=\"colContainer.getViewportStyle()\"><!-- tbody --><div class=\"ui-grid-canvas\"><div ng-repeat=\"(rowRenderIndex, row) in rowContainer.renderedRows track by $index\" class=\"ui-grid-row\" ng-style=\"Viewport.rowStyle(rowRenderIndex)\"><div role=\"row\" ui-grid-row=\"row\" row-render-index=\"rowRenderIndex\"></div></div></div></div>"
28816 $templateCache.put('ui-grid/cellEditor',
28817 "<div><form name=\"inputForm\"><input type=\"INPUT_TYPE\" ng-class=\"'colt' + col.uid\" ui-grid-editor ng-model=\"MODEL_COL_FIELD\"></form></div>"
28821 $templateCache.put('ui-grid/dropdownEditor',
28822 "<div><form name=\"inputForm\"><select ng-class=\"'colt' + col.uid\" ui-grid-edit-dropdown ng-model=\"MODEL_COL_FIELD\" ng-options=\"field[editDropdownIdLabel] as field[editDropdownValueLabel] CUSTOM_FILTERS for field in editDropdownOptionsArray\"></select></form></div>"
28826 $templateCache.put('ui-grid/fileChooserEditor',
28827 "<div><form name=\"inputForm\"><input ng-class=\"'colt' + col.uid\" ui-grid-edit-file-chooser type=\"file\" id=\"files\" name=\"files[]\" ng-model=\"MODEL_COL_FIELD\"></form></div>"
28831 $templateCache.put('ui-grid/emptyBaseLayerContainer',
28832 "<div class=\"ui-grid-empty-base-layer-container ui-grid-canvas\"><div class=\"ui-grid-row\" ng-repeat=\"(rowRenderIndex, row) in grid.baseLayer.emptyRows track by $index\" ng-style=\"Viewport.rowStyle(rowRenderIndex)\"><div><div><div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name\" class=\"ui-grid-cell {{ col.getColClass(false) }}\"></div></div></div></div></div>"
28836 $templateCache.put('ui-grid/expandableRow',
28837 "<div ui-grid-expandable-row ng-if=\"expandableRow.shouldRenderExpand()\" class=\"expandableRow\" style=\"float:left; margin-top: 1px; margin-bottom: 1px\" ng-style=\"{width: (grid.renderContainers.body.getCanvasWidth()) + 'px', height: row.expandedRowHeight + 'px'}\"></div>"
28841 $templateCache.put('ui-grid/expandableRowHeader',
28842 "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-if=\"!row.groupHeader==true\" ng-class=\"{ 'ui-grid-icon-plus-squared' : !row.isExpanded, 'ui-grid-icon-minus-squared' : row.isExpanded }\" ng-click=\"grid.api.expandable.toggleRowExpansion(row.entity)\"></i></div></div>"
28846 $templateCache.put('ui-grid/expandableScrollFiller',
28847 "<div ng-if=\"expandableRow.shouldRenderFiller()\" ng-class=\"{scrollFiller:true, scrollFillerClass:(colContainer.name === 'body')}\" ng-style=\"{ width: (grid.getViewportWidth()) + 'px', height: row.expandedRowHeight + 2 + 'px', 'margin-left': grid.options.rowHeader.rowHeaderWidth + 'px' }\"><i class=\"ui-grid-icon-spin5 ui-grid-animate-spin\" ng-style=\"{'margin-top': ( row.expandedRowHeight/2 - 5) + 'px', 'margin-left' : ((grid.getViewportWidth() - grid.options.rowHeader.rowHeaderWidth)/2 - 5) + 'px'}\"></i></div>"
28851 $templateCache.put('ui-grid/expandableTopRowHeader',
28852 "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-class=\"{ 'ui-grid-icon-plus-squared' : !grid.expandable.expandedAll, 'ui-grid-icon-minus-squared' : grid.expandable.expandedAll }\" ng-click=\"grid.api.expandable.toggleAllRows()\"></i></div></div>"
28856 $templateCache.put('ui-grid/csvLink',
28857 "<span class=\"ui-grid-exporter-csv-link-span\"><a href=\"data:text/csv;charset=UTF-8,CSV_CONTENT\" download=\"FILE_NAME\">LINK_LABEL</a></span>"
28861 $templateCache.put('ui-grid/importerMenuItem',
28862 "<li class=\"ui-grid-menu-item\"><form><input class=\"ui-grid-importer-file-chooser\" type=\"file\" id=\"files\" name=\"files[]\"></form></li>"
28866 $templateCache.put('ui-grid/importerMenuItemContainer',
28867 "<div ui-grid-importer-menu-item></div>"
28871 $templateCache.put('ui-grid/pagination',
28872 "<div role=\"contentinfo\" class=\"ui-grid-pager-panel\" ui-grid-pager ng-show=\"grid.options.enablePaginationControls\"><div role=\"navigation\" class=\"ui-grid-pager-container\"><div role=\"menubar\" class=\"ui-grid-pager-control\"><button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-first\" ui-grid-one-bind-title=\"aria.pageToFirst\" ui-grid-one-bind-aria-label=\"aria.pageToFirst\" ng-click=\"pageFirstPageClick()\" ng-disabled=\"cantPageBackward()\"><div ng-class=\"grid.isRTL() ? 'last-triangle' : 'first-triangle'\"><div ng-class=\"grid.isRTL() ? 'last-bar-rtl' : 'first-bar'\"></div></div></button> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-previous\" ui-grid-one-bind-title=\"aria.pageBack\" ui-grid-one-bind-aria-label=\"aria.pageBack\" ng-click=\"pagePreviousPageClick()\" ng-disabled=\"cantPageBackward()\"><div ng-class=\"grid.isRTL() ? 'last-triangle prev-triangle' : 'first-triangle prev-triangle'\"></div></button> <input type=\"number\" ui-grid-one-bind-title=\"aria.pageSelected\" ui-grid-one-bind-aria-label=\"aria.pageSelected\" class=\"ui-grid-pager-control-input\" ng-model=\"grid.options.paginationCurrentPage\" min=\"1\" max=\"{{ paginationApi.getTotalPages() }}\" required> <span class=\"ui-grid-pager-max-pages-number\" ng-show=\"paginationApi.getTotalPages() > 0\"><abbr ui-grid-one-bind-title=\"paginationOf\">/</abbr> {{ paginationApi.getTotalPages() }}</span> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-next\" ui-grid-one-bind-title=\"aria.pageForward\" ui-grid-one-bind-aria-label=\"aria.pageForward\" ng-click=\"pageNextPageClick()\" ng-disabled=\"cantPageForward()\"><div ng-class=\"grid.isRTL() ? 'first-triangle next-triangle' : 'last-triangle next-triangle'\"></div></button> <button type=\"button\" role=\"menuitem\" class=\"ui-grid-pager-last\" ui-grid-one-bind-title=\"aria.pageToLast\" ui-grid-one-bind-aria-label=\"aria.pageToLast\" ng-click=\"pageLastPageClick()\" ng-disabled=\"cantPageToLast()\"><div ng-class=\"grid.isRTL() ? 'first-triangle' : 'last-triangle'\"><div ng-class=\"grid.isRTL() ? 'first-bar-rtl' : 'last-bar'\"></div></div></button></div><div class=\"ui-grid-pager-row-count-picker\" ng-if=\"grid.options.paginationPageSizes.length > 1 && !grid.options.useCustomPagination\"><select ui-grid-one-bind-aria-labelledby-grid=\"'items-per-page-label'\" ng-model=\"grid.options.paginationPageSize\" ng-options=\"o as o for o in grid.options.paginationPageSizes\"></select><span ui-grid-one-bind-id-grid=\"'items-per-page-label'\" class=\"ui-grid-pager-row-count-label\"> {{sizesLabel}}</span></div><span ng-if=\"grid.options.paginationPageSizes.length <= 1\" class=\"ui-grid-pager-row-count-label\">{{grid.options.paginationPageSize}} {{sizesLabel}}</span></div><div class=\"ui-grid-pager-count-container\"><div class=\"ui-grid-pager-count\"><span ng-show=\"grid.options.totalItems > 0\">{{ 1 + paginationApi.getFirstRowIndex() }} <abbr ui-grid-one-bind-title=\"paginationThrough\">-</abbr> {{ 1 + paginationApi.getLastRowIndex() }} {{paginationOf}} {{grid.options.totalItems}} {{totalItemsLabel}}</span></div></div></div>"
28876 $templateCache.put('ui-grid/columnResizer',
28877 "<div ui-grid-column-resizer ng-if=\"grid.options.enableColumnResizing\" class=\"ui-grid-column-resizer\" col=\"col\" position=\"right\" render-index=\"renderIndex\" unselectable=\"on\"></div>"
28881 $templateCache.put('ui-grid/gridFooterSelectedItems',
28882 "<span ng-if=\"grid.selection.selectedCount !== 0 && grid.options.enableFooterTotalSelected\">({{\"search.selectedItems\" | t}} {{grid.selection.selectedCount}})</span>"
28886 $templateCache.put('ui-grid/selectionHeaderCell',
28887 "<div><!-- <div class=\"ui-grid-vertical-bar\"> </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>"
28891 $templateCache.put('ui-grid/selectionRowHeader',
28892 "<div class=\"ui-grid-disable-selection\"><div class=\"ui-grid-cell-contents\"><ui-grid-selection-row-header-buttons></ui-grid-selection-row-header-buttons></div></div>"
28896 $templateCache.put('ui-grid/selectionRowHeaderButtons',
28897 "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-row-selected': row.isSelected}\" ng-click=\"selectButtonClick(row, $event)\"> </div>"
28901 $templateCache.put('ui-grid/selectionSelectAllButtons',
28902 "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-all-selected': grid.selection.selectAll}\" ng-click=\"headerButtonClick($event)\"></div>"
28906 $templateCache.put('ui-grid/treeBaseExpandAllButtons',
28907 "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"{'ui-grid-icon-minus-squared': grid.treeBase.numberLevels > 0 && grid.treeBase.expandAll, 'ui-grid-icon-plus-squared': grid.treeBase.numberLevels > 0 && !grid.treeBase.expandAll}\" ng-click=\"headerButtonClick($event)\"></div>"
28911 $templateCache.put('ui-grid/treeBaseHeaderCell',
28912 "<div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><ui-grid-tree-base-expand-all-buttons ng-if=\"grid.options.enableExpandAll\"></ui-grid-tree-base-expand-all-buttons></div></div>"
28916 $templateCache.put('ui-grid/treeBaseRowHeader',
28917 "<div class=\"ui-grid-cell-contents\"><ui-grid-tree-base-row-header-buttons></ui-grid-tree-base-row-header-buttons></div>"
28921 $templateCache.put('ui-grid/treeBaseRowHeaderButtons',
28922 "<div class=\"ui-grid-tree-base-row-header-buttons\" ng-class=\"{'ui-grid-tree-base-header': row.treeLevel > -1 }\" ng-click=\"treeButtonClick(row, $event)\"><i ng-class=\"{'ui-grid-icon-minus-squared': ( ( grid.options.showTreeExpandNoChildren && row.treeLevel > -1 ) || ( row.treeNode.children && row.treeNode.children.length > 0 ) ) && row.treeNode.state === 'expanded', 'ui-grid-icon-plus-squared': ( ( grid.options.showTreeExpandNoChildren && row.treeLevel > -1 ) || ( row.treeNode.children && row.treeNode.children.length > 0 ) ) && row.treeNode.state === 'collapsed'}\" ng-style=\"{'padding-left': grid.options.treeIndent * row.treeLevel + 'px'}\"></i> </div>"
28926 $templateCache.put('ui-grid/cellTitleValidator',
28927 "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" title=\"{{grid.validate.getTitleFormattedErrors(row.entity,col.colDef)}}\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
28931 $templateCache.put('ui-grid/cellTooltipValidator',
28932 "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" tooltip-html-unsafe=\"{{grid.validate.getFormattedErrors(row.entity,col.colDef)}}\" tooltip-enable=\"grid.validate.isInvalid(row.entity,col.colDef)\" tooltip-append-to-body=\"true\" tooltip-placement=\"top\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>"