1 /*! ui-grid - v3.0.0-rc.16-234dd76 - 2014-11-22
2 * Copyright (c) 2014 ; License: MIT */
5 angular.module('ui.grid.i18n', []);
6 angular.module('ui.grid', ['ui.grid.i18n']);
10 angular.module('ui.grid').constant('uiGridConstants', {
11 LOG_DEBUG_MESSAGES: true,
12 LOG_WARN_MESSAGES: true,
13 LOG_ERROR_MESSAGES: true,
14 CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
15 COL_FIELD: /COL_FIELD/g,
16 MODEL_COL_FIELD: /MODEL_COL_FIELD/g,
17 DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
18 TEMPLATE_REGEXP: /<.+>/,
19 FUNC_REGEXP: /(\([^)]*\))?$/,
22 BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
23 COL_CLASS_PREFIX: 'ui-grid-col',
25 GRID_SCROLL: 'uiGridScroll',
26 COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
27 ITEM_DRAGGING: 'uiGridItemDragStart' // For any item being dragged
29 // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
75 GREATER_THAN_OR_EQUAL: 64,
77 LESS_THAN_OR_EQUAL: 256,
89 // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them?
90 CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'],
106 angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
111 compile: function() {
113 pre: function($scope, $elm, $attrs, uiGridCtrl) {
114 function compileTemplate() {
115 var compiledElementFn = $scope.col.compiledElementFn;
117 compiledElementFn($scope, function(clonedElement, scope) {
118 $elm.append(clonedElement);
122 // If the grid controller is present, use it to get the compiled cell template function
123 if (uiGridCtrl && $scope.col.compiledElementFn) {
126 // No controller, compile the element manually (for unit tests)
128 if ( uiGridCtrl && !$scope.col.compiledElementFn ){
129 // gridUtil.logError('Render has been called before precompile. Please log a ui-grid issue');
131 $scope.col.getCompiledElementFn()
132 .then(function (compiledElementFn) {
133 compiledElementFn($scope, function(clonedElement, scope) {
134 $elm.append(clonedElement);
139 var html = $scope.col.cellTemplate
140 .replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
141 .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
143 var cellElement = $compile(html)($scope);
144 $elm.append(cellElement);
148 post: function($scope, $elm, $attrs, uiGridCtrl) {
149 $elm.addClass($scope.col.getColClass(false));
152 var updateClass = function( grid ){
155 contents.removeClass( classAdded );
159 if (angular.isFunction($scope.col.cellClass)) {
160 classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
163 classAdded = $scope.col.cellClass;
165 contents.addClass(classAdded);
168 if ($scope.col.cellClass) {
172 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
173 var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
175 var deregisterFunction = function() {
176 $scope.grid.deregisterDataChangeCallback( watchUid );
179 $scope.$on( '$destroy', deregisterFunction );
191 angular.module('ui.grid')
192 .service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil',
193 function ( i18nService, uiGridConstants, gridUtil ) {
196 * @name ui.grid.service:uiGridColumnMenuService
198 * @description Services for working with column menus, factored out
199 * to make the code easier to understand
205 * @methodOf ui.grid.service:uiGridColumnMenuService
207 * @description Sets defaults, puts a reference to the $scope on
208 * the uiGridController
209 * @param {$scope} $scope the $scope from the uiGridColumnMenu
210 * @param {controller} uiGridCtrl the uiGridController for the grid
214 initialize: function( $scope, uiGridCtrl ){
215 $scope.grid = uiGridCtrl.grid;
217 // Store a reference to this link/controller in the main uiGrid controller
218 // to allow showMenu later
219 uiGridCtrl.columnMenuScope = $scope;
221 // Save whether we're shown or not so the columns can check
222 $scope.menuShown = false;
228 * @methodOf ui.grid.service:uiGridColumnMenuService
229 * @name setColMenuItemWatch
230 * @description Setup a watch on $scope.col.menuItems, and update
231 * menuItems based on this. $scope.col needs to be set by the column
232 * before calling the menu.
233 * @param {$scope} $scope the $scope from the uiGridColumnMenu
234 * @param {controller} uiGridCtrl the uiGridController for the grid
238 setColMenuItemWatch: function ( $scope ){
239 var deregFunction = $scope.$watch('col.menuItems', function (n, o) {
240 if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
241 n.forEach(function (item) {
242 if (typeof(item.context) === 'undefined' || !item.context) {
245 item.context.col = $scope.col;
248 $scope.menuItems = $scope.defaultMenuItems.concat(n);
251 $scope.menuItems = $scope.defaultMenuItems;
255 $scope.$on( '$destroy', deregFunction );
261 * @name enableSorting
262 * @propertyOf ui.grid.class:GridOptions.columnDef
263 * @description (optional) True by default. When enabled, this setting adds sort
264 * widgets to the column header, allowing sorting of the data in the individual column.
268 * @methodOf ui.grid.service:uiGridColumnMenuService
270 * @description determines whether this column is sortable
271 * @param {$scope} $scope the $scope from the uiGridColumnMenu
274 sortable: function( $scope ) {
275 if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
285 * @methodOf ui.grid.service:uiGridColumnMenuService
287 * @description determines whether the requested sort direction is current active, to
288 * allow highlighting in the menu
289 * @param {$scope} $scope the $scope from the uiGridColumnMenu
290 * @param {string} direction the direction that we'd have selected for us to be active
293 isActiveSort: function( $scope, direction ){
294 return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
295 typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
301 * @name suppressRemoveSort
302 * @propertyOf ui.grid.class:GridOptions.columnDef
303 * @description (optional) False by default. When enabled, this setting hides the removeSort option
308 * @methodOf ui.grid.service:uiGridColumnMenuService
309 * @name suppressRemoveSort
310 * @description determines whether we should suppress the removeSort option
311 * @param {$scope} $scope the $scope from the uiGridColumnMenu
314 suppressRemoveSort: function( $scope ) {
315 if ($scope.col && $scope.col.colDef && $scope.col.colDef.suppressRemoveSort) {
327 * @propertyOf ui.grid.class:GridOptions.columnDef
328 * @description (optional) True by default. When set to false, this setting prevents a user from hiding the column
329 * using the column menu or the grid menu.
333 * @methodOf ui.grid.service:uiGridColumnMenuService
335 * @description determines whether a column can be hidden, by checking the enableHiding columnDef option
336 * @param {$scope} $scope the $scope from the uiGridColumnMenu
339 hideable: function( $scope ) {
340 if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
351 * @methodOf ui.grid.service:uiGridColumnMenuService
352 * @name getDefaultMenuItems
353 * @description returns the default menu items for a column menu
354 * @param {$scope} $scope the $scope from the uiGridColumnMenu
357 getDefaultMenuItems: function( $scope ){
360 title: i18nService.getSafeText('sort.ascending'),
361 icon: 'ui-grid-icon-sort-alt-up',
362 action: function($event) {
363 $event.stopPropagation();
364 $scope.sortColumn($event, uiGridConstants.ASC);
367 return service.sortable( $scope );
370 return service.isActiveSort( $scope, uiGridConstants.ASC);
374 title: i18nService.getSafeText('sort.descending'),
375 icon: 'ui-grid-icon-sort-alt-down',
376 action: function($event) {
377 $event.stopPropagation();
378 $scope.sortColumn($event, uiGridConstants.DESC);
381 return service.sortable( $scope );
384 return service.isActiveSort( $scope, uiGridConstants.DESC);
388 title: i18nService.getSafeText('sort.remove'),
389 icon: 'ui-grid-icon-cancel',
390 action: function ($event) {
391 $event.stopPropagation();
392 $scope.unsortColumn();
395 return service.sortable( $scope ) &&
396 typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' &&
397 typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null &&
398 !service.suppressRemoveSort( $scope );
402 title: i18nService.getSafeText('column.hide'),
403 icon: 'ui-grid-icon-cancel',
405 return service.hideable( $scope );
407 action: function ($event) {
408 $event.stopPropagation();
418 * @methodOf ui.grid.service:uiGridColumnMenuService
419 * @name getColumnElementPosition
420 * @description gets the position information needed to place the column
421 * menu below the column header
422 * @param {$scope} $scope the $scope from the uiGridColumnMenu
423 * @param {GridCol} column the column we want to position below
424 * @param {element} $columnElement the column element we want to position below
425 * @returns {hash} containing left, top, offset, height, width
428 getColumnElementPosition: function( $scope, column, $columnElement ){
429 var positionData = {};
430 positionData.left = $columnElement[0].offsetLeft;
431 positionData.top = $columnElement[0].offsetTop;
433 // Get the grid scrollLeft
434 positionData.offset = 0;
435 if (column.grid.options.offsetLeft) {
436 positionData.offset = column.grid.options.offsetLeft;
439 positionData.height = gridUtil.elementHeight($columnElement, true);
440 positionData.width = gridUtil.elementWidth($columnElement, true);
448 * @methodOf ui.grid.service:uiGridColumnMenuService
449 * @name repositionMenu
450 * @description Reposition the menu below the new column. If the menu has no child nodes
451 * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
453 * @param {$scope} $scope the $scope from the uiGridColumnMenu
454 * @param {GridCol} column the column we want to position below
455 * @param {hash} positionData a hash containing left, top, offset, height, width
456 * @param {element} $elm the column menu element that we want to reposition
457 * @param {element} $columnElement the column element that we want to reposition underneath
460 repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
461 var menu = $elm[0].querySelectorAll('.ui-grid-menu');
462 var containerId = column.renderContainer ? column.renderContainer : 'body';
463 var renderContainer = column.grid.renderContainers[containerId];
465 // It's possible that the render container of the column we're attaching to is
466 // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
467 // between the render container and the grid
468 var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
469 var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
471 var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
473 // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
474 var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
475 var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
477 if ( menu.length !== 0 ){
478 var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
479 if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
480 myWidth = gridUtil.elementWidth(menu, true);
481 $scope.lastMenuWidth = myWidth;
482 column.lastMenuWidth = myWidth;
484 // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
485 // Get the column menu right padding
486 paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
487 $scope.lastMenuPaddingRight = paddingRight;
488 column.lastMenuPaddingRight = paddingRight;
492 var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.width - myWidth + paddingRight;
493 if (left < positionData.offset){
494 left = positionData.offset;
497 $elm.css('left', left + 'px');
498 $elm.css('top', (positionData.top + positionData.height) + 'px');
507 .directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService',
508 function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) {
511 * @name ui.grid.directive:uiGridColumnMenu
512 * @description Provides the column menu framework, leverages uiGridMenu underneath
516 var uiGridColumnMenu = {
520 templateUrl: 'ui-grid/uiGridColumnMenu',
522 link: function ($scope, $elm, $attrs, uiGridCtrl) {
525 uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
527 $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
529 // Set the menu items for use with the column menu. The user can later add additional items via the watch
530 $scope.menuItems = $scope.defaultMenuItems;
531 uiGridColumnMenuService.setColMenuItemWatch( $scope );
536 * @methodOf ui.grid.directive:uiGridColumnMenu
538 * @description Shows the column menu. If the menu is already displayed it
539 * calls the menu to ask it to hide (it will animate), then it repositions the menu
540 * to the right place whilst hidden (it will make an assumption on menu width),
541 * then it asks the menu to show (it will animate), then it repositions the menu again
542 * once we can calculate it's size.
543 * @param {GridCol} column the column we want to position below
544 * @param {element} $columnElement the column element we want to position below
546 $scope.showMenu = function(column, $columnElement, event) {
547 // Swap to this column
550 // Get the position information for the column element
551 var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
553 if ($scope.menuShown) {
554 // we want to hide, then reposition, then show, but we want to wait for animations
555 // we set a variable, and then rely on the menu-hidden event to call the reposition and show
556 $scope.colElement = $columnElement;
557 $scope.colElementPosition = colElementPosition;
558 $scope.hideThenShow = true;
560 $scope.$broadcast('hide-menu', { originalEvent: event });
562 self.shown = $scope.menuShown = true;
563 uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
565 $scope.colElement = $columnElement;
566 $scope.colElementPosition = colElementPosition;
567 $scope.$broadcast('show-menu', { originalEvent: event });
575 * @methodOf ui.grid.directive:uiGridColumnMenu
577 * @description Hides the column menu.
578 * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
579 * from the menu itself - in which case don't broadcast again as we'll get
582 $scope.hideMenu = function( broadcastTrigger ) {
583 // delete $scope.col;
584 $scope.menuShown = false;
586 if ( !broadcastTrigger ){
587 $scope.$broadcast('hide-menu');
592 $scope.$on('menu-hidden', function() {
593 if ( $scope.hideThenShow ){
594 delete $scope.hideThenShow;
596 uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
597 $scope.$broadcast('show-menu');
599 $scope.menuShown = true;
601 $scope.hideMenu( true );
605 $scope.$on('menu-shown', function() {
606 $timeout( function() {
607 uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
608 delete $scope.colElementPosition;
609 delete $scope.columnElement;
615 $scope.sortColumn = function (event, dir) {
616 event.stopPropagation();
618 $scope.grid.sortColumn($scope.col, dir, true)
620 $scope.grid.refresh();
625 $scope.unsortColumn = function () {
628 $scope.grid.refresh();
632 $scope.hideColumn = function () {
633 $scope.col.colDef.visible = false;
635 $scope.grid.refresh();
642 controller: ['$scope', function ($scope) {
645 $scope.$watch('menuItems', function (n, o) {
651 return uiGridColumnMenu;
659 angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
660 function ($timeout, gridUtil, uiGridConstants, $compile) {
661 var uiGridFooterCell = {
670 compile: function compile(tElement, tAttrs, transclude) {
672 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
673 function compileTemplate(template) {
674 gridUtil.getTemplate(template).then(function (contents) {
675 var linkFunction = $compile(contents);
676 var html = linkFunction($scope);
681 //compile the footer template
682 if ($scope.col.footerCellTemplate) {
683 //compile the custom template
684 compileTemplate($scope.col.footerCellTemplate);
687 //use default template
688 compileTemplate('ui-grid/uiGridFooterCell');
691 post: function ($scope, $elm, $attrs, uiGridCtrl) {
692 //$elm.addClass($scope.col.getColClass(false));
693 $scope.grid = uiGridCtrl.grid;
694 $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
696 $elm.addClass($scope.col.getColClass(false));
698 // apply any footerCellClass
700 var updateClass = function( grid ){
703 contents.removeClass( classAdded );
707 if (angular.isFunction($scope.col.footerCellClass)) {
708 classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
711 classAdded = $scope.col.footerCellClass;
713 contents.addClass(classAdded);
716 if ($scope.col.footerCellClass) {
720 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
721 var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
723 $scope.$on( '$destroy', function() {
724 $scope.grid.deregisterDataChangeCallback( watchUid );
731 return uiGridFooterCell;
739 angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
740 var defaultTemplate = 'ui-grid/ui-grid-footer';
746 require: ['^uiGrid', '^uiGridRenderContainer'],
748 compile: function ($elm, $attrs) {
750 pre: function ($scope, $elm, $attrs, controllers) {
751 var uiGridCtrl = controllers[0];
752 var containerCtrl = controllers[1];
754 $scope.grid = uiGridCtrl.grid;
755 $scope.colContainer = containerCtrl.colContainer;
756 $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
758 containerCtrl.footer = $elm;
760 var footerTemplate = ($scope.grid.options.footerTemplate) ? $scope.grid.options.footerTemplate : defaultTemplate;
761 gridUtil.getTemplate(footerTemplate)
762 .then(function (contents) {
763 var template = angular.element(contents);
765 var newElm = $compile(template)($scope);
769 // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
770 var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
772 if (footerViewport) {
773 containerCtrl.footerViewport = footerViewport;
779 post: function ($scope, $elm, $attrs, controllers) {
780 var uiGridCtrl = controllers[0];
781 var containerCtrl = controllers[1];
783 // gridUtil.logDebug('ui-grid-footer link');
785 var grid = uiGridCtrl.grid;
787 // Don't animate footer cells
788 gridUtil.disableAnimations($elm);
790 containerCtrl.footer = $elm;
792 var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
793 if (footerViewport) {
794 containerCtrl.footerViewport = footerViewport;
806 angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
807 var defaultTemplate = 'ui-grid/ui-grid-group-panel';
814 compile: function($elm, $attrs) {
816 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
817 var groupPanelTemplate = $scope.grid.options.groupPanelTemplate || defaultTemplate;
819 gridUtil.getTemplate(groupPanelTemplate)
820 .then(function (contents) {
821 var template = angular.element(contents);
823 var newElm = $compile(template)($scope);
828 post: function ($scope, $elm, $attrs, uiGridCtrl) {
829 $elm.bind('$destroy', function() {
842 angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants',
843 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
844 // Do stuff after mouse has been down this many ms on the header cell
845 var mousedownTimeout = 500;
847 var uiGridHeaderCell = {
854 require: ['?^uiGrid', '^uiGridRenderContainer'],
856 compile: function() {
858 pre: function ($scope, $elm, $attrs) {
859 var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
860 $elm.append(cellHeader);
863 post: function ($scope, $elm, $attrs, controllers) {
864 var uiGridCtrl = controllers[0];
865 var renderContainerCtrl = controllers[1];
867 $scope.grid = uiGridCtrl.grid;
868 $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
870 $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
872 $elm.addClass($scope.col.getColClass(false));
874 // Hide the menu by default
875 $scope.menuShown = false;
877 // Put asc and desc sort directions in scope
878 $scope.asc = uiGridConstants.ASC;
879 $scope.desc = uiGridConstants.DESC;
881 // Store a reference to menu element
882 var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
884 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
887 // apply any headerCellClass
889 var updateClass = function( grid ){
892 contents.removeClass( classAdded );
896 if (angular.isFunction($scope.col.headerCellClass)) {
897 classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
900 classAdded = $scope.col.headerCellClass;
902 contents.addClass(classAdded);
905 if ($scope.col.headerCellClass) {
909 // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
910 var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
912 var deregisterFunction = function() {
913 $scope.grid.deregisterDataChangeCallback( watchUid );
916 $scope.$on( '$destroy', deregisterFunction );
919 // Figure out whether this column is sortable or not
920 if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
921 $scope.sortable = true;
924 $scope.sortable = false;
927 if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
928 $scope.filterable = true;
931 $scope.filterable = false;
934 function handleClick(evt) {
935 // If the shift key is being held down, add this column to the sort
941 // Sort this column then rebuild the grid's rows
942 uiGridCtrl.grid.sortColumn($scope.col, add)
944 if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
945 uiGridCtrl.grid.refresh();
951 * @name enableColumnMenu
952 * @propertyOf ui.grid.class:GridOptions.columnDef
953 * @description if column menus are enabled, controls the column menus for this specific
954 * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
955 * using this option. If gridOptions.enableColumnMenus === false then you get no column
956 * menus irrespective of the value of this option ). Defaults to true.
961 * @name enableColumnMenus
962 * @propertyOf ui.grid.class:GridOptions.columnDef
963 * @description Override for column menus everywhere - if set to false then you get no
964 * column menus. Defaults to true.
968 // Long-click (for mobile)
969 var cancelMousedownTimeout;
970 var mousedownStartTime = 0;
971 $contentsElm.on('mousedown touchstart', function(event) {
972 if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
973 event = event.originalEvent;
976 // Don't show the menu if it's not the left button
977 if (event.button && event.button !== 0) {
981 mousedownStartTime = (new Date()).getTime();
983 cancelMousedownTimeout = $timeout(function() { }, mousedownTimeout);
985 cancelMousedownTimeout.then(function () {
986 if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
987 $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false) {
988 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
993 $contentsElm.on('mouseup touchend', function () {
994 $timeout.cancel(cancelMousedownTimeout);
997 $scope.$on('$destroy', function () {
998 $contentsElm.off('mousedown touchstart');
1002 $scope.toggleMenu = function($event) {
1003 $event.stopPropagation();
1005 // If the menu is already showing...
1006 if (uiGridCtrl.columnMenuScope.menuShown) {
1007 // ... and we're the column the menu is on...
1008 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
1010 uiGridCtrl.columnMenuScope.hideMenu();
1012 // ... and we're NOT the column the menu is on
1014 // ... move the menu to our column
1015 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1018 // If the menu is NOT showing
1020 // ... show it on our column
1021 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
1025 // If this column is sortable, add a click event handler
1026 if ($scope.sortable) {
1027 $contentsElm.on('click touchend', function(evt) {
1028 evt.stopPropagation();
1030 $timeout.cancel(cancelMousedownTimeout);
1032 var mousedownEndTime = (new Date()).getTime();
1033 var mousedownTime = mousedownEndTime - mousedownStartTime;
1035 if (mousedownTime > mousedownTimeout) {
1036 // long click, handled above with mousedown
1044 $scope.$on('$destroy', function () {
1045 // Cancel any pending long-click timeout
1046 $timeout.cancel(cancelMousedownTimeout);
1050 if ($scope.filterable) {
1051 var filterDeregisters = [];
1052 angular.forEach($scope.col.filters, function(filter, i) {
1053 filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
1055 uiGridCtrl.grid.api.core.raise.filterChanged();
1056 uiGridCtrl.grid.refresh()
1058 if (uiGridCtrl.prevScrollArgs && uiGridCtrl.prevScrollArgs.y && uiGridCtrl.prevScrollArgs.y.percentage) {
1059 uiGridCtrl.fireScrollingEvent({ y: { percentage: uiGridCtrl.prevScrollArgs.y.percentage } });
1061 // uiGridCtrl.fireEvent('force-vertical-scroll');
1066 $scope.$on('$destroy', function() {
1067 angular.forEach(filterDeregisters, function(filterDeregister) {
1077 return uiGridHeaderCell;
1085 angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
1086 var defaultTemplate = 'ui-grid/ui-grid-header';
1087 var emptyTemplate = 'ui-grid/ui-grid-no-header';
1091 // templateUrl: 'ui-grid/ui-grid-header',
1094 require: ['^uiGrid', '^uiGridRenderContainer'],
1096 compile: function($elm, $attrs) {
1098 pre: function ($scope, $elm, $attrs, controllers) {
1099 var uiGridCtrl = controllers[0];
1100 var containerCtrl = controllers[1];
1102 $scope.grid = uiGridCtrl.grid;
1103 $scope.colContainer = containerCtrl.colContainer;
1104 $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
1106 containerCtrl.header = $elm;
1107 containerCtrl.colContainer.header = $elm;
1112 * @propertyOf ui.grid.class:GridOptions
1113 * @description Null by default. When set to true, this setting will replace the
1114 * standard header template with '<div></div>', resulting in no header being shown.
1118 if ($scope.grid.options.hideHeader){
1119 headerTemplate = emptyTemplate;
1121 headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;
1124 gridUtil.getTemplate(headerTemplate)
1125 .then(function (contents) {
1126 var template = angular.element(contents);
1128 var newElm = $compile(template)($scope);
1129 $elm.replaceWith(newElm);
1131 // Replace the reference to the container's header element with this new element
1132 containerCtrl.header = newElm;
1133 containerCtrl.colContainer.header = newElm;
1135 // And update $elm to be the new element
1138 if (containerCtrl) {
1139 // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
1140 var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1142 if (headerViewport) {
1143 containerCtrl.headerViewport = headerViewport;
1149 post: function ($scope, $elm, $attrs, controllers) {
1150 var uiGridCtrl = controllers[0];
1151 var containerCtrl = controllers[1];
1153 // gridUtil.logDebug('ui-grid-header link');
1155 var grid = uiGridCtrl.grid;
1157 // Don't animate header cells
1158 gridUtil.disableAnimations($elm);
1160 function updateColumnWidths() {
1161 // Get the width of the viewport
1162 var availableWidth = containerCtrl.colContainer.getViewportWidth();
1164 if (typeof(uiGridCtrl.grid.verticalScrollbarWidth) !== 'undefined' && uiGridCtrl.grid.verticalScrollbarWidth !== undefined && uiGridCtrl.grid.verticalScrollbarWidth > 0) {
1165 availableWidth = availableWidth + uiGridCtrl.grid.verticalScrollbarWidth;
1168 // The total number of columns
1169 // var equalWidthColumnCount = columnCount = uiGridCtrl.grid.options.columnDefs.length;
1170 // var equalWidth = availableWidth / equalWidthColumnCount;
1172 var columnCache = containerCtrl.colContainer.visibleColumnCache,
1176 leftoverWidth = availableWidth,
1177 hasVariableWidth = false;
1179 var getColWidth = function(column){
1180 if (column.widthType === "manual"){
1181 return +column.width;
1183 else if (column.widthType === "percent"){
1184 return parseInt(column.width.replace(/%/g, ''), 10) * availableWidth / 100;
1186 else if (column.widthType === "auto"){
1187 // leftOverWidth is subtracted from after each call to this
1188 // function so we need to calculate oneAsterisk size only once
1189 if (oneAsterisk === 0) {
1190 oneAsterisk = parseInt(leftoverWidth / asteriskNum, 10);
1192 return column.width.length * oneAsterisk;
1196 // Populate / determine column width types:
1197 columnCache.forEach(function(column){
1198 column.widthType = null;
1199 if (isFinite(+column.width)){
1200 column.widthType = "manual";
1202 else if (gridUtil.endsWith(column.width, "%")){
1203 column.widthType = "percent";
1204 hasVariableWidth = true;
1206 else if (angular.isString(column.width) && column.width.indexOf('*') !== -1){
1207 column.widthType = "auto";
1208 asteriskNum += column.width.length;
1209 hasVariableWidth = true;
1213 // For sorting, calculate width from first to last:
1214 var colWidthPriority = ["manual", "percent", "auto"];
1215 columnCache.filter(function(column){
1216 // Only draw visible items with a widthType
1217 return (column.visible && column.widthType);
1218 }).sort(function(a,b){
1219 // Calculate widths in order, so that manual comes first, etc.
1220 return colWidthPriority.indexOf(a.widthType) - colWidthPriority.indexOf(b.widthType);
1221 }).forEach(function(column){
1222 // Calculate widths:
1223 var colWidth = getColWidth(column);
1224 if (column.minWidth){
1225 colWidth = Math.max(colWidth, column.minWidth);
1227 if (column.maxWidth){
1228 colWidth = Math.min(colWidth, column.maxWidth);
1230 column.drawnWidth = Math.floor(colWidth);
1231 canvasWidth += column.drawnWidth;
1232 leftoverWidth -= column.drawnWidth;
1235 // If the grid width didn't divide evenly into the column widths and we have pixels left over, dole them out to the columns one by one to make everything fit
1236 if (hasVariableWidth && leftoverWidth > 0 && canvasWidth > 0 && canvasWidth < availableWidth) {
1237 var remFn = function (column) {
1238 if (leftoverWidth > 0 && (column.widthType === "auto" || column.widthType === "percent")) {
1239 column.drawnWidth = column.drawnWidth + 1;
1240 canvasWidth = canvasWidth + 1;
1244 var prevLeftover = 0;
1246 prevLeftover = leftoverWidth;
1247 columnCache.forEach(remFn);
1248 } while (leftoverWidth > 0 && leftoverWidth !== prevLeftover );
1250 canvasWidth = Math.max(canvasWidth, availableWidth);
1253 // uiGridCtrl.grid.columns.forEach(function (column) {
1255 columnCache.forEach(function (column) {
1256 ret = ret + column.getColClassDefinition();
1259 // Add the vertical scrollbar width back in to the canvas width, it's taken out in getViewportWidth
1260 if (grid.verticalScrollbarWidth) {
1261 canvasWidth = canvasWidth + grid.verticalScrollbarWidth;
1263 // canvasWidth = canvasWidth + 1;
1265 // if we have a grid menu, then we prune the width of the last column header
1266 // to allow room for the button whilst still getting to the column menu
1267 if (columnCache.length > 0) { // && grid.options.enableGridMenu) {
1268 columnCache[columnCache.length - 1].headerWidth = columnCache[columnCache.length - 1].drawnWidth - 30;
1271 containerCtrl.colContainer.canvasWidth = parseInt(canvasWidth, 10);
1273 // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1277 containerCtrl.header = $elm;
1279 var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1280 if (headerViewport) {
1281 containerCtrl.headerViewport = headerViewport;
1284 //todo: remove this if by injecting gridCtrl into unit tests
1286 uiGridCtrl.grid.registerStyleComputation({
1288 func: updateColumnWidths
1301 angular.module('ui.grid')
1302 .service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', function( gridUtil, i18nService ) {
1305 * @name ui.grid.gridMenuService
1307 * @description Methods for working with the grid menu
1313 * @methodOf ui.grid.gridMenuService
1315 * @description Sets up the gridMenu. Most importantly, sets our
1316 * scope onto the grid object as grid.gridMenuScope, allowing us
1317 * to operate when passed only the grid. Second most importantly,
1318 * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
1320 * @param {$scope} $scope the scope of this gridMenu
1321 * @param {Grid} grid the grid to which this gridMenu is associated
1323 initialize: function( $scope, grid ){
1324 grid.gridMenuScope = $scope;
1326 $scope.registeredMenuItems = [];
1328 // not certain this is needed, but would be bad to create a memory leak
1329 $scope.$on('$destroy', function() {
1330 if ( $scope.grid && $scope.grid.gridMenuScope ){
1331 $scope.grid.gridMenuScope = null;
1336 if ( $scope.registeredMenuItems ){
1337 $scope.registeredMenuItems = null;
1341 $scope.registeredMenuItems = [];
1345 * @name addToGridMenu
1346 * @methodOf ui.grid.core.api:PublicApi
1347 * @description add items to the grid menu. Used by features
1348 * to add their menu items if they are enabled, can also be used by
1349 * end users to add menu items. This method has the advantage of allowing
1350 * remove again, which can simplify management of which items are included
1351 * in the menu when. (Noting that in most cases the shown and active functions
1352 * provide a better way to handle visibility of menu items)
1353 * @param {Grid} grid the grid on which we are acting
1354 * @param {array} items menu items in the format as described in the tutorial, with
1355 * the added note that if you want to use remove you must also specify an `id` field,
1356 * which is provided when you want to remove an item. The id should be unique.
1359 grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
1363 * @name removeFromGridMenu
1364 * @methodOf ui.grid.core.api:PublicApi
1365 * @description Remove an item from the grid menu based on a provided id. Assumes
1366 * that the id is unique, removes only the last instance of that id. Does nothing if
1367 * the specified id is not found
1368 * @param {Grid} grid the grid on which we are acting
1369 * @param {string} id the id we'd like to remove from the menu
1372 grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
1378 * @name addToGridMenu
1379 * @propertyOf ui.grid.class:GridOptions
1380 * @description add items to the grid menu. Used by features
1381 * to add their menu items if they are enabled, can also be used by
1382 * end users to add menu items. This method has the advantage of allowing
1383 * remove again, which can simplify management of which items are included
1384 * in the menu when. (Noting that in most cases the shown and active functions
1385 * provide a better way to handle visibility of menu items)
1386 * @param {Grid} grid the grid on which we are acting
1387 * @param {array} items menu items in the format as described in the tutorial, with
1388 * the added note that if you want to use remove you must also specify an `id` field,
1389 * which is provided when you want to remove an item. The id should be unique.
1392 addToGridMenu: function( grid, menuItems ) {
1393 if ( !angular.isArray( menuItems ) ) {
1394 gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
1396 if ( grid.gridMenuScope ){
1397 grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
1398 grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
1400 gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present. Timing issue? Please log issue with ui-grid');
1408 * @name removeFromGridMenu
1409 * @methodOf ui.grid.core.api:PublicApi
1410 * @description Remove an item from the grid menu based on a provided id. Assumes
1411 * that the id is unique, removes only the last instance of that id. Does nothing if
1412 * the specified id is not found. If there is no gridMenuScope or registeredMenuItems
1413 * then do nothing silently - the desired result is those menu items not be present and they
1415 * @param {Grid} grid the grid on which we are acting
1416 * @param {string} id the id we'd like to remove from the menu
1419 removeFromGridMenu: function( grid, id ){
1420 var foundIndex = -1;
1422 if ( grid && grid.gridMenuScope ){
1423 grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
1424 if ( value.id === id ){
1425 if (foundIndex > -1) {
1426 gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
1435 if ( foundIndex > -1 ){
1436 grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
1443 * @name gridMenuCustomItems
1444 * @propertyOf ui.grid.class:GridOptions
1445 * @description (optional) An array of menu items that should be added to
1446 * the gridMenu. Follow the format documented in the tutorial for column
1447 * menu customisation. The context provided to the action function will
1448 * include context.grid. An alternative if working with dynamic menus is to use the
1449 * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
1450 * some of the management of items for you.
1455 * @name gridMenuShowHideColumns
1456 * @propertyOf ui.grid.class:GridOptions
1457 * @description true by default, whether the grid menu should allow hide/show
1463 * @methodOf ui.grid.gridMenuService
1464 * @name getMenuItems
1465 * @description Decides the menu items to show in the menu. This is a
1468 * - the default menu items that are always included,
1469 * - any menu items that have been provided through the addMenuItem api. These
1470 * are typically added by features within the grid
1471 * - any menu items included in grid.options.gridMenuCustomItems. These can be
1472 * changed dynamically, as they're always recalculated whenever we show the
1474 * @param {$scope} $scope the scope of this gridMenu, from which we can find all
1475 * the information that we need
1476 * @returns {array} an array of menu items that can be shown
1478 getMenuItems: function( $scope ) {
1480 // this is where we add any menu items we want to always include
1483 if ( $scope.grid.options.gridMenuCustomItems ){
1484 if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
1485 gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
1487 menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
1491 menuItems = menuItems.concat( $scope.registeredMenuItems );
1493 if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
1494 menuItems = menuItems.concat( service.showHideColumns( $scope ) );
1503 * @name gridMenuTitleFilter
1504 * @propertyOf ui.grid.class:GridOptions
1505 * @description (optional) A function that takes a title string
1506 * (usually the col.displayName), and converts it into a display value. The function
1507 * must return either a string or a promise.
1509 * Used for internationalization of the grid menu column names - for angular-translate
1510 * you can pass $translate as the function, for i18nService you can pass getSafeText as the
1515 * gridMenuTitleFilter: $translate
1521 * @methodOf ui.grid.gridMenuService
1522 * @name showHideColumns
1523 * @description Adds two menu items for each of the columns in columnDefs. One
1524 * menu item for hide, one menu item for show. Each is visible when appropriate
1525 * (show when column is not visible, hide when column is visible). Each toggles
1526 * the visible property on the columnDef using toggleColumnVisibility
1527 * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
1529 showHideColumns: function( $scope ){
1530 var showHideColumns = [];
1531 if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
1532 return showHideColumns;
1535 // add header for columns
1536 showHideColumns.push({
1537 title: i18nService.getSafeText('gridMenu.columns')
1540 $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
1542 $scope.grid.options.columnDefs.forEach( function( colDef, index ){
1543 if ( colDef.enableHiding !== false ){
1544 // add hide menu item - shows an OK icon as we only show when column is already visible
1546 icon: 'ui-grid-icon-ok',
1547 action: function($event) {
1548 $event.stopPropagation();
1549 service.toggleColumnVisibility( this.context.gridCol );
1552 return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
1554 context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) }
1556 service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1557 showHideColumns.push( menuItem );
1559 // add show menu item - shows no icon as we only show when column is invisible
1561 icon: 'ui-grid-icon-cancel',
1562 action: function($event) {
1563 $event.stopPropagation();
1564 service.toggleColumnVisibility( this.context.gridCol );
1567 return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
1569 context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) }
1571 service.setMenuItemTitle( menuItem, colDef, $scope.grid );
1572 showHideColumns.push( menuItem );
1575 return showHideColumns;
1581 * @methodOf ui.grid.gridMenuService
1582 * @name setMenuItemTitle
1583 * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
1584 * item if it returns a string, otherwise waiting for the promise to resolve or reject then
1585 * putting the result into the title
1586 * @param {object} menuItem the menuItem we want to put the title on
1587 * @param {object} colDef the colDef from which we can get displayName, name or field
1588 * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
1591 setMenuItemTitle: function( menuItem, colDef, grid ){
1592 var title = grid.options.gridMenuTitleFilter( colDef.displayName || colDef.name || colDef.field );
1594 if ( typeof(title) === 'string' ){
1595 menuItem.title = title;
1596 } else if ( title.then ){
1597 // must be a promise
1598 menuItem.title = "";
1599 title.then( function( successValue ) {
1600 menuItem.title = successValue;
1601 }, function( errorValue ) {
1602 menuItem.title = errorValue;
1605 gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
1606 menuItem.title = 'badconfig';
1612 * @methodOf ui.grid.gridMenuService
1613 * @name toggleColumnVisibility
1614 * @description Toggles the visibility of an individual column. Expects to be
1615 * provided a context that has on it a gridColumn, which is the column that
1616 * we'll operate upon. We change the visibility, and refresh the grid as appropriate
1617 * @param {GridCol} gridCol the column that we want to toggle
1620 toggleColumnVisibility: function( gridCol ) {
1621 gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
1623 gridCol.grid.refresh();
1632 .directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService',
1633 function (gridUtil, uiGridConstants, uiGridGridMenuService) {
1638 require: ['?^uiGrid'],
1639 templateUrl: 'ui-grid/ui-grid-menu-button',
1643 link: function ($scope, $elm, $attrs, controllers) {
1644 var uiGridCtrl = controllers[0];
1646 uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
1648 $scope.shown = false;
1650 $scope.toggleMenu = function () {
1651 if ( $scope.shown ){
1652 $scope.$broadcast('hide-menu');
1653 $scope.shown = false;
1655 $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
1656 $scope.$broadcast('show-menu');
1657 $scope.shown = true;
1661 $scope.$on('menu-hidden', function() {
1662 $scope.shown = false;
1674 * @name ui.grid.directive:uiGridColumnMenu
1679 * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
1682 <doc:example module="app">
1685 var app = angular.module('app', ['ui.grid']);
1687 app.controller('MainCtrl', ['$scope', function ($scope) {
1692 <div ng-controller="MainCtrl">
1693 <div ui-grid-menu shown="true" ></div>
1700 angular.module('ui.grid')
1702 .directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants',
1703 function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
1711 require: '?^uiGrid',
1712 templateUrl: 'ui-grid/uiGridMenu',
1714 link: function ($scope, $elm, $attrs, uiGridCtrl) {
1719 // *** Show/Hide functions ******
1720 self.showMenu = $scope.showMenu = function(event, args) {
1721 if ( !$scope.shown ){
1724 * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
1725 * animate the removal of the ng-hide. We can't successfully (so far as I can tell)
1726 * animate removal of the ng-if, as the menu items aren't there yet. And we don't want
1727 * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
1730 * Note when testing animation that animations don't run on the tutorials. When debugging it looks
1731 * like they do, but angular has a default $animate provider that is just a stub, and that's what's
1732 * being called. ALso don't be fooled by the fact that your browser has actually loaded the
1733 * angular-translate.js, it's not using it. You need to test animations in an external application.
1735 $scope.shown = true;
1737 $timeout( function() {
1738 $scope.shownMid = true;
1739 $scope.$emit('menu-shown');
1741 } else if ( !$scope.shownMid ) {
1742 // we're probably doing a hide then show, so we don't need to wait for ng-if
1743 $scope.shownMid = true;
1744 $scope.$emit('menu-shown');
1747 var docEventType = 'click';
1748 if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
1749 docEventType = args.originalEvent.type;
1752 // Turn off an existing document click handler
1753 angular.element(document).off('click touchstart', applyHideMenu);
1755 // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
1756 $timeout(function() {
1757 angular.element(document).on(docEventType, applyHideMenu);
1762 self.hideMenu = $scope.hideMenu = function(event, args) {
1763 if ( $scope.shown ){
1765 * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
1766 * set the ng-if (shown = false) after the animation runs. In theory we can cascade off the
1767 * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
1769 * The user may have clicked on the menu again whilst
1770 * we're waiting, so we check that the mid isn't shown before applying the ng-if.
1772 $scope.shownMid = false;
1773 $timeout( function() {
1774 if ( !$scope.shownMid ){
1775 $scope.shown = false;
1776 $scope.$emit('menu-hidden');
1781 angular.element(document).off('click touchstart', applyHideMenu);
1784 $scope.$on('hide-menu', function (event, args) {
1785 $scope.hideMenu(event, args);
1788 $scope.$on('show-menu', function (event, args) {
1789 $scope.showMenu(event, args);
1793 // *** Auto hide when click elsewhere ******
1794 var applyHideMenu = function(){
1796 $scope.$apply(function () {
1802 if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
1803 $scope.autoHide = true;
1806 if ($scope.autoHide) {
1807 angular.element($window).on('resize', applyHideMenu);
1810 $scope.$on('$destroy', function () {
1811 angular.element(document).off('click touchstart', applyHideMenu);
1815 $scope.$on('$destroy', function() {
1816 angular.element($window).off('resize', applyHideMenu);
1819 $scope.$on('$destroy', $scope.$on(uiGridConstants.events.GRID_SCROLL, applyHideMenu ));
1821 $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
1825 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
1833 .directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
1834 var uiGridMenuItem = {
1845 require: ['?^uiGrid', '^uiGridMenu'],
1846 templateUrl: 'ui-grid/uiGridMenuItem',
1848 compile: function($elm, $attrs) {
1850 pre: function ($scope, $elm, $attrs, controllers) {
1851 var uiGridCtrl = controllers[0],
1852 uiGridMenuCtrl = controllers[1];
1854 if ($scope.templateUrl) {
1855 gridUtil.getTemplate($scope.templateUrl)
1856 .then(function (contents) {
1857 var template = angular.element(contents);
1859 var newElm = $compile(template)($scope);
1860 $elm.replaceWith(newElm);
1864 post: function ($scope, $elm, $attrs, controllers) {
1865 var uiGridCtrl = controllers[0],
1866 uiGridMenuCtrl = controllers[1];
1868 // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
1869 // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
1870 // throw new TypeError("$scope.shown is defined but not a function");
1872 if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
1873 $scope.shown = function() { return true; };
1876 $scope.itemShown = function () {
1878 if ($scope.context) {
1879 context.context = $scope.context;
1882 if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
1883 context.grid = uiGridCtrl.grid;
1886 return $scope.shown.call(context);
1889 $scope.itemAction = function($event,title) {
1890 // gridUtil.logDebug('itemAction');
1891 $event.stopPropagation();
1893 if (typeof($scope.action) === 'function') {
1896 if ($scope.context) {
1897 context.context = $scope.context;
1900 // Add the grid to the function call context if the uiGrid controller is present
1901 if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
1902 context.grid = uiGridCtrl.grid;
1905 $scope.action.call(context, $event, title);
1907 $scope.$emit('hide-menu');
1911 $scope.i18n = i18nService.get();
1917 return uiGridMenuItem;
1924 angular.module('ui.grid').directive('uiGridNativeScrollbar', ['$timeout', '$document', 'uiGridConstants', 'gridUtil',
1925 function ($timeout, $document, uiGridConstants, gridUtil) {
1926 var scrollBarWidth = gridUtil.getScrollbarWidth();
1928 // scrollBarWidth = scrollBarWidth > 0 ? scrollBarWidth : 17;
1929 if (!angular.isNumber(scrollBarWidth)) {
1933 // If the browser is IE, add 1px to the scrollbar container, otherwise scroll events won't work right (in IE11 at least)
1934 var browser = gridUtil.detectBrowser();
1935 if (browser === 'ie') {
1936 scrollBarWidth = scrollBarWidth + 1;
1943 require: ['^uiGrid', '^uiGridRenderContainer'],
1944 link: function ($scope, $elm, $attrs, controllers) {
1945 var uiGridCtrl = controllers[0];
1946 var containerCtrl = controllers[1];
1947 var rowContainer = containerCtrl.rowContainer;
1948 var colContainer = containerCtrl.colContainer;
1949 var grid = uiGridCtrl.grid;
1951 var contents = angular.element('<div class="contents"> </div>');
1953 $elm.addClass('ui-grid-native-scrollbar');
1955 var previousScrollPosition;
1957 var elmMaxScroll = 0;
1959 if ($scope.type === 'vertical') {
1960 // Update the width based on native scrollbar width
1961 $elm.css('width', scrollBarWidth + 'px');
1963 $elm.addClass('vertical');
1965 grid.verticalScrollbarWidth = grid.options.enableVerticalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? 0 : scrollBarWidth;
1966 colContainer.verticalScrollbarWidth = grid.verticalScrollbarWidth;
1968 // Save the initial scroll position for use in scroll events
1969 previousScrollPosition = $elm[0].scrollTop;
1971 else if ($scope.type === 'horizontal') {
1972 // Update the height based on native scrollbar height
1973 $elm.css('height', scrollBarWidth + 'px');
1975 $elm.addClass('horizontal');
1977 // Save this scrollbar's dimension in the grid properties
1978 grid.horizontalScrollbarHeight = grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? 0 : scrollBarWidth;
1979 rowContainer.horizontalScrollbarHeight = grid.horizontalScrollbarHeight;
1981 // Save the initial scroll position for use in scroll events
1982 previousScrollPosition = gridUtil.normalizeScrollLeft($elm);
1985 // Save the contents elm inside the scrollbar elm so it sizes correctly
1986 $elm.append(contents);
1988 // Get the relevant element dimension now that the contents are in it
1989 if ($scope.type === 'vertical') {
1990 elmMaxScroll = gridUtil.elementHeight($elm);
1992 else if ($scope.type === 'horizontal') {
1993 elmMaxScroll = gridUtil.elementWidth($elm);
1996 function updateNativeVerticalScrollbar() {
1997 // Get the height that the scrollbar should have
1998 var height = rowContainer.getViewportHeight();
2000 // Update the vertical scrollbar's content height so it's the same as the canvas
2001 var contentHeight = rowContainer.getCanvasHeight();
2003 // TODO(c0bra): set scrollbar `top` by height of header row
2004 // var headerHeight = gridUtil.outerElementHeight(containerCtrl.header);
2005 var headerHeight = colContainer.headerHeight ? colContainer.headerHeight : grid.headerHeight;
2007 // gridUtil.logDebug('headerHeight in scrollbar', headerHeight);
2009 var ondemand = grid.options.enableVerticalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? "overflow-y:auto;" : "";
2010 // var ret = '.grid' + uiGridCtrl.grid.id + ' .ui-grid-native-scrollbar.vertical .contents { height: ' + h + 'px; }';
2011 var ret = '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.vertical .contents { height: ' + contentHeight + 'px; }';
2012 ret += '\n .grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.vertical { height: ' + height + 'px; top: ' + headerHeight + 'px;' +ondemand +'}';
2014 elmMaxScroll = contentHeight;
2019 // Get the grid's bottom border height (TODO(c0bra): need to account for footer here!)
2020 var gridElm = gridUtil.closestElm($elm, '.ui-grid');
2021 var gridBottomBorder = gridUtil.getBorderSize(gridElm, 'bottom');
2023 function updateNativeHorizontalScrollbar() {
2024 var w = colContainer.getCanvasWidth();
2026 var bottom = gridBottomBorder;
2027 if (grid.options.showFooter) {
2031 var adjustment = colContainer.getViewportAdjustment();
2032 bottom -= adjustment.height;
2034 var ondemand = grid.options.enableHorizontalScrollbar === uiGridConstants.scrollbars.WHEN_NEEDED ? "overflow-x:auto" : "";
2035 var ret = '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.horizontal { bottom: ' + bottom + 'px;' +ondemand + ' }';
2036 ret += '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.horizontal .contents { width: ' + w + 'px; }';
2043 // NOTE: priority 6 so they run after the column widths update, which in turn update the canvas width
2044 if ($scope.type === 'vertical') {
2045 grid.registerStyleComputation({
2047 func: updateNativeVerticalScrollbar
2050 else if ($scope.type === 'horizontal') {
2051 grid.registerStyleComputation({
2053 func: updateNativeHorizontalScrollbar
2058 $scope.scrollSource = null;
2060 function scrollEvent(evt) {
2061 if ($scope.type === 'vertical') {
2062 grid.flagScrollingVertically();
2063 var newScrollTop = $elm[0].scrollTop;
2065 var yDiff = previousScrollPosition - newScrollTop;
2067 var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2069 // Subtract the h. scrollbar height from the vertical length if it's present
2070 if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
2071 vertScrollLength = vertScrollLength - uiGridCtrl.grid.horizontalScrollbarHeight;
2074 var vertScrollPercentage = newScrollTop / vertScrollLength;
2076 if (vertScrollPercentage > 1) {
2077 vertScrollPercentage = 1;
2079 if (vertScrollPercentage < 0) {
2080 vertScrollPercentage = 0;
2086 percentage: vertScrollPercentage
2090 // If the source of this scroll is defined (i.e., not us, then don't fire the scroll event because we'll be re-triggering)
2091 if (!$scope.scrollSource) {
2092 uiGridCtrl.fireScrollingEvent(yArgs);
2095 // Reset the scroll source for the next scroll event
2096 $scope.scrollSource = null;
2099 previousScrollPosition = newScrollTop;
2101 else if ($scope.type === 'horizontal') {
2102 grid.flagScrollingHorizontally();
2103 // var newScrollLeft = $elm[0].scrollLeft;
2104 var newScrollLeft = gridUtil.normalizeScrollLeft($elm);
2106 var xDiff = previousScrollPosition - newScrollLeft;
2108 var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2109 var horizScrollPercentage = newScrollLeft / horizScrollLength;
2114 percentage: horizScrollPercentage
2118 // If the source of this scroll is defined (i.e., not us, then don't fire the scroll event because we'll be re-triggering)
2119 if (!$scope.scrollSource) {
2120 uiGridCtrl.fireScrollingEvent(xArgs);
2123 // Reset the scroll source for the next scroll event
2124 $scope.scrollSource = null;
2127 previousScrollPosition = newScrollLeft;
2131 $elm.on('scroll', scrollEvent);
2133 $elm.on('$destroy', function () {
2137 function gridScroll(evt, args) {
2138 // Don't listen to our own scroll event!
2139 if (args.target && (args.target === $elm || angular.element(args.target).hasClass('ui-grid-native-scrollbar'))) {
2143 // Set the source of the scroll event in our scope so it's available in our 'scroll' event handler
2144 $scope.scrollSource = args.target;
2146 if ($scope.type === 'vertical') {
2147 if (args.y && typeof(args.y.percentage) !== 'undefined' && args.y.percentage !== undefined) {
2148 grid.flagScrollingVertically();
2149 var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2151 var newScrollTop = Math.max(0, args.y.percentage * vertScrollLength);
2153 $elm[0].scrollTop = newScrollTop;
2158 else if ($scope.type === 'horizontal') {
2159 if (args.x && typeof(args.x.percentage) !== 'undefined' && args.x.percentage !== undefined) {
2160 grid.flagScrollingHorizontally();
2161 var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2163 var newScrollLeft = Math.max(0, args.x.percentage * horizScrollLength);
2165 // $elm[0].scrollLeft = newScrollLeft;
2166 $elm[0].scrollLeft = gridUtil.denormalizeScrollLeft($elm, newScrollLeft);
2171 var gridScrollDereg = $scope.$on(uiGridConstants.events.GRID_SCROLL, gridScroll);
2172 $scope.$on('$destroy', gridScrollDereg);
2183 var module = angular.module('ui.grid');
2185 module.directive('uiGridRenderContainer', ['$timeout', '$document', 'uiGridConstants', 'gridUtil',
2186 function($timeout, $document, uiGridConstants, GridUtil) {
2190 templateUrl: 'ui-grid/uiGridRenderContainer',
2191 require: ['^uiGrid', 'uiGridRenderContainer'],
2194 rowContainerName: '=',
2195 colContainerName: '=',
2196 bindScrollHorizontal: '=',
2197 bindScrollVertical: '=',
2198 enableVerticalScrollbar: '=',
2199 enableHorizontalScrollbar: '='
2201 controller: 'uiGridRenderContainer as RenderContainer',
2202 compile: function () {
2204 pre: function prelink($scope, $elm, $attrs, controllers) {
2205 // gridUtil.logDebug('render container ' + $scope.containerId + ' pre-link');
2207 var uiGridCtrl = controllers[0];
2208 var containerCtrl = controllers[1];
2210 var grid = $scope.grid = uiGridCtrl.grid;
2212 // Verify that the render container for this element exists
2213 if (!$scope.rowContainerName) {
2214 throw "No row render container name specified";
2216 if (!$scope.colContainerName) {
2217 throw "No column render container name specified";
2220 if (!grid.renderContainers[$scope.rowContainerName]) {
2221 throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
2223 if (!grid.renderContainers[$scope.colContainerName]) {
2224 throw "Column render container '" + $scope.colContainerName + "' is not registered.";
2227 var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
2228 var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
2230 containerCtrl.containerId = $scope.containerId;
2231 containerCtrl.rowContainer = rowContainer;
2232 containerCtrl.colContainer = colContainer;
2234 post: function postlink($scope, $elm, $attrs, controllers) {
2235 // gridUtil.logDebug('render container ' + $scope.containerId + ' post-link');
2237 var uiGridCtrl = controllers[0];
2238 var containerCtrl = controllers[1];
2240 var grid = uiGridCtrl.grid;
2241 var rowContainer = containerCtrl.rowContainer;
2242 var colContainer = containerCtrl.colContainer;
2244 var renderContainer = grid.renderContainers[$scope.containerId];
2246 // Put the container name on this element as a class
2247 $elm.addClass('ui-grid-render-container-' + $scope.containerId);
2249 // Bind to left/right-scroll events
2251 if ($scope.bindScrollHorizontal || $scope.bindScrollVertical) {
2252 scrollUnbinder = $scope.$on(uiGridConstants.events.GRID_SCROLL, scrollHandler);
2255 function scrollHandler (evt, args) {
2257 if (args.y && $scope.bindScrollVertical) {
2258 containerCtrl.prevScrollArgs = args;
2260 var scrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2262 // Add the height of the native horizontal scrollbar, if it's there. Otherwise it will mask over the final row
2263 if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
2264 scrollLength = scrollLength + grid.horizontalScrollbarHeight;
2267 var oldScrollTop = containerCtrl.viewport[0].scrollTop;
2269 var scrollYPercentage;
2270 if (typeof(args.y.percentage) !== 'undefined' && args.y.percentage !== undefined) {
2271 scrollYPercentage = args.y.percentage;
2273 else if (typeof(args.y.pixels) !== 'undefined' && args.y.pixels !== undefined) {
2274 scrollYPercentage = args.y.percentage = (oldScrollTop + args.y.pixels) / scrollLength;
2275 // gridUtil.logDebug('y.percentage', args.y.percentage);
2278 throw new Error("No percentage or pixel value provided for scroll event Y axis");
2281 var newScrollTop = Math.max(0, scrollYPercentage * scrollLength);
2283 containerCtrl.viewport[0].scrollTop = newScrollTop;
2285 // TOOD(c0bra): what's this for?
2286 // grid.options.offsetTop = newScrollTop;
2288 containerCtrl.prevScrollArgs.y.pixels = newScrollTop - oldScrollTop;
2291 // Horizontal scroll
2292 if (args.x && $scope.bindScrollHorizontal) {
2293 containerCtrl.prevScrollArgs = args;
2295 var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2297 // var oldScrollLeft = containerCtrl.viewport[0].scrollLeft;
2298 var oldScrollLeft = GridUtil.normalizeScrollLeft(containerCtrl.viewport);
2300 var scrollXPercentage;
2301 if (typeof(args.x.percentage) !== 'undefined' && args.x.percentage !== undefined) {
2302 scrollXPercentage = args.x.percentage;
2304 else if (typeof(args.x.pixels) !== 'undefined' && args.x.pixels !== undefined) {
2305 scrollXPercentage = args.x.percentage = (oldScrollLeft + args.x.pixels) / scrollWidth;
2308 throw new Error("No percentage or pixel value provided for scroll event X axis");
2311 var newScrollLeft = Math.max(0, scrollXPercentage * scrollWidth);
2313 // uiGridCtrl.adjustScrollHorizontal(newScrollLeft, scrollXPercentage);
2315 // containerCtrl.viewport[0].scrollLeft = newScrollLeft;
2316 containerCtrl.viewport[0].scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.viewport, newScrollLeft);
2318 containerCtrl.prevScrollLeft = newScrollLeft;
2320 if (containerCtrl.headerViewport) {
2321 // containerCtrl.headerViewport.scrollLeft = newScrollLeft;
2322 containerCtrl.headerViewport.scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.headerViewport, newScrollLeft);
2325 if (containerCtrl.footerViewport) {
2326 // containerCtrl.footerViewport.scrollLeft = newScrollLeft;
2327 containerCtrl.footerViewport.scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.footerViewport, newScrollLeft);
2330 // uiGridCtrl.grid.options.offsetLeft = newScrollLeft;
2332 containerCtrl.prevScrollArgs.x.pixels = newScrollLeft - oldScrollLeft;
2336 // Scroll the render container viewport when the mousewheel is used
2337 $elm.bind('wheel mousewheel DomMouseScroll MozMousePixelScroll', function(evt) {
2339 evt.preventDefault();
2341 var newEvent = GridUtil.normalizeWheelEvent(evt);
2343 var args = { target: $elm };
2344 if (newEvent.deltaY !== 0) {
2345 var scrollYAmount = newEvent.deltaY * -120;
2347 // Get the scroll percentage
2348 var scrollYPercentage = (containerCtrl.viewport[0].scrollTop + scrollYAmount) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2350 // Keep scrollPercentage within the range 0-1.
2351 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2352 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2354 args.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
2356 if (newEvent.deltaX !== 0) {
2357 var scrollXAmount = newEvent.deltaX * -120;
2359 // Get the scroll percentage
2360 var scrollLeft = GridUtil.normalizeScrollLeft(containerCtrl.viewport);
2361 var scrollXPercentage = (scrollLeft + scrollXAmount) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2363 // Keep scrollPercentage within the range 0-1.
2364 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2365 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2367 args.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
2370 uiGridCtrl.fireScrollingEvent(args);
2377 scrollLeftStart = 0,
2382 function touchmove(event) {
2383 if (event.originalEvent) {
2384 event = event.originalEvent;
2387 event.preventDefault();
2389 var deltaX, deltaY, newX, newY;
2390 newX = event.targetTouches[0].screenX;
2391 newY = event.targetTouches[0].screenY;
2392 deltaX = -(newX - startX);
2393 deltaY = -(newY - startY);
2395 directionY = (deltaY < 1) ? -1 : 1;
2396 directionX = (deltaX < 1) ? -1 : 1;
2401 var args = { target: event.target };
2404 var scrollYPercentage = (scrollTopStart + deltaY) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2406 if (scrollYPercentage > 1) { scrollYPercentage = 1; }
2407 else if (scrollYPercentage < 0) { scrollYPercentage = 0; }
2409 args.y = { percentage: scrollYPercentage, pixels: deltaY };
2412 var scrollXPercentage = (scrollLeftStart + deltaX) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2414 if (scrollXPercentage > 1) { scrollXPercentage = 1; }
2415 else if (scrollXPercentage < 0) { scrollXPercentage = 0; }
2417 args.x = { percentage: scrollXPercentage, pixels: deltaX };
2420 uiGridCtrl.fireScrollingEvent(args);
2423 function touchend(event) {
2424 if (event.originalEvent) {
2425 event = event.originalEvent;
2428 event.preventDefault();
2430 $document.unbind('touchmove', touchmove);
2431 $document.unbind('touchend', touchend);
2432 $document.unbind('touchcancel', touchend);
2434 // Get the distance we moved on the Y axis
2435 var scrollTopEnd = containerCtrl.viewport[0].scrollTop;
2436 var scrollLeftEnd = containerCtrl.viewport[0].scrollTop;
2437 var deltaY = Math.abs(scrollTopEnd - scrollTopStart);
2438 var deltaX = Math.abs(scrollLeftEnd - scrollLeftStart);
2440 // Get the duration it took to move this far
2441 var moveDuration = (new Date()) - moveStart;
2443 // Scale the amount moved by the time it took to move it (i.e. quicker, longer moves == more scrolling after the move is over)
2444 var moveYScale = deltaY / moveDuration;
2445 var moveXScale = deltaX / moveDuration;
2447 var decelerateInterval = 63; // 1/16th second
2448 var decelerateCount = 8; // == 1/2 second
2449 var scrollYLength = 120 * directionY * moveYScale;
2450 var scrollXLength = 120 * directionX * moveXScale;
2452 function decelerate() {
2453 $timeout(function() {
2454 var args = { target: event.target };
2456 if (scrollYLength !== 0) {
2457 var scrollYPercentage = (containerCtrl.viewport[0].scrollTop + scrollYLength) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2459 args.y = { percentage: scrollYPercentage, pixels: scrollYLength };
2462 if (scrollXLength !== 0) {
2463 var scrollXPercentage = (containerCtrl.viewport[0].scrollLeft + scrollXLength) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2464 args.x = { percentage: scrollXPercentage, pixels: scrollXLength };
2467 uiGridCtrl.fireScrollingEvent(args);
2469 decelerateCount = decelerateCount -1;
2470 scrollYLength = scrollYLength / 2;
2471 scrollXLength = scrollXLength / 2;
2473 if (decelerateCount > 0) {
2477 uiGridCtrl.scrollbars.forEach(function (sbar) {
2478 sbar.removeClass('ui-grid-scrollbar-visible');
2479 sbar.removeClass('ui-grid-scrolling');
2482 }, decelerateInterval);
2488 if (GridUtil.isTouchEnabled()) {
2489 $elm.bind('touchstart', function (event) {
2490 if (event.originalEvent) {
2491 event = event.originalEvent;
2494 event.preventDefault();
2496 uiGridCtrl.scrollbars.forEach(function (sbar) {
2497 sbar.addClass('ui-grid-scrollbar-visible');
2498 sbar.addClass('ui-grid-scrolling');
2501 moveStart = new Date();
2502 startY = event.targetTouches[0].screenY;
2503 startX = event.targetTouches[0].screenX;
2504 scrollTopStart = containerCtrl.viewport[0].scrollTop;
2505 scrollLeftStart = containerCtrl.viewport[0].scrollLeft;
2507 $document.on('touchmove', touchmove);
2508 $document.on('touchend touchcancel', touchend);
2512 $elm.bind('$destroy', function() {
2514 $elm.unbind('keydown');
2516 ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
2517 $elm.unbind(eventName);
2521 // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
2525 var canvasWidth = colContainer.getCanvasWidth();
2526 var viewportWidth = colContainer.getViewportWidth();
2528 var canvasHeight = rowContainer.getCanvasHeight();
2529 var viewportHeight = rowContainer.getViewportHeight();
2531 var headerViewportWidth = colContainer.getHeaderViewportWidth();
2532 var footerViewportWidth = colContainer.getHeaderViewportWidth();
2534 // Set canvas dimensions
2535 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
2536 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + canvasWidth + 'px; }';
2538 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
2539 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
2541 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + canvasWidth + 'px; }';
2542 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
2544 // If the render container has an "explicit" header height (such as in the case that its header is smaller than the other headers and needs to be explicitly set to be the same, ue thae)
2545 if (renderContainer.explicitHeaderHeight !== undefined && renderContainer.explicitHeaderHeight !== null && renderContainer.explicitHeaderHeight > 0) {
2546 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-cell { height: ' + renderContainer.explicitHeaderHeight + 'px; }';
2548 // Otherwise if the render container has an INNER header height, use that on the header cells (so that all the header cells are the same height and those that have less elements don't have undersized borders)
2549 else if (renderContainer.innerHeaderHeight !== undefined && renderContainer.innerHeaderHeight !== null && renderContainer.innerHeaderHeight > 0) {
2550 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-cell { height: ' + renderContainer.innerHeaderHeight + 'px; }';
2556 uiGridCtrl.grid.registerStyleComputation({
2567 module.controller('uiGridRenderContainer', ['$scope', 'gridUtil', function ($scope, gridUtil) {
2570 self.rowStyle = function (index) {
2571 var renderContainer = $scope.grid.renderContainers[$scope.containerId];
2575 if (!renderContainer.disableRowOffset) {
2576 if (index === 0 && self.currentTopRow !== 0) {
2577 // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
2578 var hiddenRowWidth = ($scope.rowContainer.currentTopRow) *
2579 $scope.rowContainer.visibleRowCache[$scope.rowContainer.currentTopRow].height;
2581 // return { 'margin-top': hiddenRowWidth + 'px' };
2582 styles['margin-top'] = hiddenRowWidth + 'px';
2586 if (!renderContainer.disableColumnOffset && $scope.colContainer.currentFirstColumn !== 0) {
2587 if ($scope.grid.isRTL()) {
2588 styles['margin-right'] = $scope.colContainer.columnOffset + 'px';
2591 styles['margin-left'] = $scope.colContainer.columnOffset + 'px';
2598 self.columnStyle = function (index) {
2599 var renderContainer = $scope.grid.renderContainers[$scope.containerId];
2603 if (!renderContainer.disableColumnOffset) {
2604 if (index === 0 && $scope.colContainer.currentFirstColumn !== 0) {
2605 var offset = $scope.colContainer.columnOffset;
2607 if ($scope.grid.isRTL()) {
2608 return { 'margin-right': offset + 'px' };
2611 return { 'margin-left': offset + 'px' };
2624 angular.module('ui.grid').directive('uiGridRow', ['gridUtil', function(gridUtil) {
2628 // templateUrl: 'ui-grid/ui-grid-row',
2629 require: ['^uiGrid', '^uiGridRenderContainer'],
2632 //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
2635 compile: function() {
2637 pre: function($scope, $elm, $attrs, controllers) {
2638 var uiGridCtrl = controllers[0];
2639 var containerCtrl = controllers[1];
2641 var grid = uiGridCtrl.grid;
2643 $scope.grid = uiGridCtrl.grid;
2644 $scope.colContainer = containerCtrl.colContainer;
2646 grid.getRowTemplateFn.then(function (templateFn) {
2647 templateFn($scope, function(clonedElement, scope) {
2648 $elm.replaceWith(clonedElement);
2652 post: function($scope, $elm, $attrs, controllers) {
2653 var uiGridCtrl = controllers[0];
2654 var containerCtrl = controllers[1];
2656 //add optional reference to externalScopes function to scope
2657 //so it can be retrieved in lower elements
2658 $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
2671 * @name ui.grid.directive:uiGridStyle
2676 * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2679 <doc:example module="app">
2682 var app = angular.module('app', ['ui.grid']);
2684 app.controller('MainCtrl', ['$scope', function ($scope) {
2685 $scope.myStyle = '.blah { border: 1px solid }';
2689 <div ng-controller="MainCtrl">
2690 <style ui-grid-style>{{ myStyle }}</style>
2691 <span class="blah">I am in a box.</span>
2695 it('should apply the right class to the element', function () {
2696 element(by.css('.blah')).getCssValue('border')
2698 expect(c).toContain('1px solid');
2706 angular.module('ui.grid').directive('uiGridStyle', ['gridUtil', '$interpolate', function(gridUtil, $interpolate) {
2710 // require: '?^uiGrid',
2711 link: function($scope, $elm, $attrs, uiGridCtrl) {
2712 // gridUtil.logDebug('ui-grid-style link');
2713 // if (uiGridCtrl === undefined) {
2714 // gridUtil.logWarn('[ui-grid-style link] uiGridCtrl is undefined!');
2717 var interpolateFn = $interpolate($elm.text(), true);
2719 if (interpolateFn) {
2720 $scope.$watch(interpolateFn, function(value) {
2725 // uiGridCtrl.recalcRowStyles = function() {
2726 // var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
2727 // var rowHeight = scope.options.rowHeight;
2730 // var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
2731 // for (var i = 1; i <= rowStyleCount; i++) {
2732 // ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
2733 // offset = offset + rowHeight;
2736 // scope.rowStyles = ret;
2739 // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
2749 angular.module('ui.grid').directive('uiGridViewport', ['gridUtil',
2750 function(gridUtil) {
2754 templateUrl: 'ui-grid/uiGridViewport',
2755 require: ['^uiGrid', '^uiGridRenderContainer'],
2756 link: function($scope, $elm, $attrs, controllers) {
2757 // gridUtil.logDebug('viewport post-link');
2759 var uiGridCtrl = controllers[0];
2760 var containerCtrl = controllers[1];
2762 $scope.containerCtrl = containerCtrl;
2764 var rowContainer = containerCtrl.rowContainer;
2765 var colContainer = containerCtrl.colContainer;
2767 var grid = uiGridCtrl.grid;
2769 $scope.grid = uiGridCtrl.grid;
2771 // Put the containers in scope so we can get rows and columns from them
2772 $scope.rowContainer = containerCtrl.rowContainer;
2773 $scope.colContainer = containerCtrl.colContainer;
2775 // Register this viewport with its container
2776 containerCtrl.viewport = $elm;
2778 $elm.on('scroll', function (evt) {
2779 var newScrollTop = $elm[0].scrollTop;
2780 // var newScrollLeft = $elm[0].scrollLeft;
2781 var newScrollLeft = gridUtil.normalizeScrollLeft($elm);
2782 var horizScrollPercentage = -1;
2783 var vertScrollPercentage = -1;
2787 if (newScrollLeft !== colContainer.prevScrollLeft) {
2788 var xDiff = newScrollLeft - colContainer.prevScrollLeft;
2790 var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2791 horizScrollPercentage = newScrollLeft / horizScrollLength;
2793 colContainer.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
2796 if (newScrollTop !== rowContainer.prevScrollTop) {
2797 var yDiff = newScrollTop - rowContainer.prevScrollTop;
2799 // uiGridCtrl.fireScrollingEvent({ y: { pixels: diff } });
2800 var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2801 // var vertScrollPercentage = (uiGridCtrl.prevScrollTop + yDiff) / vertScrollLength;
2802 vertScrollPercentage = newScrollTop / vertScrollLength;
2804 if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
2805 if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
2807 rowContainer.adjustScrollVertical(newScrollTop, vertScrollPercentage);
2810 if ( !$scope.grid.isScrollingVertically && !$scope.grid.isScrollingHorizontally ){
2811 // viewport scroll that didn't come from fireScrollEvent, so fire a scroll to keep
2812 // the header in sync
2814 if ( horizScrollPercentage > -1 ){
2815 args.x = { percentage: horizScrollPercentage };
2818 if ( vertScrollPercentage > -1 ){
2819 args.y = { percentage: vertScrollPercentage };
2821 uiGridCtrl.fireScrollingEvent(args);
2832 angular.module('ui.grid')
2833 .directive('uiGridVisible', function uiGridVisibleAction() {
2834 return function ($scope, $elm, $attr) {
2835 $scope.$watch($attr.uiGridVisible, function (visible) {
2836 // $elm.css('visibility', visible ? 'visible' : 'hidden');
2837 $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
2846 angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants',
2847 '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
2848 function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants,
2849 $templateCache, gridClassFactory, $timeout, $parse, $compile) {
2850 // gridUtil.logDebug('ui-grid controller');
2854 // Extend options with ui-grid attribute reference
2855 self.grid = gridClassFactory.createGrid($scope.uiGrid);
2856 $elm.addClass('grid' + self.grid.id);
2857 self.grid.rtl = gridUtil.getStyles($elm[0])['direction'] === 'rtl';
2860 //add optional reference to externalScopes function to controller
2861 //so it can be retrieved in lower elements that have isolate scope
2862 self.getExternalScopes = $scope.getExternalScopes;
2864 // angular.extend(self.grid.options, );
2866 //all properties of grid are available on scope
2867 $scope.grid = self.grid;
2869 if ($attrs.uiGridColumns) {
2870 $attrs.$observe('uiGridColumns', function(value) {
2871 self.grid.options.columnDefs = value;
2872 self.grid.buildColumns()
2874 self.grid.preCompileCellTemplates();
2876 self.grid.refreshCanvas(true);
2882 var dataWatchCollectionDereg;
2883 if (angular.isString($scope.uiGrid.data)) {
2884 dataWatchCollectionDereg = $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction);
2887 dataWatchCollectionDereg = $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction);
2890 var columnDefWatchCollectionDereg = $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction);
2892 function columnDefsWatchFunction(n, o) {
2894 self.grid.options.columnDefs = n;
2895 self.grid.buildColumns()
2898 self.grid.preCompileCellTemplates();
2900 self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN);
2905 function dataWatchFunction(newData) {
2906 // gridUtil.logDebug('dataWatch fired');
2911 // If we have no columns (i.e. columns length is either 0 or equal to the number of row header columns, which don't count because they're created automatically)
2912 self.grid.columns.length === (self.grid.rowHeaderColumns ? self.grid.rowHeaderColumns.length : 0) &&
2913 // ... and we don't have a ui-grid-columns attribute, which would define columns for us
2914 !$attrs.uiGridColumns &&
2915 // ... and we have no pre-defined columns
2916 self.grid.options.columnDefs.length === 0 &&
2917 // ... but we DO have data
2920 // ... then build the column definitions from the data that we have
2921 self.grid.buildColumnDefsFromData(newData);
2924 // If we either have some columns defined, or some data defined
2925 if (self.grid.options.columnDefs.length > 0 || newData.length > 0) {
2926 // Build the column set, then pre-compile the column cell templates
2927 promises.push(self.grid.buildColumns()
2929 self.grid.preCompileCellTemplates();
2933 $q.all(promises).then(function() {
2934 self.grid.modifyRows(newData)
2936 // if (self.viewport) {
2937 self.grid.redrawInPlace();
2940 $scope.$evalAsync(function() {
2941 self.grid.refreshCanvas(true);
2942 self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW);
2950 $scope.$on('$destroy', function() {
2951 dataWatchCollectionDereg();
2952 columnDefWatchCollectionDereg();
2955 $scope.$watch(function () { return self.grid.styleComputations; }, function() {
2956 self.grid.refreshCanvas(true);
2962 self.fireScrollingEvent = gridUtil.throttle(function(args) {
2963 $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
2964 }, self.grid.options.scrollThrottle, {trailing: true});
2966 self.fireEvent = function(eventName, args) {
2967 // Add the grid to the event arguments if it's not there
2968 if (typeof(args) === 'undefined' || args === undefined) {
2972 if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
2973 args.grid = self.grid;
2976 $scope.$broadcast(eventName, args);
2979 self.innerCompile = function innerCompile(elm) {
2980 $compile(elm)($scope);
2987 * @name ui.grid.directive:uiGrid
2990 * @param {Object} uiGrid Options for the grid to use
2991 * @param {Object=} external-scopes Add external-scopes='someScopeObjectYouNeed' attribute so you can access
2992 * your scopes from within any custom templatedirective. You access by $scope.getExternalScopes() function
2994 * @description Create a very basic grid.
2997 <example module="app">
2998 <file name="app.js">
2999 var app = angular.module('app', ['ui.grid']);
3001 app.controller('MainCtrl', ['$scope', function ($scope) {
3003 { name: 'Bob', title: 'CEO' },
3004 { name: 'Frank', title: 'Lowly Developer' }
3008 <file name="index.html">
3009 <div ng-controller="MainCtrl">
3010 <div ui-grid="{ data: data }"></div>
3015 angular.module('ui.grid').directive('uiGrid',
3028 templateUrl: 'ui-grid/ui-grid',
3031 getExternalScopes: '&?externalScopes' //optional functionwrapper around any needed external scope instances
3035 controller: 'uiGridController',
3036 compile: function () {
3038 post: function ($scope, $elm, $attrs, uiGridCtrl) {
3039 // gridUtil.logDebug('ui-grid postlink');
3041 var grid = uiGridCtrl.grid;
3043 // Initialize scrollbars (TODO: move to controller??)
3044 uiGridCtrl.scrollbars = [];
3046 //todo: assume it is ok to communicate that rendering is complete??
3047 grid.renderingComplete();
3049 grid.element = $elm;
3051 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3053 // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
3054 grid.canvasWidth = uiGridCtrl.grid.gridWidth;
3056 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3058 // If the grid isn't tall enough to fit a single row, it's kind of useless. Resize it to fit a minimum number of rows
3059 if (grid.gridHeight < grid.options.rowHeight) {
3060 // Figure out the new height
3061 var contentHeight = grid.options.minRowsToShow * grid.options.rowHeight;
3062 var headerHeight = grid.options.hideHeader ? 0 : grid.options.headerRowHeight;
3063 var footerHeight = grid.options.showFooter ? grid.options.footerRowHeight : 0;
3064 var scrollbarHeight = grid.options.enableScrollbars ? gridUtil.getScrollbarWidth() : 0;
3066 var maxNumberOfFilters = 0;
3067 // Calculates the maximum number of filters in the columns
3068 angular.forEach(grid.options.columnDefs, function(col) {
3069 if (col.hasOwnProperty('filter')) {
3070 if (maxNumberOfFilters < 1) {
3071 maxNumberOfFilters = 1;
3074 else if (col.hasOwnProperty('filters')) {
3075 if (maxNumberOfFilters < col.filters.length) {
3076 maxNumberOfFilters = col.filters.length;
3080 var filterHeight = maxNumberOfFilters * headerHeight;
3082 var newHeight = headerHeight + contentHeight + footerHeight + scrollbarHeight + filterHeight;
3084 $elm.css('height', newHeight + 'px');
3086 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3089 // Run initial canvas refresh
3090 grid.refreshCanvas();
3092 //add pinned containers for row headers support
3093 //moved from pinning feature
3094 var left = angular.element('<div ng-if="grid.hasLeftContainer()" style="width: 0" ui-grid-pinned-container="\'left\'"></div>');
3096 uiGridCtrl.innerCompile(left);
3098 var right = angular.element('<div ng-if="grid.hasRightContainer()" style="width: 0" ui-grid-pinned-container="\'right\'"></div>');
3100 uiGridCtrl.innerCompile(right);
3103 //if we add a left container after render, we need to watch and react
3104 $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
3105 if (newValue === oldValue) {
3109 //todo: remove this code. it was commented out after moving from pinning because body is already float:left
3110 // var bodyContainer = angular.element($elm[0].querySelectorAll('[container-id="body"]'));
3112 // bodyContainer.attr('style', 'float: left; position: inherit');
3115 // bodyContainer.attr('style', 'float: left; position: relative');
3118 grid.refreshCanvas(true);
3121 //if we add a right container after render, we need to watch and react
3122 $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
3123 if (newValue === oldValue) {
3126 grid.refreshCanvas(true);
3130 // Resize the grid on window resize events
3131 function gridResize($event) {
3132 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
3133 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
3135 grid.queueRefresh();
3138 angular.element($window).on('resize', gridResize);
3140 // Unbind from window resize events when the grid is destroyed
3141 $elm.on('$destroy', function () {
3142 angular.element($window).off('resize', gridResize);
3156 angular.module('ui.grid').directive('uiGridPinnedContainer', ['gridUtil', function (gridUtil) {
3160 template: '<div class="ui-grid-pinned-container"><div ui-grid-render-container container-id="side" row-container-name="\'body\'" col-container-name="side" bind-scroll-vertical="true" class="{{ side }} ui-grid-render-container-{{ side }}"></div></div>',
3162 side: '=uiGridPinnedContainer'
3165 compile: function compile() {
3167 post: function ($scope, $elm, $attrs, uiGridCtrl) {
3168 // gridUtil.logDebug('ui-grid-pinned-container ' + $scope.side + ' link');
3170 var grid = uiGridCtrl.grid;
3174 $elm.addClass('ui-grid-pinned-container-' + $scope.side);
3176 function updateContainerWidth() {
3177 if ($scope.side === 'left' || $scope.side === 'right') {
3178 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
3180 for (var i = 0; i < cols.length; i++) {
3182 width += col.drawnWidth;
3189 function updateContainerDimensions() {
3190 // gridUtil.logDebug('update ' + $scope.side + ' dimensions');
3194 // Column containers
3195 if ($scope.side === 'left' || $scope.side === 'right') {
3196 updateContainerWidth();
3198 // gridUtil.logDebug('myWidth', myWidth);
3200 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
3201 $elm.attr('style', null);
3203 var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
3205 ret += '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.side + ', .grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.side + ' .ui-grid-render-container-' + $scope.side + ' .ui-grid-viewport { width: ' + myWidth + 'px; height: ' + myHeight + 'px; } ';
3211 grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
3212 if ( myWidth === 0 ){
3213 updateContainerWidth();
3215 // Subtract our own width
3216 adjustment.width -= myWidth;
3221 // Register style computation to adjust for columns in `side`'s render container
3222 grid.registerStyleComputation({
3224 func: updateContainerDimensions
3234 angular.module('ui.grid')
3235 .factory('Grid', ['$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout',
3236 function($q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout) {
3240 * @name ui.grid.core.api:PublicApi
3241 * @description Public Api for the core grid features
3247 * @name ui.grid.class:Grid
3248 * @description Grid is the main viewModel. Any properties or methods needed to maintain state are defined in
3249 * * this prototype. One instance of Grid is created per Grid directive instance.
3250 * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
3252 var Grid = function Grid(options) {
3254 // Get the id out of the options, then remove it
3255 if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
3256 if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
3257 throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
3261 throw new Error('No ID provided. An ID must be given when creating a grid.');
3264 self.id = options.id;
3267 // Get default options
3268 self.options = GridOptions.initialize( options );
3270 self.headerHeight = self.options.headerRowHeight;
3271 self.footerHeight = self.options.showFooter === true ? self.options.footerRowHeight : 0;
3274 self.gridHeight = 0;
3276 self.columnBuilders = [];
3277 self.rowBuilders = [];
3278 self.rowsProcessors = [];
3279 self.columnsProcessors = [];
3280 self.styleComputations = [];
3281 self.viewportAdjusters = [];
3282 self.rowHeaderColumns = [];
3283 self.dataChangeCallbacks = {};
3285 // self.visibleRowCache = [];
3287 // Set of 'render' containers for self grid, which can render sets of rows
3288 self.renderContainers = {};
3291 self.renderContainers.body = new GridRenderContainer('body', self);
3293 self.cellValueGetterCache = {};
3295 // Cached function to use with custom row templates
3296 self.getRowTemplateFn = null;
3299 //representation of the rows on the grid.
3300 //these are wrapped references to the actual data rows (options.data)
3303 //represents the columns on the grid
3308 * @name isScrollingVertically
3309 * @propertyOf ui.grid.class:Grid
3310 * @description set to true when Grid is scrolling vertically. Set to false via debounced method
3312 self.isScrollingVertically = false;
3316 * @name isScrollingHorizontally
3317 * @propertyOf ui.grid.class:Grid
3318 * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
3320 self.isScrollingHorizontally = false;
3322 var debouncedVertical = gridUtil.debounce(function () {
3323 self.isScrollingVertically = false;
3326 var debouncedHorizontal = gridUtil.debounce(function () {
3327 self.isScrollingHorizontally = false;
3333 * @name flagScrollingVertically
3334 * @methodOf ui.grid.class:Grid
3335 * @description sets isScrollingVertically to true and sets it to false in a debounced function
3337 self.flagScrollingVertically = function() {
3338 self.isScrollingVertically = true;
3339 debouncedVertical();
3344 * @name flagScrollingHorizontally
3345 * @methodOf ui.grid.class:Grid
3346 * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
3348 self.flagScrollingHorizontally = function() {
3349 self.isScrollingHorizontally = true;
3350 debouncedHorizontal();
3355 self.api = new GridApi(self);
3360 * @methodOf ui.grid.core.api:PublicApi
3361 * @description Refresh the rendered grid on screen.
3364 self.api.registerMethod( 'core', 'refresh', this.refresh );
3369 * @methodOf ui.grid.core.api:PublicApi
3370 * @description Refresh the rendered grid on screen? Note: not functional at present
3371 * @returns {promise} promise that is resolved when render completes?
3374 self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
3378 * @name handleWindowResize
3379 * @methodOf ui.grid.core.api:PublicApi
3380 * @description Trigger a grid resize, normally this would be picked
3381 * up by a watch on window size, but in some circumstances it is necessary
3382 * to call this manually
3383 * @returns {promise} promise that is resolved when render completes?
3386 self.api.registerMethod( 'core', 'handleWindowResize', this.handleWindowResize );
3391 * @name addRowHeaderColumn
3392 * @methodOf ui.grid.core.api:PublicApi
3393 * @description adds a row header column to the grid
3394 * @param {object} column def
3397 self.api.registerMethod( 'core', 'addRowHeaderColumn', this.addRowHeaderColumn );
3402 * @name sortHandleNulls
3403 * @methodOf ui.grid.core.api:PublicApi
3404 * @description A null handling method that can be used when building custom sort
3408 * mySortFn = function(a, b) {
3409 * var nulls = $scope.gridApi.core.sortHandleNulls(a, b);
3410 * if ( nulls !== null ){
3413 * // your code for sorting here
3416 * @param {object} a sort value a
3417 * @param {object} b sort value b
3418 * @returns {number} null if there were no nulls/undefineds, otherwise returns
3419 * a sort value that should be passed back from the sort function
3422 self.api.registerMethod( 'core', 'sortHandleNulls', rowSorter.handleNulls );
3428 * @methodOf ui.grid.core.api:PublicApi
3429 * @description The sort criteria on one or more columns has
3430 * changed. Provides as parameters the grid and the output of
3431 * getColumnSorting, which is an array of gridColumns
3432 * that have sorting on them, sorted in priority order.
3434 * @param {Grid} grid the grid
3435 * @param {array} sortColumns an array of columns with
3436 * sorts on them, in priority order
3440 * gridApi.core.on.sortChanged( grid, sortColumns );
3443 self.api.registerEvent( 'core', 'sortChanged' );
3447 * @name notifyDataChange
3448 * @methodOf ui.grid.core.api:PublicApi
3449 * @description Notify the grid that a data or config change has occurred,
3450 * where that change isn't something the grid was otherwise noticing. This
3451 * might be particularly relevant where you've changed values within the data
3452 * and you'd like cell classes to be re-evaluated, or changed config within
3453 * the columnDef and you'd like headerCellClasses to be re-evaluated.
3454 * @param {Grid} grid the grid
3455 * @param {string} type one of the
3456 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells
3457 * us which refreshes to fire.
3460 self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange );
3462 self.registerDataChangeCallback( self.columnRefreshCallback, [uiGridConstants.dataChange.COLUMN]);
3463 self.registerDataChangeCallback( self.processRowsCallback, [uiGridConstants.dataChange.EDIT]);
3469 * @methodOf ui.grid.class:Grid
3470 * @description Returns true if grid is RightToLeft
3472 Grid.prototype.isRTL = function () {
3479 * @name registerColumnBuilder
3480 * @methodOf ui.grid.class:Grid
3481 * @description When the build creates columns from column definitions, the columnbuilders will be called to add
3482 * additional properties to the column.
3483 * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called
3485 Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
3486 this.columnBuilders.push(columnBuilder);
3491 * @name buildColumnDefsFromData
3492 * @methodOf ui.grid.class:Grid
3493 * @description Populates columnDefs from the provided data
3494 * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
3496 Grid.prototype.buildColumnDefsFromData = function (dataRows){
3497 this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
3502 * @name registerRowBuilder
3503 * @methodOf ui.grid.class:Grid
3504 * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
3505 * additional properties to the row.
3506 * @param {function(row, gridOptions)} rowBuilder function to be called
3508 Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
3509 this.rowBuilders.push(rowBuilder);
3515 * @name registerDataChangeCallback
3516 * @methodOf ui.grid.class:Grid
3517 * @description When a data change occurs, the data change callbacks of the specified type
3518 * will be called. The rules are:
3520 * - when the data watch fires, that is considered a ROW change (the data watch only notices
3521 * added or removed rows)
3522 * - when the api is called to inform us of a change, the declared type of that change is used
3523 * - when a cell edit completes, the EDIT callbacks are triggered
3524 * - when the columnDef watch fires, the COLUMN callbacks are triggered
3526 * For a given event:
3527 * - ALL calls ROW, EDIT, COLUMN and ALL callbacks
3528 * - ROW calls ROW and ALL callbacks
3529 * - EDIT calls EDIT and ALL callbacks
3530 * - COLUMN calls COLUMN and ALL callbacks
3532 * @param {function(grid)} callback function to be called
3533 * @param {array} types the types of data change you want to be informed of. Values from
3534 * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ). Optional and defaults to
3536 * @returns {string} uid of the callback, can be used to deregister it again
3538 Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) {
3539 var uid = gridUtil.nextUid();
3541 types = [uiGridConstants.dataChange.ALL];
3543 if ( !Array.isArray(types)){
3544 gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types );
3546 this.dataChangeCallbacks[uid] = { callback: callback, types: types };
3552 * @name deregisterDataChangeCallback
3553 * @methodOf ui.grid.class:Grid
3554 * @description Delete the callback identified by the id.
3555 * @param {string} uid the uid of the function that is to be deregistered
3557 Grid.prototype.deregisterDataChangeCallback = function deregisterDataChangeCallback(uid) {
3558 delete this.dataChangeCallbacks[uid];
3563 * @name callDataChangeCallbacks
3564 * @methodOf ui.grid.class:Grid
3565 * @description Calls the callbacks based on the type of data change that
3566 * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT, and COLUMN callbacks if the
3567 * event type is matching, or if the type is ALL.
3568 * @param {number} type the type of event that occurred - one of the
3569 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
3571 Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type, options) {
3572 angular.forEach( this.dataChangeCallbacks, function( callback, uid ){
3573 if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 ||
3574 callback.types.indexOf( type ) !== -1 ||
3575 type === uiGridConstants.dataChange.ALL ) {
3576 callback.callback( this );
3583 * @name notifyDataChange
3584 * @methodOf ui.grid.class:Grid
3585 * @description Notifies us that a data change has occurred, used in the public
3586 * api for users to tell us when they've changed data or some other event that
3587 * our watches cannot pick up
3588 * @param {Grid} grid the grid
3589 * @param {string} type the type of event that occurred - one of the
3590 * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN)
3592 Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) {
3593 var constants = uiGridConstants.dataChange;
3594 if ( type === constants.ALL ||
3595 type === constants.COLUMN ||
3596 type === constants.EDIT ||
3597 type === constants.ROW ){
3598 grid.callDataChangeCallbacks( type );
3600 gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type);
3607 * @name columnRefreshCallback
3608 * @methodOf ui.grid.class:Grid
3609 * @description refreshes the grid when a column refresh
3610 * is notified, which triggers handling of the visible flag.
3611 * This is called on uiGridConstants.dataChange.COLUMN, and is
3612 * registered as a dataChangeCallback in grid.js
3613 * @param {string} name column name
3615 Grid.prototype.columnRefreshCallback = function columnRefreshCallback( grid ){
3622 * @name processRowsCallback
3623 * @methodOf ui.grid.class:Grid
3624 * @description calls the row processors, specifically
3625 * intended to reset the sorting when an edit is called,
3626 * registered as a dataChangeCallback on uiGridConstants.dataChange.EDIT
3627 * @param {string} name column name
3629 Grid.prototype.processRowsCallback = function processRowsCallback( grid ){
3637 * @methodOf ui.grid.class:Grid
3638 * @description returns a grid column for the column name
3639 * @param {string} name column name
3641 Grid.prototype.getColumn = function getColumn(name) {
3642 var columns = this.columns.filter(function (column) {
3643 return column.colDef.name === name;
3645 return columns.length > 0 ? columns[0] : null;
3651 * @methodOf ui.grid.class:Grid
3652 * @description returns a grid colDef for the column name
3653 * @param {string} name column.field
3655 Grid.prototype.getColDef = function getColDef(name) {
3656 var colDefs = this.options.columnDefs.filter(function (colDef) {
3657 return colDef.name === name;
3659 return colDefs.length > 0 ? colDefs[0] : null;
3665 * @methodOf ui.grid.class:Grid
3666 * @description uses the first row of data to assign colDef.type for any types not defined.
3671 * @propertyOf ui.grid.class:GridOptions.columnDef
3672 * @description the type of the column, used in sorting. If not provided then the
3673 * grid will guess the type. Add this only if the grid guessing is not to your
3674 * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for
3675 * a list of values the grid knows about.
3678 Grid.prototype.assignTypes = function(){
3680 self.options.columnDefs.forEach(function (colDef, index) {
3682 //Assign colDef type if not specified
3684 var col = new GridColumn(colDef, index, self);
3685 var firstRow = self.rows.length > 0 ? self.rows[0] : null;
3687 colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
3690 gridUtil.logWarn('Unable to assign type from data, so defaulting to string');
3691 colDef.type = 'string';
3699 * @name addRowHeaderColumn
3700 * @methodOf ui.grid.class:Grid
3701 * @description adds a row header column to the grid
3702 * @param {object} column def
3704 Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) {
3706 //self.createLeftContainer();
3707 var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length, self);
3708 rowHeaderCol.isRowHeader = true;
3710 self.createRightContainer();
3711 rowHeaderCol.renderContainer = 'right';
3714 self.createLeftContainer();
3715 rowHeaderCol.renderContainer = 'left';
3718 // relies on the default column builder being first in array, as it is instantiated
3719 // as part of grid creation
3720 self.columnBuilders[0](colDef,rowHeaderCol,self.options)
3722 rowHeaderCol.enableFiltering = false;
3723 rowHeaderCol.enableSorting = false;
3724 rowHeaderCol.enableHiding = false;
3725 self.rowHeaderColumns.push(rowHeaderCol);
3728 self.preCompileCellTemplates();
3729 self.handleWindowResize();
3736 * @name buildColumns
3737 * @methodOf ui.grid.class:Grid
3738 * @description creates GridColumn objects from the columnDefinition. Calls each registered
3739 * columnBuilder to further process the column
3740 * @returns {Promise} a promise to load any needed column resources
3742 Grid.prototype.buildColumns = function buildColumns() {
3743 // gridUtil.logDebug('buildColumns');
3745 var builderPromises = [];
3746 var headerOffset = self.rowHeaderColumns.length;
3749 // Remove any columns for which a columnDef cannot be found
3750 // Deliberately don't use forEach, as it doesn't like splice being called in the middle
3751 // Also don't cache columns.length, as it will change during this operation
3752 for (i = 0; i < self.columns.length; i++){
3753 if (!self.getColDef(self.columns[i].name)) {
3754 self.columns.splice(i, 1);
3759 //add row header columns to the grid columns array _after_ columns without columnDefs have been removed
3760 self.rowHeaderColumns.forEach(function (rowHeaderColumn) {
3761 self.columns.unshift(rowHeaderColumn);
3765 // look at each column def, and update column properties to match. If the column def
3766 // doesn't have a column, then splice in a new gridCol
3767 self.options.columnDefs.forEach(function (colDef, index) {
3768 self.preprocessColDef(colDef);
3769 var col = self.getColumn(colDef.name);
3772 col = new GridColumn(colDef, gridUtil.nextUid(), self);
3773 self.columns.splice(index + headerOffset, 0, col);
3776 col.updateColumnDef(colDef);
3779 self.columnBuilders.forEach(function (builder) {
3780 builderPromises.push(builder.call(self, colDef, col, self.options));
3784 return $q.all(builderPromises);
3789 * @name preCompileCellTemplates
3790 * @methodOf ui.grid.class:Grid
3791 * @description precompiles all cell templates
3793 Grid.prototype.preCompileCellTemplates = function() {
3795 this.columns.forEach(function (col) {
3796 var html = col.cellTemplate.replace(uiGridConstants.MODEL_COL_FIELD, self.getQualifiedColField(col));
3797 html = html.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
3800 var compiledElementFn = $compile(html);
3801 col.compiledElementFn = compiledElementFn;
3803 if (col.compiledElementFnDefer) {
3804 col.compiledElementFnDefer.resolve(col.compiledElementFn);
3811 * @name getGridQualifiedColField
3812 * @methodOf ui.grid.class:Grid
3813 * @description Returns the $parse-able accessor for a column within its $scope
3814 * @param {GridColumn} col col object
3816 Grid.prototype.getQualifiedColField = function (col) {
3817 return 'row.entity.' + gridUtil.preEval(col.field);
3822 * @name createLeftContainer
3823 * @methodOf ui.grid.class:Grid
3824 * @description creates the left render container if it doesn't already exist
3826 Grid.prototype.createLeftContainer = function() {
3827 if (!this.hasLeftContainer()) {
3828 this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
3834 * @name createRightContainer
3835 * @methodOf ui.grid.class:Grid
3836 * @description creates the right render container if it doesn't already exist
3838 Grid.prototype.createRightContainer = function() {
3839 if (!this.hasRightContainer()) {
3840 this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
3846 * @name hasLeftContainer
3847 * @methodOf ui.grid.class:Grid
3848 * @description returns true if leftContainer exists
3850 Grid.prototype.hasLeftContainer = function() {
3851 return this.renderContainers.left !== undefined;
3856 * @name hasLeftContainer
3857 * @methodOf ui.grid.class:Grid
3858 * @description returns true if rightContainer exists
3860 Grid.prototype.hasRightContainer = function() {
3861 return this.renderContainers.right !== undefined;
3866 * undocumented function
3867 * @name preprocessColDef
3868 * @methodOf ui.grid.class:Grid
3869 * @description defaults the name property from field to maintain backwards compatibility with 2.x
3870 * validates that name or field is present
3872 Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
3873 if (!colDef.field && !colDef.name) {
3874 throw new Error('colDef.name or colDef.field property is required');
3877 //maintain backwards compatibility with 2.x
3878 //field was required in 2.x. now name is required
3879 if (colDef.name === undefined && colDef.field !== undefined) {
3880 colDef.name = colDef.field;
3885 // Return a list of items that exist in the `n` array but not the `o` array. Uses optional property accessors passed as third & fourth parameters
3886 Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
3890 for (var i = 0; i < n.length; i++) {
3891 var nV = nAccessor ? n[i][nAccessor] : n[i];
3894 for (var j = 0; j < o.length; j++) {
3895 var oV = oAccessor ? o[j][oAccessor] : o[j];
3896 if (self.options.rowEquality(nV, oV)) {
3912 * @methodOf ui.grid.class:Grid
3913 * @description returns the GridRow that contains the rowEntity
3914 * @param {object} rowEntity the gridOptions.data array element instance
3916 Grid.prototype.getRow = function getRow(rowEntity) {
3917 var rows = this.rows.filter(function (row) {
3918 return row.entity === rowEntity;
3920 return rows.length > 0 ? rows[0] : null;
3927 * @methodOf ui.grid.class:Grid
3928 * @description creates or removes GridRow objects from the newRawData array. Calls each registered
3929 * rowBuilder to further process the row
3931 * Rows are identified using the gridOptions.rowEquality function
3933 Grid.prototype.modifyRows = function modifyRows(newRawData) {
3939 if ((self.options.useExternalSorting || self.getColumnSorting().length === 0) && newRawData.length > 0) {
3940 var oldRowHash = self.rowHashMap;
3942 oldRowHash = {get: function(){return null;}};
3944 self.createRowHashMap();
3945 rowhash = self.rowHashMap;
3946 var wasEmpty = self.rows.length === 0;
3947 self.rows.length = 0;
3948 for (i = 0; i < newRawData.length; i++) {
3949 var newRawRow = newRawData[i];
3950 found = oldRowHash.get(newRawRow);
3955 newRow = self.processRowBuilders(new GridRow(newRawRow, i, self));
3957 self.rows.push(newRow);
3958 rowhash.put(newRawRow, {
3964 //now that we have data, it is save to assign types to colDefs
3969 if (self.rows.length === 0 && newRawData.length > 0) {
3970 if (self.options.enableRowHashing) {
3971 if (!self.rowHashMap) {
3972 self.createRowHashMap();
3975 for (i = 0; i < newRawData.length; i++) {
3976 newRow = newRawData[i];
3978 self.rowHashMap.put(newRow, {
3985 self.addRows(newRawData);
3986 //now that we have data, it is save to assign types to colDefs
3989 else if (newRawData.length > 0) {
3990 var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind;
3992 // If row hashing is turned on
3993 if (self.options.enableRowHashing) {
3994 // Array of new rows that haven't been found in the old rowset
3995 unfoundNewRows = [];
3996 // Array of new rows that we explicitly HAVE to search for manually in the old row set. They cannot be looked up by their identity (because it doesn't exist).
3997 unfoundNewRowsToFind = [];
3998 // Map of rows that have been found in the new rowset
3999 var foundOldRows = {};
4000 // Array of old rows that have NOT been found in the new rowset
4001 unfoundOldRows = [];
4003 // Create the row HashMap if it doesn't exist already
4004 if (!self.rowHashMap) {
4005 self.createRowHashMap();
4007 rowhash = self.rowHashMap;
4009 // Make sure every new row has a hash
4010 for (i = 0; i < newRawData.length; i++) {
4011 newRow = newRawData[i];
4013 // Flag this row as needing to be manually found if it didn't come in with a $$hashKey
4014 var mustFind = false;
4015 if (!self.options.getRowIdentity(newRow)) {
4019 // See if the new row is already in the rowhash
4020 found = rowhash.get(newRow);
4023 // See if it's already being used by as GridRow
4025 // If so, mark this new row as being found
4026 foundOldRows[self.options.rowIdentity(newRow)] = true;
4030 // Put the row in the hashmap with the index it corresponds to
4031 rowhash.put(newRow, {
4036 // This row has to be searched for manually in the old row set
4038 unfoundNewRowsToFind.push(newRow);
4041 unfoundNewRows.push(newRow);
4046 // Build the list of unfound old rows
4047 for (i = 0; i < self.rows.length; i++) {
4048 var row = self.rows[i];
4049 var hash = self.options.rowIdentity(row.entity);
4050 if (!foundOldRows[hash]) {
4051 unfoundOldRows.push(row);
4056 // Look for new rows
4057 var newRows = unfoundNewRows || [];
4059 // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't
4060 var unfoundNew = (unfoundNewRowsToFind || newRawData);
4062 // Search for real new rows in `unfoundNew` and concat them onto `newRows`
4063 newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity'));
4065 self.addRows(newRows);
4067 var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData);
4069 for (i = 0; i < deletedRows.length; i++) {
4070 if (self.options.enableRowHashing) {
4071 self.rowHashMap.remove(deletedRows[i].entity);
4074 self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 );
4079 // Reset the row HashMap
4080 self.createRowHashMap();
4082 // Reset the rows length!
4083 self.rows.length = 0;
4087 var p1 = $q.when(self.processRowsProcessors(self.rows))
4088 .then(function (renderableRows) {
4089 return self.setVisibleRows(renderableRows);
4092 var p2 = $q.when(self.processColumnsProcessors(self.columns))
4093 .then(function (renderableColumns) {
4094 return self.setVisibleColumns(renderableColumns);
4097 return $q.all([p1, p2]);
4100 Grid.prototype.getDeletedRows = function(oldRows, newRows) {
4103 var olds = oldRows.filter(function (oldRow) {
4104 return !newRows.some(function (newItem) {
4105 return self.options.rowEquality(newItem, oldRow.entity);
4108 // var olds = self.newInN(newRows, oldRows, null, 'entity');
4109 // dump('olds', olds);
4114 * Private Undocumented Method
4116 * @methodOf ui.grid.class:Grid
4117 * @description adds the newRawData array of rows to the grid and calls all registered
4118 * rowBuilders. this keyword will reference the grid
4120 Grid.prototype.addRows = function addRows(newRawData) {
4123 var existingRowCount = self.rows.length;
4124 for (var i = 0; i < newRawData.length; i++) {
4125 var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
4127 if (self.options.enableRowHashing) {
4128 var found = self.rowHashMap.get(newRow.entity);
4134 self.rows.push(newRow);
4140 * @name processRowBuilders
4141 * @methodOf ui.grid.class:Grid
4142 * @description processes all RowBuilders for the gridRow
4143 * @param {GridRow} gridRow reference to gridRow
4144 * @returns {GridRow} the gridRow with all additional behavior added
4146 Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
4149 self.rowBuilders.forEach(function (builder) {
4150 builder.call(self, gridRow, self.options);
4158 * @name registerStyleComputation
4159 * @methodOf ui.grid.class:Grid
4160 * @description registered a styleComputation function
4162 * If the function returns a value it will be appended into the grid's `<style>` block
4163 * @param {function($scope)} styleComputation function
4165 Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
4166 this.styleComputations.push(styleComputationInfo);
4170 // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
4171 // Grid.prototype.registerRowFilter = function(filter) {
4172 // // TODO(c0bra): validate filter?
4174 // this.rowFilters.push(filter);
4177 // Grid.prototype.removeRowFilter = function(filter) {
4178 // var idx = this.rowFilters.indexOf(filter);
4180 // if (typeof(idx) !== 'undefined' && idx !== undefined) {
4181 // this.rowFilters.slice(idx, 1);
4185 // Grid.prototype.processRowFilters = function(rows) {
4187 // self.rowFilters.forEach(function (filter) {
4188 // filter.call(self, rows);
4195 * @name registerRowsProcessor
4196 * @methodOf ui.grid.class:Grid
4197 * @param {function(renderableRows)} rows processor function
4198 * @returns {Array[GridRow]} Updated renderable rows
4201 Register a "rows processor" function. When the rows are updated,
4202 the grid calls each registered "rows processor", which has a chance
4203 to alter the set of rows (sorting, etc) as long as the count is not
4206 Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor) {
4207 if (!angular.isFunction(processor)) {
4208 throw 'Attempt to register non-function rows processor: ' + processor;
4211 this.rowsProcessors.push(processor);
4216 * @name removeRowsProcessor
4217 * @methodOf ui.grid.class:Grid
4218 * @param {function(renderableRows)} rows processor function
4219 * @description Remove a registered rows processor
4221 Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
4222 var idx = this.rowsProcessors.indexOf(processor);
4224 if (typeof(idx) !== 'undefined' && idx !== undefined) {
4225 this.rowsProcessors.splice(idx, 1);
4230 * Private Undocumented Method
4231 * @name processRowsProcessors
4232 * @methodOf ui.grid.class:Grid
4233 * @param {Array[GridRow]} The array of "renderable" rows
4234 * @param {Array[GridColumn]} The array of columns
4235 * @description Run all the registered rows processors on the array of renderable rows
4237 Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
4240 // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4241 var myRenderableRows = renderableRows.slice(0);
4243 // self.rowsProcessors.forEach(function (processor) {
4244 // myRenderableRows = processor.call(self, myRenderableRows, self.columns);
4246 // if (!renderableRows) {
4247 // throw "Processor at index " + i + " did not return a set of renderable rows";
4250 // if (!angular.isArray(renderableRows)) {
4251 // throw "Processor at index " + i + " did not return an array";
4257 // Return myRenderableRows with no processing if we have no rows processors
4258 if (self.rowsProcessors.length === 0) {
4259 return $q.when(myRenderableRows);
4262 // Counter for iterating through rows processors
4265 // Promise for when we're done with all the processors
4266 var finished = $q.defer();
4268 // This function will call the processor in self.rowsProcessors at index 'i', and then
4269 // when done will call the next processor in the list, using the output from the processor
4270 // at i as the argument for 'renderedRowsToProcess' on the next iteration.
4272 // If we're at the end of the list of processors, we resolve our 'finished' callback with
4274 function startProcessor(i, renderedRowsToProcess) {
4275 // Get the processor at 'i'
4276 var processor = self.rowsProcessors[i];
4278 // Call the processor, passing in the rows to process and the current columns
4279 // (note: it's wrapped in $q.when() in case the processor does not return a promise)
4280 return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
4281 .then(function handleProcessedRows(processedRows) {
4283 if (!processedRows) {
4284 throw "Processor at index " + i + " did not return a set of renderable rows";
4287 if (!angular.isArray(processedRows)) {
4288 throw "Processor at index " + i + " did not return an array";
4291 // Processor is done, increment the counter
4294 // If we're not done with the processors, call the next one
4295 if (i <= self.rowsProcessors.length - 1) {
4296 return startProcessor(i, processedRows);
4298 // We're done! Resolve the 'finished' promise
4300 finished.resolve(processedRows);
4305 // Start on the first processor
4306 startProcessor(0, myRenderableRows);
4308 return finished.promise;
4311 Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
4312 // gridUtil.logDebug('setVisibleRows');
4316 //var newVisibleRowCache = [];
4318 // Reset all the render container row caches
4319 for (var i in self.renderContainers) {
4320 var container = self.renderContainers[i];
4322 container.visibleRowCache.length = 0;
4325 // rows.forEach(function (row) {
4326 for (var ri = 0; ri < rows.length; ri++) {
4329 // If the row is visible
4331 // newVisibleRowCache.push(row);
4333 // If the row has a container specified
4334 if (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) {
4335 self.renderContainers[row.renderContainer].visibleRowCache.push(row);
4337 // If not, put it into the body container
4339 self.renderContainers.body.visibleRowCache.push(row);
4347 * @name registerColumnsProcessor
4348 * @methodOf ui.grid.class:Grid
4349 * @param {function(renderableColumns)} rows processor function
4350 * @returns {Array[GridColumn]} Updated renderable columns
4353 Register a "columns processor" function. When the columns are updated,
4354 the grid calls each registered "columns processor", which has a chance
4355 to alter the set of columns, as long as the count is not modified.
4357 Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor) {
4358 if (!angular.isFunction(processor)) {
4359 throw 'Attempt to register non-function rows processor: ' + processor;
4362 this.columnsProcessors.push(processor);
4365 Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
4366 var idx = this.columnsProcessors.indexOf(processor);
4368 if (typeof(idx) !== 'undefined' && idx !== undefined) {
4369 this.columnsProcessors.splice(idx, 1);
4373 Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
4376 // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
4377 var myRenderableColumns = renderableColumns.slice(0);
4379 // Return myRenderableRows with no processing if we have no rows processors
4380 if (self.columnsProcessors.length === 0) {
4381 return $q.when(myRenderableColumns);
4384 // Counter for iterating through rows processors
4387 // Promise for when we're done with all the processors
4388 var finished = $q.defer();
4390 // This function will call the processor in self.rowsProcessors at index 'i', and then
4391 // when done will call the next processor in the list, using the output from the processor
4392 // at i as the argument for 'renderedRowsToProcess' on the next iteration.
4394 // If we're at the end of the list of processors, we resolve our 'finished' callback with
4396 function startProcessor(i, renderedColumnsToProcess) {
4397 // Get the processor at 'i'
4398 var processor = self.columnsProcessors[i];
4400 // Call the processor, passing in the rows to process and the current columns
4401 // (note: it's wrapped in $q.when() in case the processor does not return a promise)
4402 return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
4403 .then(function handleProcessedRows(processedColumns) {
4405 if (!processedColumns) {
4406 throw "Processor at index " + i + " did not return a set of renderable rows";
4409 if (!angular.isArray(processedColumns)) {
4410 throw "Processor at index " + i + " did not return an array";
4413 // Processor is done, increment the counter
4416 // If we're not done with the processors, call the next one
4417 if (i <= self.columnsProcessors.length - 1) {
4418 return startProcessor(i, myRenderableColumns);
4420 // We're done! Resolve the 'finished' promise
4422 finished.resolve(myRenderableColumns);
4427 // Start on the first processor
4428 startProcessor(0, myRenderableColumns);
4430 return finished.promise;
4433 Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
4434 // gridUtil.logDebug('setVisibleColumns');
4438 // Reset all the render container row caches
4439 for (var i in self.renderContainers) {
4440 var container = self.renderContainers[i];
4442 container.visibleColumnCache.length = 0;
4445 for (var ci = 0; ci < columns.length; ci++) {
4446 var column = columns[ci];
4448 // If the column is visible
4449 if (column.visible) {
4450 // If the column has a container specified
4451 if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
4452 self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
4454 // If not, put it into the body container
4456 self.renderContainers.body.visibleColumnCache.push(column);
4464 * @name handleWindowResize
4465 * @methodOf ui.grid.class:Grid
4466 * @description Triggered when the browser window resizes; automatically resizes the grid
4468 Grid.prototype.handleWindowResize = function handleWindowResize($event) {
4471 self.gridWidth = gridUtil.elementWidth(self.element);
4472 self.gridHeight = gridUtil.elementHeight(self.element);
4474 self.queueRefresh();
4479 * @name queueRefresh
4480 * @methodOf ui.grid.class:Grid
4481 * @description todo: @c0bra can you document this method?
4483 Grid.prototype.queueRefresh = function queueRefresh() {
4486 if (self.refreshCanceller) {
4487 $timeout.cancel(self.refreshCanceller);
4490 self.refreshCanceller = $timeout(function () {
4491 self.refreshCanvas(true);
4494 self.refreshCanceller.then(function () {
4495 self.refreshCanceller = null;
4498 return self.refreshCanceller;
4504 * @methodOf ui.grid.class:Grid
4505 * @description calls each styleComputation function
4507 // TODO: this used to take $scope, but couldn't see that it was used
4508 Grid.prototype.buildStyles = function buildStyles() {
4509 // gridUtil.logDebug('buildStyles');
4513 self.customStyles = '';
4515 self.styleComputations
4516 .sort(function(a, b) {
4517 if (a.priority === null) { return 1; }
4518 if (b.priority === null) { return -1; }
4519 if (a.priority === null && b.priority === null) { return 0; }
4520 return a.priority - b.priority;
4522 .forEach(function (compInfo) {
4523 // this used to provide $scope as a second parameter, but I couldn't find any
4524 // style builders that used it, so removed it as part of moving to grid from controller
4525 var ret = compInfo.func.call(self);
4527 if (angular.isString(ret)) {
4528 self.customStyles += '\n' + ret;
4534 Grid.prototype.minColumnsToRender = function minColumnsToRender() {
4536 var viewport = this.getViewportWidth();
4540 self.columns.forEach(function(col, i) {
4541 if (totalWidth < viewport) {
4542 totalWidth += col.drawnWidth;
4547 for (var j = i; j >= i - min; j--) {
4548 currWidth += self.columns[j].drawnWidth;
4550 if (currWidth < viewport) {
4559 Grid.prototype.getBodyHeight = function getBodyHeight() {
4560 // Start with the viewportHeight
4561 var bodyHeight = this.getViewportHeight();
4563 // Add the horizontal scrollbar height if there is one
4564 if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
4565 bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
4571 // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
4572 // TODO(c0bra): account for footer height
4573 Grid.prototype.getViewportHeight = function getViewportHeight() {
4576 var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
4578 // Account for native horizontal scrollbar, if present
4579 if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
4580 viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
4583 var adjustment = self.getViewportAdjustment();
4585 viewPortHeight = viewPortHeight + adjustment.height;
4587 // gridUtil.logDebug('viewPortHeight', viewPortHeight);
4589 return viewPortHeight;
4592 Grid.prototype.getViewportWidth = function getViewportWidth() {
4595 var viewPortWidth = this.gridWidth;
4597 if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
4598 viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
4601 var adjustment = self.getViewportAdjustment();
4603 viewPortWidth = viewPortWidth + adjustment.width;
4605 // gridUtil.logDebug('getviewPortWidth', viewPortWidth);
4607 return viewPortWidth;
4610 Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
4611 var viewPortWidth = this.getViewportWidth();
4613 if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
4614 viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
4617 return viewPortWidth;
4620 Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
4621 this.viewportAdjusters.push(func);
4624 Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
4625 var idx = this.viewportAdjusters.indexOf(func);
4627 if (typeof(idx) !== 'undefined' && idx !== undefined) {
4628 this.viewportAdjusters.splice(idx, 1);
4632 Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
4635 var adjustment = { height: 0, width: 0 };
4637 self.viewportAdjusters.forEach(function (func) {
4638 adjustment = func.call(this, adjustment);
4644 Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
4647 // this.rows.forEach(function (row) {
4648 // if (row.visible) {
4653 // return this.visibleRowCache.length;
4654 return this.renderContainers.body.visibleRowCache.length;
4657 Grid.prototype.getVisibleRows = function getVisibleRows() {
4658 return this.renderContainers.body.visibleRowCache;
4661 Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
4664 // this.rows.forEach(function (row) {
4665 // if (row.visible) {
4670 // return this.visibleRowCache.length;
4671 return this.renderContainers.body.visibleColumnCache.length;
4675 Grid.prototype.searchRows = function searchRows(renderableRows) {
4676 return rowSearcher.search(this, renderableRows, this.columns);
4679 Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
4680 return rowSorter.sort(this, renderableRows, this.columns);
4685 * @name getCellValue
4686 * @methodOf ui.grid.class:Grid
4687 * @description Gets the value of a cell for a particular row and column
4688 * @param {GridRow} row Row to access
4689 * @param {GridColumn} col Column to access
4691 Grid.prototype.getCellValue = function getCellValue(row, col){
4694 if (!self.cellValueGetterCache[col.colDef.name]) {
4695 self.cellValueGetterCache[col.colDef.name] = $parse(row.getEntityQualifiedColField(col));
4698 return self.cellValueGetterCache[col.colDef.name](row);
4702 Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
4706 self.columns.forEach(function (col) {
4707 if (col.sort && col.sort.priority && col.sort.priority > p) {
4708 p = col.sort.priority;
4717 * @name resetColumnSorting
4718 * @methodOf ui.grid.class:Grid
4719 * @description Return the columns that the grid is currently being sorted by
4720 * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
4722 Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
4725 self.columns.forEach(function (col) {
4726 if (col !== excludeCol) {
4734 * @name getColumnSorting
4735 * @methodOf ui.grid.class:Grid
4736 * @description Return the columns that the grid is currently being sorted by
4737 * @returns {Array[GridColumn]} An array of GridColumn objects
4739 Grid.prototype.getColumnSorting = function getColumnSorting() {
4742 var sortedCols = [], myCols;
4744 // Iterate through all the columns, sorted by priority
4745 // Make local copy of column list, because sorting is in-place and we do not want to
4746 // change the original sequence of columns
4747 myCols = self.columns.slice(0);
4748 myCols.sort(rowSorter.prioritySort).forEach(function (col) {
4749 if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
4750 sortedCols.push(col);
4760 * @methodOf ui.grid.class:Grid
4761 * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
4762 * Emits the sortChanged event whenever the sort criteria are changed.
4763 * @param {GridColumn} column Column to set the sorting on
4764 * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
4765 * If not provided, the column will iterate through the sort directions: ascending, descending, unsorted.
4766 * @param {boolean} [add] Add this column to the sorting. If not provided or set to `false`, the Grid will reset any existing sorting and sort
4767 * by this column only
4768 * @returns {Promise} A resolved promise that supplies the column.
4771 Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
4775 if (typeof(column) === 'undefined' || !column) {
4776 throw new Error('No column parameter provided');
4779 // Second argument can either be a direction or whether to add this column to the existing sort.
4780 // If it's a boolean, it's an add, otherwise, it's a direction
4781 if (typeof(directionOrAdd) === 'boolean') {
4782 add = directionOrAdd;
4785 direction = directionOrAdd;
4789 self.resetColumnSorting(column);
4790 column.sort.priority = 0;
4793 column.sort.priority = self.getNextColumnSortPriority();
4797 // Figure out the sort direction
4798 if (column.sort.direction && column.sort.direction === uiGridConstants.ASC) {
4799 column.sort.direction = uiGridConstants.DESC;
4801 else if (column.sort.direction && column.sort.direction === uiGridConstants.DESC) {
4802 if ( column.colDef && column.colDef.suppressRemoveSort ){
4803 column.sort.direction = uiGridConstants.ASC;
4805 column.sort.direction = null;
4809 column.sort.direction = uiGridConstants.ASC;
4813 column.sort.direction = direction;
4816 self.api.core.raise.sortChanged( self, self.getColumnSorting() );
4818 return $q.when(column);
4822 * communicate to outside world that we are done with initial rendering
4824 Grid.prototype.renderingComplete = function(){
4825 if (angular.isFunction(this.options.onRegisterApi)) {
4826 this.options.onRegisterApi(this.api);
4828 this.api.core.raise.renderingComplete( this.api );
4831 Grid.prototype.createRowHashMap = function createRowHashMap() {
4834 var hashMap = new RowHashMap();
4835 hashMap.grid = self;
4837 self.rowHashMap = hashMap;
4844 * @methodOf ui.grid.class:Grid
4845 * @description Refresh the rendered grid on screen.
4848 Grid.prototype.refresh = function refresh() {
4849 gridUtil.logDebug('grid refresh');
4853 var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
4854 self.setVisibleRows(renderableRows);
4857 var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
4858 self.setVisibleColumns(renderableColumns);
4861 return $q.all([p1, p2]).then(function () {
4862 self.redrawInPlace();
4864 self.refreshCanvas(true);
4871 * @methodOf ui.grid.class:Grid
4872 * @description Refresh the rendered rows on screen? Note: not functional at present
4873 * @returns {promise} promise that is resolved when render completes?
4876 Grid.prototype.refreshRows = function refreshRows() {
4879 return self.processRowsProcessors(self.rows)
4880 .then(function (renderableRows) {
4881 self.setVisibleRows(renderableRows);
4883 self.redrawInPlace();
4885 self.refreshCanvas( true );
4891 * @name redrawCanvas
4892 * @methodOf ui.grid.class:Grid
4894 * @params {object} buildStyles optional parameter. Use TBD
4895 * @returns {promise} promise that is resolved when the canvas
4896 * has been refreshed
4899 Grid.prototype.refreshCanvas = function(buildStyles) {
4908 // Get all the header heights
4909 var containerHeadersToRecalc = [];
4910 for (var containerId in self.renderContainers) {
4911 if (self.renderContainers.hasOwnProperty(containerId)) {
4912 var container = self.renderContainers[containerId];
4914 // Skip containers that have no canvasWidth set yet
4915 if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
4919 if (container.header) {
4920 containerHeadersToRecalc.push(container);
4925 if (containerHeadersToRecalc.length > 0) {
4926 // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
4927 $timeout(function() {
4928 // var oldHeaderHeight = self.grid.headerHeight;
4929 // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
4931 var rebuildStyles = false;
4933 // Get all the header heights
4936 for (i = 0; i < containerHeadersToRecalc.length; i++) {
4937 container = containerHeadersToRecalc[i];
4939 // Skip containers that have no canvasWidth set yet
4940 if (container.canvasWidth === null || isNaN(container.canvasWidth)) {
4944 if (container.header) {
4945 var oldHeaderHeight = container.headerHeight;
4946 var headerHeight = gridUtil.outerElementHeight(container.header);
4948 container.headerHeight = parseInt(headerHeight, 10);
4950 if (oldHeaderHeight !== headerHeight) {
4951 rebuildStyles = true;
4954 // Get the "inner" header height, that is the height minus the top and bottom borders, if present. We'll use it to make sure all the headers have a consistent height
4955 var topBorder = gridUtil.getBorderSize(container.header, 'top');
4956 var bottomBorder = gridUtil.getBorderSize(container.header, 'bottom');
4957 var innerHeaderHeight = parseInt(headerHeight - topBorder - bottomBorder, 10);
4959 innerHeaderHeight = innerHeaderHeight < 0 ? 0 : innerHeaderHeight;
4961 container.innerHeaderHeight = innerHeaderHeight;
4963 // Save the largest header height for use later
4964 if (innerHeaderHeight > maxHeight) {
4965 maxHeight = innerHeaderHeight;
4970 // Go through all the headers
4971 for (i = 0; i < containerHeadersToRecalc.length; i++) {
4972 container = containerHeadersToRecalc[i];
4974 // If this header's height is less than another header's height, then explicitly set it so they're the same and one isn't all offset and weird looking
4975 if (container.headerHeight < maxHeight) {
4976 container.explicitHeaderHeight = maxHeight;
4980 // Rebuild styles if the header height has changed
4981 // The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
4982 if (buildStyles && rebuildStyles) {
4990 // Timeout still needs to be here to trigger digest after styles have been rebuilt
4991 $timeout(function() {
5002 * @name redrawCanvas
5003 * @methodOf ui.grid.class:Grid
5004 * @description Redraw the rows and columns based on our current scroll position
5007 Grid.prototype.redrawInPlace = function redrawInPlace() {
5008 // gridUtil.logDebug('redrawInPlace');
5012 for (var i in self.renderContainers) {
5013 var container = self.renderContainers[i];
5015 // gridUtil.logDebug('redrawing container', i);
5017 container.adjustRows(container.prevScrollTop, null);
5018 container.adjustColumns(container.prevScrollLeft, null);
5023 // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
5024 function RowHashMap() {}
5026 RowHashMap.prototype = {
5028 * Store key value pair
5029 * @param key key to store can be any type
5030 * @param value value to store can be any type
5032 put: function(key, value) {
5033 this[this.grid.options.rowIdentity(key)] = value;
5038 * @returns {Object} the value for the key
5040 get: function(key) {
5041 return this[this.grid.options.rowIdentity(key)];
5045 * Remove the key/value pair
5048 remove: function(key) {
5049 var value = this[key = this.grid.options.rowIdentity(key)];
5065 angular.module('ui.grid')
5066 .factory('GridApi', ['$q', '$rootScope', 'gridUtil', 'uiGridConstants', 'GridRow', 'uiGridGridMenuService',
5067 function ($q, $rootScope, gridUtil, uiGridConstants, GridRow, uiGridGridMenuService) {
5070 * @name ui.grid.class:GridApi
5071 * @description GridApi provides the ability to register public methods events inside the grid and allow
5072 * for other components to use the api via featureName.methodName and featureName.on.eventName(function(args){}
5073 * @param {object} grid grid that owns api
5075 var GridApi = function GridApi(grid) {
5077 this.listeners = [];
5081 * @name renderingComplete
5082 * @methodOf ui.grid.core.api:PublicApi
5083 * @description Rendering is complete, called at the same
5084 * time as `onRegisterApi`, but provides a way to obtain
5085 * that same event within features without stopping end
5086 * users from getting at the onRegisterApi method.
5088 * Included in gridApi so that it's always there - otherwise
5089 * there is still a timing problem with when a feature can
5092 * @param {GridApi} gridApi the grid api, as normally
5093 * returned in the onRegisterApi method
5097 * gridApi.core.on.renderingComplete( grid );
5100 this.registerEvent( 'core', 'renderingComplete' );
5104 * @name filterChanged
5105 * @eventOf ui.grid.core.api:PublicApi
5106 * @description is raised after the filter is changed. The nature
5107 * of the watch expression doesn't allow notification of what changed,
5108 * so the receiver of this event will need to re-extract the filter
5109 * conditions from the columns.
5112 this.registerEvent( 'core', 'filterChanged' );
5116 * @name setRowInvisible
5117 * @methodOf ui.grid.core.api:PublicApi
5118 * @description Sets an override on the row to make it always invisible,
5119 * which will override any filtering or other visibility calculations.
5120 * If the row is currently visible then sets it to invisible and calls
5121 * both grid refresh and emits the rowsVisibleChanged event
5122 * @param {object} rowEntity gridOptions.data[] array instance
5124 this.registerMethod( 'core', 'setRowInvisible', GridRow.prototype.setRowInvisible );
5128 * @name clearRowInvisible
5129 * @methodOf ui.grid.core.api:PublicApi
5130 * @description Clears any override on visibility for the row so that it returns to
5131 * using normal filtering and other visibility calculations.
5132 * If the row is currently invisible then sets it to visible and calls
5133 * both grid refresh and emits the rowsVisibleChanged event
5134 * TODO: if a filter is active then we can't just set it to visible?
5135 * @param {object} rowEntity gridOptions.data[] array instance
5137 this.registerMethod( 'core', 'clearRowInvisible', GridRow.prototype.clearRowInvisible );
5141 * @name getVisibleRows
5142 * @methodOf ui.grid.core.api:PublicApi
5143 * @description Returns all visible rows
5144 * @param {Grid} grid the grid you want to get visible rows from
5145 * @returns {array} an array of gridRow
5147 this.registerMethod( 'core', 'getVisibleRows', this.grid.getVisibleRows );
5151 * @name rowsVisibleChanged
5152 * @eventOf ui.grid.core.api:PublicApi
5153 * @description is raised after the rows that are visible
5154 * change. The filtering is zero-based, so it isn't possible
5155 * to say which rows changed (unlike in the selection feature).
5156 * We can plausibly know which row was changed when setRowInvisible
5157 * is called, but in that situation the user already knows which row
5158 * they changed. When a filter runs we don't know what changed,
5159 * and that is the one that would have been useful.
5162 this.registerEvent( 'core', 'rowsVisibleChanged' );
5167 * @name ui.grid.class:suppressEvents
5168 * @methodOf ui.grid.class:GridApi
5169 * @description Used to execute a function while disabling the specified event listeners.
5170 * Disables the listenerFunctions, executes the callbackFn, and then enables
5171 * the listenerFunctions again
5172 * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
5173 * functions that were used in the .on.eventName method
5174 * @param {object} callBackFn function to execute
5177 * var navigate = function (newRowCol, oldRowCol){
5178 * //do something on navigate
5181 * gridApi.cellNav.on.navigate(scope,navigate);
5184 * //call the scrollTo event and suppress our navigate listener
5185 * //scrollTo will still raise the event for other listeners
5186 * gridApi.suppressEvents(navigate, function(){
5187 * gridApi.cellNav.scrollTo(aRow, aCol);
5192 GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
5194 var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
5196 //find all registered listeners
5197 var foundListeners = [];
5198 listeners.forEach(function (l) {
5199 foundListeners = self.listeners.filter(function (lstnr) {
5200 return l === lstnr.handler;
5204 //deregister all the listeners
5205 foundListeners.forEach(function(l){
5211 //reregister all the listeners
5212 foundListeners.forEach(function(l){
5213 l.dereg = registerEventWithAngular(l.scope, l.eventId, l.handler, self.grid);
5220 * @name registerEvent
5221 * @methodOf ui.grid.class:GridApi
5222 * @description Registers a new event for the given feature
5223 * @param {string} featureName name of the feature that raises the event
5224 * @param {string} eventName name of the event
5226 GridApi.prototype.registerEvent = function (featureName, eventName) {
5228 if (!self[featureName]) {
5229 self[featureName] = {};
5232 var feature = self[featureName];
5238 var eventId = self.grid.id + featureName + eventName;
5240 // gridUtil.logDebug('Creating raise event method ' + featureName + '.raise.' + eventName);
5241 feature.raise[eventName] = function () {
5242 $rootScope.$broadcast.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
5245 // gridUtil.logDebug('Creating on event method ' + featureName + '.on.' + eventName);
5246 feature.on[eventName] = function (scope, handler) {
5247 var dereg = registerEventWithAngular(scope, eventId, handler, self.grid);
5249 //track our listener so we can turn off and on
5250 var listener = {handler: handler, dereg: dereg, eventId: eventId, scope: scope};
5251 self.listeners.push(listener);
5253 //destroy tracking when scope is destroyed
5254 //wanted to remove the listener from the array but angular does
5255 //strange things in scope.$destroy so I could not access the listener array
5256 scope.$on('$destroy', function() {
5257 listener.dereg = null;
5258 listener.handler = null;
5259 listener.eventId = null;
5260 listener.scope = null;
5265 function registerEventWithAngular(scope, eventId, handler, grid) {
5266 return scope.$on(eventId, function (event) {
5267 var args = Array.prototype.slice.call(arguments);
5268 args.splice(0, 1); //remove evt argument
5269 handler.apply(grid.api, args);
5275 * @name registerEventsFromObject
5276 * @methodOf ui.grid.class:GridApi
5277 * @description Registers features and events from a simple objectMap.
5278 * eventObjectMap must be in this format (multiple features allowed)
5282 * eventNameOne:function(args){},
5283 * eventNameTwo:function(args){}
5287 * @param {object} eventObjectMap map of feature/event names
5289 GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
5292 angular.forEach(eventObjectMap, function (featProp, featPropName) {
5293 var feature = {name: featPropName, events: []};
5294 angular.forEach(featProp, function (prop, propName) {
5295 feature.events.push(propName);
5297 features.push(feature);
5300 features.forEach(function (feature) {
5301 feature.events.forEach(function (event) {
5302 self.registerEvent(feature.name, event);
5310 * @name registerMethod
5311 * @methodOf ui.grid.class:GridApi
5312 * @description Registers a new event for the given feature
5313 * @param {string} featureName name of the feature
5314 * @param {string} methodName name of the method
5315 * @param {object} callBackFn function to execute
5316 * @param {object} thisArg binds callBackFn 'this' to thisArg. Defaults to gridApi.grid
5318 GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, thisArg) {
5319 if (!this[featureName]) {
5320 this[featureName] = {};
5323 var feature = this[featureName];
5325 feature[methodName] = gridUtil.createBoundedWrapper(thisArg || this.grid, callBackFn);
5330 * @name registerMethodsFromObject
5331 * @methodOf ui.grid.class:GridApi
5332 * @description Registers features and methods from a simple objectMap.
5333 * eventObjectMap must be in this format (multiple features allowed)
5337 * methodNameOne:function(args){},
5338 * methodNameTwo:function(args){}
5340 * @param {object} eventObjectMap map of feature/event names
5341 * @param {object} thisArg binds this to thisArg for all functions. Defaults to gridApi.grid
5343 GridApi.prototype.registerMethodsFromObject = function (methodMap, thisArg) {
5346 angular.forEach(methodMap, function (featProp, featPropName) {
5347 var feature = {name: featPropName, methods: []};
5348 angular.forEach(featProp, function (prop, propName) {
5349 feature.methods.push({name: propName, fn: prop});
5351 features.push(feature);
5354 features.forEach(function (feature) {
5355 feature.methods.forEach(function (method) {
5356 self.registerMethod(feature.name, method.name, method.fn, thisArg);
5370 angular.module('ui.grid')
5371 .factory('GridColumn', ['gridUtil', 'uiGridConstants', 'i18nService', function(gridUtil, uiGridConstants, i18nService) {
5375 * @name ui.grid.class:GridColumn
5376 * @description Represents the viewModel for each column. Any state or methods needed for a Grid Column
5377 * are defined on this prototype
5378 * @param {ColDef} colDef Column definition.
5379 * @param {number} index the current position of the column in the array
5380 * @param {Grid} grid reference to the grid
5384 * ******************************************************************************************
5385 * PaulL1: Ugly hack here in documentation. These properties are clearly properties of GridColumn,
5386 * and need to be noted as such for those extending and building ui-grid itself.
5387 * However, from an end-developer perspective, they interact with all these through columnDefs,
5388 * and they really need to be documented there. I feel like they're relatively static, and
5389 * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
5390 * comment block. Ugh.
5397 * @propertyOf ui.grid.class:GridColumn
5398 * @description (mandatory) each column should have a name, although for backward
5399 * compatibility with 2.x name can be omitted if field is present
5406 * @propertyOf ui.grid.class:GridOptions.columnDef
5407 * @description (mandatory) each column should have a name, although for backward
5408 * compatibility with 2.x name can be omitted if field is present
5415 * @propertyOf ui.grid.class:GridColumn
5416 * @description Column name that will be shown in the header. If displayName is not
5417 * provided then one is generated using the name.
5424 * @propertyOf ui.grid.class:GridOptions.columnDef
5425 * @description Column name that will be shown in the header. If displayName is not
5426 * provided then one is generated using the name.
5433 * @propertyOf ui.grid.class:GridColumn
5434 * @description field must be provided if you wish to bind to a
5435 * property in the data source. Should be an angular expression that evaluates against grid.options.data
5436 * array element. Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
5437 * See the angular docs on binding expressions.
5444 * @propertyOf ui.grid.class:GridOptions.columnDef
5445 * @description field must be provided if you wish to bind to a
5446 * property in the data source. Should be an angular expression that evaluates against grid.options.data
5447 * array element. Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
5448 * See the angular docs on binding expressions.
5455 * @propertyOf ui.grid.class:GridColumn
5456 * @description Filter on this column.
5458 * <pre>{ term: 'text', condition: uiGridConstants.filter.STARTS_WITH, placeholder: 'type to filter...' }</pre>
5465 * @propertyOf ui.grid.class:GridOptions.columnDef
5466 * @description Specify a single filter field on this column.
5468 * <pre>$scope.gridOptions.columnDefs = [
5472 * condition: uiGridConstants.filter.STARTS_WITH,
5473 * placeholder: 'starts with...'
5482 * @methodOf ui.grid.class:GridColumn
5484 * @description Initializes a gridColumn
5485 * @param {ColumnDef} colDef the column def to associate with this column
5486 * @param {number} uid the unique and immutable uid we'd like to allocate to this column
5487 * @param {Grid} grid the grid we'd like to create this column in
5489 function GridColumn(colDef, uid, grid) {
5495 self.updateColumnDef(colDef);
5501 * @methodOf ui.grid.class:GridColumn
5502 * @name setPropertyOrDefault
5503 * @description Sets a property on the column using the passed in columnDef, and
5504 * setting the defaultValue if the value cannot be found on the colDef
5505 * @param {ColumnDef} colDef the column def to look in for the property value
5506 * @param {string} propName the property name we'd like to set
5507 * @param {object} defaultValue the value to use if the colDef doesn't provide the setting
5509 GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
5512 // Use the column definition filter if we were passed it
5513 if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
5514 self[propName] = colDef[propName];
5516 // Otherwise use our own if it's set
5517 else if (typeof(self[propName]) !== 'undefined') {
5518 self[propName] = self[propName];
5520 // Default to empty object for the filter
5522 self[propName] = defaultValue ? defaultValue : {};
5531 * @propertyOf ui.grid.class:GridOptions.columnDef
5532 * @description sets the column width. Can be either
5533 * a number or a percentage, or an * for auto.
5535 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
5536 * { field: 'field2', width: '20%'},
5537 * { field: 'field3', width: '*' }]; </pre>
5544 * @propertyOf ui.grid.class:GridOptions.columnDef
5545 * @description sets the minimum column width. Should be a number.
5547 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
5554 * @propertyOf ui.grid.class:GridOptions.columnDef
5555 * @description sets the maximum column width. Should be a number.
5557 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
5564 * @propertyOf ui.grid.class:GridOptions.columnDef
5565 * @description sets whether or not the column is visible
5566 * </br>Default is true
5568 * <pre> $scope.gridOptions.columnDefs = [
5569 * { field: 'field1', visible: true},
5570 * { field: 'field2', visible: false }
5578 * @propertyOf ui.grid.class:GridOptions.columnDef
5579 * @description Can be used to set the sort direction for the column, values are
5580 * uiGridConstants.ASC or uiGridConstants.DESC
5582 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', sort: { direction: uiGridConstants.ASC }}] </pre>
5588 * @name sortingAlgorithm
5589 * @propertyOf ui.grid.class:GridColumn
5590 * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
5591 * like any normal sorting function.
5597 * @name sortingAlgorithm
5598 * @propertyOf ui.grid.class:GridOptions.columnDef
5599 * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
5600 * like any normal sorting function.
5607 * @propertyOf ui.grid.class:GridOptions.columnDef
5608 * @description Specify multiple filter fields.
5610 * <pre>$scope.gridOptions.columnDefs = [
5612 * field: 'field1', filters: [
5614 * condition: uiGridConstants.filter.STARTS_WITH,
5615 * placeholder: 'starts with...'
5618 * condition: uiGridConstants.filter.ENDS_WITH,
5619 * placeholder: 'ends with...'
5631 * @propertyOf ui.grid.class:GridColumn
5632 * @description Filters for this column. Includes 'term' property bound to filter input elements.
5636 * term: 'foo', // ngModel for <input>
5637 * condition: uiGridConstants.filter.STARTS_WITH,
5638 * placeholder: 'starts with...'
5642 * condition: uiGridConstants.filter.ENDS_WITH,
5643 * placeholder: 'ends with...'
5653 * @propertyOf ui.grid.class:GridOptions.columnDef
5654 * @description used to add menu items to a column. Refer to the tutorial on this
5655 * functionality. A number of settings are supported:
5657 * - title: controls the title that is displayed in the menu
5658 * - icon: the icon shown alongside that title
5659 * - action: the method to call when the menu is clicked
5660 * - shown: a function to evaluate to determine whether or not to show the item
5661 * - active: a function to evaluate to determine whether or not the item is currently selected
5662 * - context: context to pass to the action function??
5664 * <pre> $scope.gridOptions.columnDefs = [
5665 * { field: 'field1', menuItems: [
5667 * title: 'Outer Scope Alert',
5668 * icon: 'ui-grid-icon-info-circled',
5669 * action: function($event) {
5670 * this.context.blargh(); // $scope.blargh() would work too, this is just an example
5672 * shown: function() { return true; },
5673 * active: function() { return true; },
5678 * action: function() {
5679 * alert('Grid ID: ' + this.grid.id);
5688 * @methodOf ui.grid.class:GridColumn
5689 * @name updateColumnDef
5690 * @description Moves settings from the columnDef down onto the column,
5691 * and sets properties as appropriate
5692 * @param {ColumnDef} colDef the column def to look in for the property value
5694 GridColumn.prototype.updateColumnDef = function(colDef) {
5697 self.colDef = colDef;
5699 if (colDef.name === undefined) {
5700 throw new Error('colDef.name is required for column at index ' + self.grid.options.columnDefs.indexOf(colDef));
5703 var parseErrorMsg = "Cannot parse column width '" + colDef.width + "' for column named '" + colDef.name + "'";
5705 // If width is not defined, set it to a single star
5706 if (gridUtil.isNullOrUndefined(self.width) || !angular.isNumber(self.width)) {
5707 if (gridUtil.isNullOrUndefined(colDef.width)) {
5711 // If the width is not a number
5712 if (!angular.isNumber(colDef.width)) {
5713 // See if it ends with a percent
5714 if (gridUtil.endsWith(colDef.width, '%')) {
5715 // If so we should be able to parse the non-percent-sign part to a number
5716 var percentStr = colDef.width.replace(/%/g, '');
5717 var percent = parseInt(percentStr, 10);
5718 if (isNaN(percent)) {
5719 throw new Error(parseErrorMsg);
5721 self.width = colDef.width;
5723 // And see if it's a number string
5724 else if (colDef.width.match(/^(\d+)$/)) {
5725 self.width = parseInt(colDef.width.match(/^(\d+)$/)[1], 10);
5727 // Otherwise it should be a string of asterisks
5728 else if (colDef.width.match(/^\*+$/)) {
5729 self.width = colDef.width;
5731 // No idea, throw an Error
5733 throw new Error(parseErrorMsg);
5736 // Is a number, use it as the width
5738 self.width = colDef.width;
5743 // Remove this column from the grid sorting
5744 GridColumn.prototype.unsort = function () {
5746 self.grid.api.core.raise.sortChanged( self, self.grid.getColumnSorting() );
5749 self.minWidth = !colDef.minWidth ? 30 : colDef.minWidth;
5750 self.maxWidth = !colDef.maxWidth ? 9000 : colDef.maxWidth;
5752 //use field if it is defined; name if it is not
5753 self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
5755 if ( typeof( self.field ) !== 'string' ){
5756 gridUtil.logError( 'Field is not a string, this is likely to break the code, Field is: ' + self.field );
5759 self.name = colDef.name;
5761 // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
5762 self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
5764 //self.originalIndex = index;
5766 self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
5767 self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
5771 * @name footerCellClass
5772 * @propertyOf ui.grid.class:GridOptions.columnDef
5773 * @description footerCellClass can be a string specifying the class to append to a cell
5774 * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
5777 self.footerCellClass = colDef.footerCellClass;
5782 * @propertyOf ui.grid.class:GridOptions.columnDef
5783 * @description cellClass can be a string specifying the class to append to a cell
5784 * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
5787 self.cellClass = colDef.cellClass;
5791 * @name headerCellClass
5792 * @propertyOf ui.grid.class:GridOptions.columnDef
5793 * @description headerCellClass can be a string specifying the class to append to a cell
5794 * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
5797 self.headerCellClass = colDef.headerCellClass;
5802 * @propertyOf ui.grid.class:GridOptions.columnDef
5803 * @description cellFilter is a filter to apply to the content of each cell
5806 * gridOptions.columnDefs[0].cellFilter = 'date'
5809 self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
5813 * @name headerCellFilter
5814 * @propertyOf ui.grid.class:GridOptions.columnDef
5815 * @description headerCellFilter is a filter to apply to the content of the column header
5818 * gridOptions.columnDefs[0].headerCellFilter = 'translate'
5821 self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
5823 self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
5825 self.headerClass = colDef.headerClass;
5826 //self.cursor = self.sortable ? 'pointer' : 'default';
5828 self.visible = true;
5830 // Turn on sorting by default
5831 self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : true;
5832 self.sortingAlgorithm = colDef.sortingAlgorithm;
5834 // Turn on filtering by default (it's disabled by default at the Grid level)
5835 self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
5837 // self.menuItems = colDef.menuItems;
5838 self.setPropertyOrDefault(colDef, 'menuItems', []);
5840 // Use the column definition sort if we were passed it
5841 self.setPropertyOrDefault(colDef, 'sort');
5843 // Set up default filters array for when one is not provided.
5844 // In other words, this (in column def):
5846 // filter: { term: 'something', flags: {}, condition: [CONDITION] }
5848 // is just shorthand for this:
5850 // filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
5852 var defaultFilters = [];
5853 if (colDef.filter) {
5854 defaultFilters.push(colDef.filter);
5856 else if (self.enableFiltering && self.grid.options.enableFiltering) {
5857 // Add an empty filter definition object, which will
5858 // translate to a guessed condition and no pre-populated
5859 // value for the filter <input>.
5860 defaultFilters.push({});
5865 * @name ui.grid.class:GridOptions.columnDef.filter
5866 * @propertyOf ui.grid.class:GridOptions.columnDef
5867 * @description An object defining filtering for a column.
5873 * @propertyOf ui.grid.class:GridOptions.columnDef.filter
5874 * @description Defines how rows are chosen as matching the filter term. This can be set to
5875 * one of the constants in uiGridConstants.filter, or you can supply a custom filter function
5876 * that gets passed the following arguments: [searchTerm, cellValue, row, column].
5882 * @propertyOf ui.grid.class:GridOptions.columnDef.filter
5883 * @description If set, the filter field will be pre-populated
5890 * @propertyOf ui.grid.class:GridOptions.columnDef.filter
5891 * @description String that will be set to the <input>.placeholder attribute.
5899 condition: uiGridConstants.filter.CONTAINS,
5900 placeholder: 'my placeholder',
5909 self.setPropertyOrDefault(colDef, 'filter');
5910 self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
5919 * @methodOf ui.grid.class:GridColumn
5920 * @description Returns the class name for the column
5921 * @param {bool} prefixDot if true, will return .className instead of className
5923 GridColumn.prototype.getColClass = function (prefixDot) {
5924 var cls = uiGridConstants.COL_CLASS_PREFIX + this.uid;
5926 return prefixDot ? '.' + cls : cls;
5931 * @name getColClassDefinition
5932 * @methodOf ui.grid.class:GridColumn
5933 * @description Returns the class definition for th column
5935 GridColumn.prototype.getColClassDefinition = function () {
5936 return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { width: ' + this.drawnWidth + 'px; }';
5941 * @name getRenderContainer
5942 * @methodOf ui.grid.class:GridColumn
5943 * @description Returns the render container object that this column belongs to.
5945 * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
5947 GridColumn.prototype.getRenderContainer = function getRenderContainer() {
5950 var containerId = self.renderContainer;
5952 if (containerId === null || containerId === '' || containerId === undefined) {
5953 containerId = 'body';
5956 return self.grid.renderContainers[containerId];
5962 * @methodOf ui.grid.class:GridColumn
5963 * @description Makes the column visible by setting colDef.visible = true
5965 GridColumn.prototype.showColumn = function() {
5966 this.colDef.visible = true;
5972 * @methodOf ui.grid.class:GridColumn
5973 * @description Hides the column by setting colDef.visible = false
5975 GridColumn.prototype.hideColumn = function() {
5976 this.colDef.visible = false;
5981 * @name getAggregationValue
5982 * @methodOf ui.grid.class:GridColumn
5983 * @description gets the aggregation value based on the aggregation type for this column
5985 GridColumn.prototype.getAggregationValue = function () {
5988 var visibleRows = self.grid.getVisibleRows();
5989 var cellValues = [];
5990 angular.forEach(visibleRows, function (row) {
5991 var cellValue = self.grid.getCellValue(row, self);
5992 if (angular.isNumber(cellValue)) {
5993 cellValues.push(cellValue);
5996 if (angular.isFunction(self.aggregationType)) {
5997 return self.aggregationType(visibleRows, self);
5999 else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
6000 return self.getAggregationText('aggregation.count', self.grid.getVisibleRowCount());
6002 else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
6003 angular.forEach(cellValues, function (value) {
6006 return self.getAggregationText('aggregation.sum', result);
6008 else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
6009 angular.forEach(cellValues, function (value) {
6012 result = result / cellValues.length;
6013 return self.getAggregationText('aggregation.avg', result);
6015 else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
6016 return self.getAggregationText('aggregation.min', Math.min.apply(null, cellValues));
6018 else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
6019 return self.getAggregationText('aggregation.max', Math.max.apply(null, cellValues));
6028 * @name aggregationHideLabel
6029 * @propertyOf ui.grid.class:GridOptions.columnDef
6030 * @description defaults to false, if set to true hides the label text
6031 * in the aggregation footer, so only the value is displayed.
6036 * @name getAggregationText
6037 * @methodOf ui.grid.class:GridColumn
6038 * @description converts the aggregation value into a text string, including
6039 * i18n and deciding whether or not to display based on colDef.aggregationHideLabel
6041 * @param {string} label the i18n lookup value to use for the column label
6042 * @param {number} value the calculated aggregate value for this column
6045 GridColumn.prototype.getAggregationText = function ( label, value ) {
6047 if ( self.colDef.aggregationHideLabel ){
6050 return i18nService.getSafeText(label) + value;
6054 GridColumn.prototype.getCellTemplate = function () {
6057 return self.cellTemplatePromise;
6060 GridColumn.prototype.getCompiledElementFn = function () {
6063 return self.compiledElementFnDefer.promise;
6073 angular.module('ui.grid')
6074 .factory('GridOptions', ['gridUtil','uiGridConstants', function(gridUtil,uiGridConstants) {
6078 * @name ui.grid.class:GridOptions
6079 * @description Default GridOptions class. GridOptions are defined by the application developer and overlaid
6080 * over this object. Setting gridOptions within your controller is the most common method for an application
6081 * developer to configure the behaviour of their ui-grid
6083 * @example To define your gridOptions within your controller:
6084 * <pre>$scope.gridOptions = {
6085 * data: $scope.myData,
6087 * { name: 'field1', displayName: 'pretty display name' },
6088 * { name: 'field2', visible: false }
6092 * You can then use this within your html template, when you define your grid:
6093 * <pre><div ui-grid="gridOptions"></div></pre>
6095 * To provide default options for all of the grids within your application, use an angular
6096 * decorator to modify the GridOptions factory.
6097 * <pre>app.config(function($provide){
6098 * $provide.decorator('GridOptions',function($delegate){
6099 * return function(){
6100 * var defaultOptions = new $delegate();
6101 * defaultOptions.excludeProperties = ['id' ,'$$hashKey'];
6102 * return defaultOptions;
6108 initialize: function( baseOptions ){
6109 baseOptions.onRegisterApi = baseOptions.onRegisterApi || angular.noop();
6114 * @propertyOf ui.grid.class:GridOptions
6115 * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for
6116 * the grid. The most common case is an array of objects, where each object has a number of attributes.
6117 * Each attribute automatically becomes a column in your grid. This array could, for example, be sourced from
6118 * an angularJS $resource query request. The array can also contain complex objects.
6121 baseOptions.data = baseOptions.data || [];
6126 * @propertyOf ui.grid.class:GridOptions
6127 * @description Array of columnDef objects. Only required property is name.
6128 * The individual options available in columnDefs are documented in the
6129 * {@link ui.grid.class:GridOptions.columnDef columnDef} section
6130 * </br>_field property can be used in place of name for backwards compatibility with 2.x_
6133 * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
6136 baseOptions.columnDefs = baseOptions.columnDefs || [];
6140 * @name ui.grid.class:GridOptions.columnDef
6141 * @description Definition / configuration of an individual column, which would typically be
6142 * one of many column definitions within the gridOptions.columnDefs array
6144 * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
6151 * @name excludeProperties
6152 * @propertyOf ui.grid.class:GridOptions
6153 * @description Array of property names in data to ignore when auto-generating column names. Provides the
6154 * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
6157 * If columnDefs is defined, this will be ignored.
6159 * Defaults to ['$$hashKey']
6162 baseOptions.excludeProperties = baseOptions.excludeProperties || ['$$hashKey'];
6166 * @name enableRowHashing
6167 * @propertyOf ui.grid.class:GridOptions
6168 * @description True by default. When enabled, this setting allows uiGrid to add
6169 * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
6170 * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
6172 * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
6173 * you do not want this at all you can disable this setting but you will take a performance hit if you are using large numbers of rows
6174 * and are altering the data set often.
6176 baseOptions.enableRowHashing = baseOptions.enableRowHashing !== false;
6181 * @methodOf ui.grid.class:GridOptions
6182 * @description This function is used to get and, if necessary, set the value uniquely identifying this row.
6184 * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
6186 baseOptions.rowIdentity = baseOptions.rowIdentity || function rowIdentity(row) {
6187 return gridUtil.hashKey(row);
6192 * @name getRowIdentity
6193 * @methodOf ui.grid.class:GridOptions
6194 * @description This function returns the identity value uniquely identifying this row.
6196 * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
6198 baseOptions.getRowIdentity = baseOptions.getRowIdentity || function getRowIdentity(row) {
6199 return row.$$hashKey;
6204 * @name headerRowHeight
6205 * @propertyOf ui.grid.class:GridOptions
6206 * @description The height of the header in pixels, defaults to 30
6209 baseOptions.headerRowHeight = typeof(baseOptions.headerRowHeight) !== "undefined" ? baseOptions.headerRowHeight : 30;
6214 * @propertyOf ui.grid.class:GridOptions
6215 * @description The height of the row in pixels, defaults to 30
6218 baseOptions.rowHeight = baseOptions.rowHeight || 30;
6222 * @name maxVisibleRowCount
6223 * @propertyOf ui.grid.class:GridOptions
6224 * @description Defaults to 200
6227 baseOptions.maxVisibleRowCount = baseOptions.maxVisibleRowCount || 200;
6231 * @name minRowsToShow
6232 * @propertyOf ui.grid.class:GridOptions
6233 * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
6235 baseOptions.minRowsToShow = typeof(baseOptions.minRowsToShow) !== "undefined" ? baseOptions.minRowsToShow : 10;
6240 * @propertyOf ui.grid.class:GridOptions
6241 * @description Whether or not to show the footer, defaults to false
6244 baseOptions.showFooter = baseOptions.showFooter === true;
6248 * @name footerRowHeight
6249 * @propertyOf ui.grid.class:GridOptions
6250 * @description The height of the footer in pixels
6253 baseOptions.footerRowHeight = typeof(baseOptions.footerRowHeight) !== "undefined" ? baseOptions.footerRowHeight : 30;
6255 baseOptions.columnWidth = typeof(baseOptions.columnWidth) !== "undefined" ? baseOptions.columnWidth : 50;
6259 * @name maxVisibleColumnCount
6260 * @propertyOf ui.grid.class:GridOptions
6261 * @description Defaults to 200
6264 baseOptions.maxVisibleColumnCount = typeof(baseOptions.maxVisibleColumnCount) !== "undefined" ? baseOptions.maxVisibleColumnCount : 200;
6268 * @name virtualizationThreshold
6269 * @propertyOf ui.grid.class:GridOptions
6270 * @description Turn virtualization on when number of data elements goes over this number, defaults to 20
6272 baseOptions.virtualizationThreshold = typeof(baseOptions.virtualizationThreshold) !== "undefined" ? baseOptions.virtualizationThreshold : 20;
6276 * @name columnVirtualizationThreshold
6277 * @propertyOf ui.grid.class:GridOptions
6278 * @description Turn virtualization on when number of columns goes over this number, defaults to 10
6280 baseOptions.columnVirtualizationThreshold = typeof(baseOptions.columnVirtualizationThreshold) !== "undefined" ? baseOptions.columnVirtualizationThreshold : 10;
6285 * @propertyOf ui.grid.class:GridOptions
6286 * @description Extra rows to to render outside of the viewport, which helps with smoothness of scrolling.
6289 baseOptions.excessRows = typeof(baseOptions.excessRows) !== "undefined" ? baseOptions.excessRows : 4;
6292 * @name scrollThreshold
6293 * @propertyOf ui.grid.class:GridOptions
6294 * @description Defaults to 4
6296 baseOptions.scrollThreshold = typeof(baseOptions.scrollThreshold) !== "undefined" ? baseOptions.scrollThreshold : 4;
6300 * @name excessColumns
6301 * @propertyOf ui.grid.class:GridOptions
6302 * @description Extra columns to to render outside of the viewport, which helps with smoothness of scrolling.
6305 baseOptions.excessColumns = typeof(baseOptions.excessColumns) !== "undefined" ? baseOptions.excessColumns : 4;
6308 * @name horizontalScrollThreshold
6309 * @propertyOf ui.grid.class:GridOptions
6310 * @description Defaults to 4
6312 baseOptions.horizontalScrollThreshold = typeof(baseOptions.horizontalScrollThreshold) !== "undefined" ? baseOptions.horizontalScrollThreshold : 2;
6316 * @name scrollThrottle
6317 * @propertyOf ui.grid.class:GridOptions
6318 * @description Default time to throttle scroll events to, defaults to 70ms
6320 baseOptions.scrollThrottle = typeof(baseOptions.scrollThrottle) !== "undefined" ? baseOptions.scrollThrottle : 70;
6324 * @name enableSorting
6325 * @propertyOf ui.grid.class:GridOptions
6326 * @description True by default. When enabled, this setting adds sort
6327 * widgets to the column headers, allowing sorting of the data for the entire grid.
6328 * Sorting can then be disabled on individual columns using the columnDefs.
6330 baseOptions.enableSorting = baseOptions.enableSorting !== false;
6334 * @name enableFiltering
6335 * @propertyOf ui.grid.class:GridOptions
6336 * @description False by default. When enabled, this setting adds filter
6337 * boxes to each column header, allowing filtering within the column for the entire grid.
6338 * Filtering can then be disabled on individual columns using the columnDefs.
6340 baseOptions.enableFiltering = baseOptions.enableFiltering === true;
6344 * @name enableColumnMenus
6345 * @propertyOf ui.grid.class:GridOptions
6346 * @description True by default. When enabled, this setting displays a column
6347 * menu within each column.
6349 baseOptions.enableColumnMenus = baseOptions.enableColumnMenus !== false;
6353 * @name enableVerticalScrollbar
6354 * @propertyOf ui.grid.class:GridOptions
6355 * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the vertical scrollbar for the grid.
6356 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER, uiGridConstants.scrollbars.WHEN_NEEDED.
6358 baseOptions.enableVerticalScrollbar = typeof(baseOptions.enableVerticalScrollbar) !== "undefined" ? baseOptions.enableVerticalScrollbar : uiGridConstants.scrollbars.ALWAYS;
6362 * @name enableHorizontalScrollbar
6363 * @propertyOf ui.grid.class:GridOptions
6364 * @description uiGridConstants.scrollbars.ALWAYS by default. This settings controls the horizontal scrollbar for the grid.
6365 * Supported values: uiGridConstants.scrollbars.ALWAYS, uiGridConstants.scrollbars.NEVER, uiGridConstants.scrollbars.WHEN_NEEDED.
6367 baseOptions.enableHorizontalScrollbar = typeof(baseOptions.enableHorizontalScrollbar) !== "undefined" ? baseOptions.enableHorizontalScrollbar : uiGridConstants.scrollbars.ALWAYS;
6371 * @name minimumColumnSize
6372 * @propertyOf ui.grid.class:GridOptions
6373 * @description Columns can't be smaller than this, defaults to 10 pixels
6375 baseOptions.minimumColumnSize = typeof(baseOptions.minimumColumnSize) !== "undefined" ? baseOptions.minimumColumnSize : 10;
6380 * @methodOf ui.grid.class:GridOptions
6381 * @description By default, rows are compared using object equality. This option can be overridden
6382 * to compare on any data item property or function
6383 * @param {object} entityA First Data Item to compare
6384 * @param {object} entityB Second Data Item to compare
6386 baseOptions.rowEquality = baseOptions.rowEquality || function(entityA, entityB) {
6387 return entityA === entityB;
6392 * @name headerTemplate
6393 * @propertyOf ui.grid.class:GridOptions
6394 * @description Null by default. When provided, this setting uses a custom header
6395 * template, rather than the default template. Can be set to either the name of a template file:
6396 * <pre> $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
6398 * <pre> $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
6399 * or the id of a precompiled template (TBD how to use this).
6400 * </br>Refer to the custom header tutorial for more information.
6401 * If you want no header at all, you can set to an empty div:
6402 * <pre> $scope.gridOptions.headerTemplate = '<div></div>';</pre>
6404 * If you want to only have a static header, then you can set to static content. If
6405 * you want to tailor the existing column headers, then you should look at the
6406 * current 'ui-grid-header.html' template in github as your starting point.
6409 baseOptions.headerTemplate = baseOptions.headerTemplate || null;
6413 * @name footerTemplate
6414 * @propertyOf ui.grid.class:GridOptions
6415 * @description (optional) Null by default. When provided, this setting uses a custom footer
6416 * template. Can be set to either the name of a template file 'footer_template.html', inline html
6417 * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
6418 * of a precompiled template (TBD how to use this). Refer to the custom footer tutorial for more information.
6420 baseOptions.footerTemplate = baseOptions.footerTemplate || null;
6425 * @propertyOf ui.grid.class:GridOptions
6426 * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a
6427 * custom row template. Can be set to either the name of a template file:
6428 * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
6430 * <pre> $scope.gridOptions.rowTemplate = '<div style="background-color: aquamarine" ng-click="getExternalScopes().fnOne(row)" ng-repeat="col in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ui-grid-cell></div>';</pre>
6431 * or the id of a precompiled template (TBD how to use this) can be provided.
6432 * </br>Refer to the custom row template tutorial for more information.
6434 baseOptions.rowTemplate = baseOptions.rowTemplate || 'ui-grid/ui-grid-row';
6447 angular.module('ui.grid')
6451 * @name ui.grid.class:GridRenderContainer
6452 * @description The grid has render containers, allowing the ability to have pinned columns. If the grid
6453 * is right-to-left then there may be a right render container, if left-to-right then there may
6454 * be a left render container. There is always a body render container.
6455 * @param {string} name The name of the render container ('body', 'left', or 'right')
6456 * @param {Grid} grid the grid the render container is in
6457 * @param {object} options the render container options
6459 .factory('GridRenderContainer', ['gridUtil', function(gridUtil) {
6460 function GridRenderContainer(name, grid, options) {
6463 // if (gridUtil.type(grid) !== 'Grid') {
6464 // throw new Error('Grid argument is not a Grid object');
6471 // self.rowCache = [];
6472 // self.columnCache = [];
6474 self.visibleRowCache = [];
6475 self.visibleColumnCache = [];
6477 self.renderedRows = [];
6478 self.renderedColumns = [];
6480 self.prevScrollTop = 0;
6481 self.prevScrolltopPercentage = 0;
6482 self.prevRowScrollIndex = 0;
6484 self.prevScrollLeft = 0;
6485 self.prevScrollleftPercentage = 0;
6486 self.prevColumnScrollIndex = 0;
6488 self.columnStyles = "";
6490 self.viewportAdjusters = [];
6492 if (options && angular.isObject(options)) {
6493 angular.extend(self, options);
6496 grid.registerStyleComputation({
6499 return self.columnStyles;
6504 // GridRenderContainer.prototype.addRenderable = function addRenderable(renderable) {
6505 // this.renderables.push(renderable);
6508 GridRenderContainer.prototype.reset = function reset() {
6509 // this.rowCache.length = 0;
6510 // this.columnCache.length = 0;
6512 this.visibleColumnCache.length = 0;
6513 this.visibleRowCache.length = 0;
6515 this.renderedRows.length = 0;
6516 this.renderedColumns.length = 0;
6519 // TODO(c0bra): calculate size?? Should this be in a stackable directive?
6521 GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
6524 var rowAddedHeight = 0;
6525 var viewPortHeight = self.getViewportHeight();
6526 for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
6527 rowAddedHeight += self.visibleRowCache[i].height;
6533 GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
6535 var viewportWidth = this.getViewportWidth();
6539 // self.columns.forEach(function(col, i) {
6540 for (var i = 0; i < self.visibleColumnCache.length; i++) {
6541 var col = self.visibleColumnCache[i];
6543 if (totalWidth < viewportWidth) {
6544 totalWidth += col.drawnWidth ? col.drawnWidth : 0;
6549 for (var j = i; j >= i - min; j--) {
6550 currWidth += self.visibleColumnCache[j].drawnWidth ? self.visibleColumnCache[j].drawnWidth : 0;
6552 if (currWidth < viewportWidth) {
6561 GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
6562 return this.visibleRowCache.length;
6567 * @name registerViewportAdjuster
6568 * @methodOf ui.grid.class:GridRenderContainer
6569 * @description Registers an adjuster to the render container's available width or height. Adjusters are used
6570 * to tell the render container that there is something else consuming space, and to adjust it's size
6572 * @param {function} func the adjuster function we want to register
6575 GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
6576 this.viewportAdjusters.push(func);
6581 * @name removeViewportAdjuster
6582 * @methodOf ui.grid.class:GridRenderContainer
6583 * @description Removes an adjuster, should be used when your element is destroyed
6584 * @param {function} func the adjuster function we want to remove
6586 GridRenderContainer.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
6587 var idx = this.viewportAdjusters.indexOf(func);
6589 if (typeof(idx) !== 'undefined' && idx !== undefined) {
6590 this.viewportAdjusters.splice(idx, 1);
6596 * @name getViewportAdjustment
6597 * @methodOf ui.grid.class:GridRenderContainer
6598 * @description Gets the adjustment based on the viewportAdjusters.
6599 * @returns {object} a hash of { height: x, width: y }. Usually the values will be negative
6601 GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
6604 var adjustment = { height: 0, width: 0 };
6606 self.viewportAdjusters.forEach(function (func) {
6607 adjustment = func.call(this, adjustment);
6613 GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
6616 var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
6618 var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
6620 // Account for native horizontal scrollbar, if present
6621 if (typeof(self.horizontalScrollbarHeight) !== 'undefined' && self.horizontalScrollbarHeight !== undefined && self.horizontalScrollbarHeight > 0) {
6622 viewPortHeight = viewPortHeight - self.horizontalScrollbarHeight;
6625 var adjustment = self.getViewportAdjustment();
6627 viewPortHeight = viewPortHeight + adjustment.height;
6629 return viewPortHeight;
6632 GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
6635 var viewPortWidth = self.grid.gridWidth;
6637 if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
6638 viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
6641 var adjustment = self.getViewportAdjustment();
6643 viewPortWidth = viewPortWidth + adjustment.width;
6645 return viewPortWidth;
6648 GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
6651 var viewPortWidth = this.getViewportWidth();
6653 if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
6654 viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
6657 // var adjustment = self.getViewportAdjustment();
6658 // viewPortWidth = viewPortWidth + adjustment.width;
6660 return viewPortWidth;
6663 GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
6668 self.visibleRowCache.forEach(function(row){
6672 if (typeof(self.grid.horizontalScrollbarHeight) !== 'undefined' && self.grid.horizontalScrollbarHeight !== undefined && self.grid.horizontalScrollbarHeight > 0) {
6673 ret = ret - self.grid.horizontalScrollbarHeight;
6679 GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
6682 var ret = self.canvasWidth;
6684 if (typeof(self.verticalScrollbarWidth) !== 'undefined' && self.verticalScrollbarWidth !== undefined && self.verticalScrollbarWidth > 0) {
6685 ret = ret - self.verticalScrollbarWidth;
6691 GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
6692 this.renderedRows.length = newRows.length;
6693 for (var i = 0; i < newRows.length; i++) {
6694 this.renderedRows[i] = newRows[i];
6698 GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
6702 this.renderedColumns.length = newColumns.length;
6703 for (var i = 0; i < newColumns.length; i++) {
6704 this.renderedColumns[i] = newColumns[i];
6707 this.updateColumnOffset();
6710 GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
6711 // Calculate the width of the columns on the left side that are no longer rendered.
6712 // That will be the offset for the columns as we scroll horizontally.
6713 var hiddenColumnsWidth = 0;
6714 for (var i = 0; i < this.currentFirstColumn; i++) {
6715 hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
6718 this.columnOffset = hiddenColumnsWidth;
6721 GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
6722 if (this.prevScrollTop === scrollTop && !force) {
6726 if (typeof(scrollTop) === 'undefined' || scrollTop === undefined || scrollTop === null) {
6727 scrollTop = (this.getCanvasHeight() - this.getCanvasWidth()) * scrollPercentage;
6730 this.adjustRows(scrollTop, scrollPercentage);
6732 this.prevScrollTop = scrollTop;
6733 this.prevScrolltopPercentage = scrollPercentage;
6735 this.grid.queueRefresh();
6738 GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
6739 if (this.prevScrollLeft === scrollLeft && !force) {
6743 if (typeof(scrollLeft) === 'undefined' || scrollLeft === undefined || scrollLeft === null) {
6744 scrollLeft = (this.getCanvasWidth() - this.getViewportWidth()) * scrollPercentage;
6747 this.adjustColumns(scrollLeft, scrollPercentage);
6749 this.prevScrollLeft = scrollLeft;
6750 this.prevScrollleftPercentage = scrollPercentage;
6752 this.grid.queueRefresh();
6755 GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage) {
6758 var minRows = self.minRowsToRender();
6760 var rowCache = self.visibleRowCache;
6762 var maxRowIndex = rowCache.length - minRows;
6763 self.maxRowIndex = maxRowIndex;
6765 var curRowIndex = self.prevRowScrollIndex;
6767 // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
6768 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
6769 scrollPercentage = scrollTop / self.getCanvasHeight();
6772 var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
6774 // Define a max row index that we can't scroll past
6775 if (rowIndex > maxRowIndex) {
6776 rowIndex = maxRowIndex;
6780 if (rowCache.length > self.grid.options.virtualizationThreshold) {
6781 // Have we hit the threshold going down?
6782 if (self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
6785 //Have we hit the threshold going up?
6786 if (self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
6790 var rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
6791 var rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
6793 newRange = [rangeStart, rangeEnd];
6796 var maxLen = self.visibleRowCache.length;
6797 newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
6800 self.updateViewableRowRange(newRange);
6802 self.prevRowScrollIndex = rowIndex;
6805 GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
6808 var minCols = self.minColumnsToRender();
6810 var columnCache = self.visibleColumnCache;
6811 var maxColumnIndex = columnCache.length - minCols;
6813 // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
6814 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
6815 scrollPercentage = scrollLeft / self.getCanvasWidth();
6818 var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
6820 // Define a max row index that we can't scroll past
6821 if (colIndex > maxColumnIndex) {
6822 colIndex = maxColumnIndex;
6826 if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
6827 // Have we hit the threshold going down?
6828 if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
6831 //Have we hit the threshold going up?
6832 if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
6836 var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
6837 var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
6839 newRange = [rangeStart, rangeEnd];
6842 var maxLen = self.visibleColumnCache.length;
6844 newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
6847 self.updateViewableColumnRange(newRange);
6849 self.prevColumnScrollIndex = colIndex;
6852 // Method for updating the visible rows
6853 GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
6854 // Slice out the range of rows from the data
6855 // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
6856 var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
6858 // Define the top-most rendered row
6859 this.currentTopRow = renderedRange[0];
6861 // TODO(c0bra): make this method!
6862 this.setRenderedRows(rowArr);
6865 // Method for updating the visible columns
6866 GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
6867 // Slice out the range of rows from the data
6868 // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
6869 var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
6871 // Define the left-most rendered columns
6872 this.currentFirstColumn = renderedRange[0];
6874 this.setRenderedColumns(columnArr);
6877 GridRenderContainer.prototype.rowStyle = function (index) {
6882 if (index === 0 && self.currentTopRow !== 0) {
6883 // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
6884 var hiddenRowWidth = (self.currentTopRow) * self.grid.options.rowHeight;
6886 // return { 'margin-top': hiddenRowWidth + 'px' };
6887 styles['margin-top'] = hiddenRowWidth + 'px';
6890 if (self.currentFirstColumn !== 0) {
6891 if (self.grid.isRTL()) {
6892 styles['margin-right'] = self.columnOffset + 'px';
6895 styles['margin-left'] = self.columnOffset + 'px';
6902 GridRenderContainer.prototype.columnStyle = function (index) {
6905 if (index === 0 && self.currentFirstColumn !== 0) {
6906 var offset = self.columnOffset;
6908 if (self.grid.isRTL()) {
6909 return { 'margin-right': offset + 'px' };
6912 return { 'margin-left': offset + 'px' };
6919 GridRenderContainer.prototype.updateColumnWidths = function () {
6922 var asterisksArray = [],
6928 // Get the width of the viewport
6929 var availableWidth = self.getViewportWidth();
6931 if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
6932 availableWidth = availableWidth + self.grid.verticalScrollbarWidth;
6935 // The total number of columns
6936 // var equalWidthColumnCount = columnCount = uiGridCtrl.grid.options.columnDefs.length;
6937 // var equalWidth = availableWidth / equalWidthColumnCount;
6939 // The last column we processed
6942 var manualWidthSum = 0;
6944 var canvasWidth = 0;
6949 // uiGridCtrl.grid.columns.forEach(function(column, i) {
6951 var columnCache = self.visibleColumnCache;
6953 columnCache.forEach(function(column, i) {
6954 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + i + ' { width: ' + equalWidth + 'px; left: ' + left + 'px; }';
6955 //var colWidth = (typeof(c.width) !== 'undefined' && c.width !== undefined) ? c.width : equalWidth;
6957 // Skip hidden columns
6958 if (!column.visible) { return; }
6963 if (!angular.isNumber(column.width)) {
6964 isPercent = isNaN(column.width) && gridUtil.endsWith(column.width, "%");
6967 if (angular.isString(column.width) && column.width.indexOf('*') !== -1) { // we need to save it until the end to do the calulations on the remaining width.
6968 asteriskNum = parseInt(asteriskNum + column.width.length, 10);
6970 asterisksArray.push(column);
6972 else if (isPercent) { // If the width is a percentage, save it until the very last.
6973 percentArray.push(column);
6975 else if (angular.isNumber(column.width)) {
6976 manualWidthSum = parseInt(manualWidthSum + column.width, 10);
6978 canvasWidth = parseInt(canvasWidth, 10) + parseInt(column.width, 10);
6980 column.drawnWidth = column.width;
6984 // Get the remaining width (available width subtracted by the manual widths sum)
6985 var remainingWidth = availableWidth - manualWidthSum;
6987 var i, column, colWidth;
6989 if (percentArray.length > 0) {
6990 // Pre-process to make sure they're all within any min/max values
6991 for (i = 0; i < percentArray.length; i++) {
6992 column = percentArray[i];
6994 var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
6996 colWidth = parseInt(percent * remainingWidth, 10);
6998 if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
6999 colWidth = column.colDef.minWidth;
7001 remainingWidth = remainingWidth - colWidth;
7003 canvasWidth += colWidth;
7004 column.drawnWidth = colWidth;
7006 // Remove this element from the percent array so it's not processed below
7007 percentArray.splice(i, 1);
7009 else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
7010 colWidth = column.colDef.maxWidth;
7012 remainingWidth = remainingWidth - colWidth;
7014 canvasWidth += colWidth;
7015 column.drawnWidth = colWidth;
7017 // Remove this element from the percent array so it's not processed below
7018 percentArray.splice(i, 1);
7022 percentArray.forEach(function(column) {
7023 var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
7024 var colWidth = parseInt(percent * remainingWidth, 10);
7026 canvasWidth += colWidth;
7028 column.drawnWidth = colWidth;
7032 if (asterisksArray.length > 0) {
7033 var asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
7035 // Pre-process to make sure they're all within any min/max values
7036 for (i = 0; i < asterisksArray.length; i++) {
7037 column = asterisksArray[i];
7039 colWidth = parseInt(asteriskVal * column.width.length, 10);
7041 if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
7042 colWidth = column.colDef.minWidth;
7044 remainingWidth = remainingWidth - colWidth;
7047 canvasWidth += colWidth;
7048 column.drawnWidth = colWidth;
7050 lastColumn = column;
7052 // Remove this element from the percent array so it's not processed below
7053 asterisksArray.splice(i, 1);
7055 else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
7056 colWidth = column.colDef.maxWidth;
7058 remainingWidth = remainingWidth - colWidth;
7061 canvasWidth += colWidth;
7062 column.drawnWidth = colWidth;
7064 // Remove this element from the percent array so it's not processed below
7065 asterisksArray.splice(i, 1);
7069 // Redo the asterisk value, as we may have removed columns due to width constraints
7070 asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
7072 asterisksArray.forEach(function(column) {
7073 var colWidth = parseInt(asteriskVal * column.width.length, 10);
7075 canvasWidth += colWidth;
7077 column.drawnWidth = colWidth;
7081 // If the grid width didn't divide evenly into the column widths and we have pixels left over, dole them out to the columns one by one to make everything fit
7082 var leftoverWidth = availableWidth - parseInt(canvasWidth, 10);
7084 if (leftoverWidth > 0 && canvasWidth > 0 && canvasWidth < availableWidth) {
7085 var variableColumn = false;
7086 // uiGridCtrl.grid.columns.forEach(function(col) {
7087 columnCache.forEach(function(col) {
7088 if (col.width && !angular.isNumber(col.width)) {
7089 variableColumn = true;
7093 if (variableColumn) {
7094 var remFn = function (column) {
7095 if (leftoverWidth > 0) {
7096 column.drawnWidth = column.drawnWidth + 1;
7097 canvasWidth = canvasWidth + 1;
7101 while (leftoverWidth > 0) {
7102 columnCache.forEach(remFn);
7107 if (canvasWidth < availableWidth) {
7108 canvasWidth = availableWidth;
7112 columnCache.forEach(function (column) {
7113 ret = ret + column.getColClassDefinition();
7116 // Add the vertical scrollbar width back in to the canvas width, it's taken out in getCanvasWidth
7117 if (self.grid.verticalScrollbarWidth) {
7118 canvasWidth = canvasWidth + self.grid.verticalScrollbarWidth;
7120 // canvasWidth = canvasWidth + 1;
7122 self.canvasWidth = parseInt(canvasWidth, 10);
7124 // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
7127 // Set this render container's column styles so they can be used in style computation
7128 this.columnStyles = ret;
7131 return GridRenderContainer;
7138 angular.module('ui.grid')
7139 .factory('GridRow', ['gridUtil', function(gridUtil) {
7143 * @name ui.grid.class:GridRow
7144 * @description GridRow is the viewModel for one logical row on the grid. A grid Row is not necessarily a one-to-one
7145 * relation to gridOptions.data.
7146 * @param {object} entity the array item from GridOptions.data
7147 * @param {number} index the current position of the row in the array
7148 * @param {Grid} reference to the parent grid
7150 function GridRow(entity, index, grid) {
7155 * @propertyOf ui.grid.class:GridRow
7156 * @description A reference back to the grid
7163 * @propertyOf ui.grid.class:GridRow
7164 * @description A reference to an item in gridOptions.data[]
7166 this.entity = entity;
7171 * @propertyOf ui.grid.class:GridRow
7172 * @description UniqueId of row
7174 this.uid = gridUtil.nextUid();
7179 * @propertyOf ui.grid.class:GridRow
7180 * @description If true, the row will be rendered
7183 this.visible = true;
7188 * @propertyOf ui.grid.class:GridRow
7189 * @description height of each individual row
7191 this.height = grid.options.rowHeight;
7196 * @name getQualifiedColField
7197 * @methodOf ui.grid.class:GridRow
7198 * @description returns the qualified field name as it exists on scope
7199 * ie: row.entity.fieldA
7200 * @param {GridCol} col column instance
7201 * @returns {string} resulting name that can be evaluated on scope
7203 GridRow.prototype.getQualifiedColField = function(col) {
7204 return 'row.' + this.getEntityQualifiedColField(col);
7209 * @name getEntityQualifiedColField
7210 * @methodOf ui.grid.class:GridRow
7211 * @description returns the qualified field name minus the row path
7213 * @param {GridCol} col column instance
7214 * @returns {string} resulting name that can be evaluated against a row
7216 GridRow.prototype.getEntityQualifiedColField = function(col) {
7217 return gridUtil.preEval('entity.' + col.field);
7223 * @name setRowInvisible
7224 * @methodOf ui.grid.class:GridRow
7225 * @description Sets an override on the row that forces it to always
7226 * be invisible, and if the row is currently visible then marks it
7227 * as invisible and refreshes the grid. Emits the rowsVisibleChanged
7228 * event if it changed the row visibility
7229 * @param {GridRow} row row to force invisible, needs to be a GridRow,
7230 * which can be found from your data entity using grid.findRow
7232 GridRow.prototype.setRowInvisible = function (row) {
7234 row.forceInvisible = true;
7237 row.visible = false;
7239 row.grid.api.core.raise.rowsVisibleChanged();
7246 * @name clearRowInvisible
7247 * @methodOf ui.grid.class:GridRow
7248 * @description Clears any override on the row visibility, returning it
7249 * to normal visibility calculations. If the row is currently invisible
7250 * then sets it to visible and calls refresh and emits the rowsVisibleChanged
7252 * TODO: if filter in action, then is this right?
7253 * @param {GridRow} row row clear force invisible, needs to be a GridRow,
7254 * which can be found from your data entity using grid.findRow
7256 GridRow.prototype.clearRowInvisible = function (row) {
7258 row.forceInvisible = false;
7260 if ( !row.visible ){
7263 row.grid.api.core.raise.rowsVisibleChanged();
7276 * @name ui.grid.service:gridClassFactory
7278 * @description factory to return dom specific instances of a grid
7281 angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', 'Grid', 'GridColumn', 'GridRow',
7282 function (gridUtil, $q, $compile, $templateCache, uiGridConstants, Grid, GridColumn, GridRow) {
7288 * @methodOf ui.grid.service:gridClassFactory
7289 * @description Creates a new grid instance. Each instance will have a unique id
7290 * @param {object} options An object map of options to pass into the created grid instance.
7291 * @returns {Grid} grid
7293 createGrid : function(options) {
7294 options = (typeof(options) !== 'undefined') ? options : {};
7295 options.id = gridUtil.newId();
7296 var grid = new Grid(options);
7298 // NOTE/TODO: rowTemplate should always be defined...
7299 if (grid.options.rowTemplate) {
7300 var rowTemplateFnPromise = $q.defer();
7301 grid.getRowTemplateFn = rowTemplateFnPromise.promise;
7303 gridUtil.getTemplate(grid.options.rowTemplate)
7305 function (template) {
7306 var rowTemplateFn = $compile(template);
7307 rowTemplateFnPromise.resolve(rowTemplateFn);
7310 // Todo handle response error here?
7311 throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
7315 grid.registerColumnBuilder(service.defaultColumnBuilder);
7317 // Reset all rows to visible initially
7318 grid.registerRowsProcessor(function allRowsVisible(rows) {
7319 rows.forEach(function (row) {
7320 row.visible = !row.forceInvisible;
7326 grid.registerColumnsProcessor(function allColumnsVisible(columns) {
7327 columns.forEach(function (column) {
7328 column.visible = true;
7334 grid.registerColumnsProcessor(function(renderableColumns) {
7335 renderableColumns.forEach(function (column) {
7336 if (column.colDef.visible === false) {
7337 column.visible = false;
7341 return renderableColumns;
7346 if (grid.options.enableFiltering) {
7347 grid.registerRowsProcessor(grid.searchRows);
7350 // Register the default row processor, it sorts rows by selected columns
7351 if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
7352 grid.registerRowsProcessor(grid.options.externalSort);
7355 grid.registerRowsProcessor(grid.sortByColumn);
7363 * @name defaultColumnBuilder
7364 * @methodOf ui.grid.service:gridClassFactory
7365 * @description Processes designTime column definitions and applies them to col for the
7366 * core grid features
7367 * @param {object} colDef reference to column definition
7368 * @param {GridColumn} col reference to gridCol
7369 * @param {object} gridOptions reference to grid options
7371 defaultColumnBuilder: function (colDef, col, gridOptions) {
7373 var templateGetPromises = [];
7377 * @name headerCellTemplate
7378 * @propertyOf ui.grid.class:GridOptions.columnDef
7379 * @description a custom template for the header for this column. The default
7380 * is ui-grid/uiGridHeaderCell
7383 if (!colDef.headerCellTemplate) {
7384 col.providedHeaderCellTemplate = 'ui-grid/uiGridHeaderCell';
7386 col.providedHeaderCellTemplate = colDef.headerCellTemplate;
7391 * @name cellTemplate
7392 * @propertyOf ui.grid.class:GridOptions.columnDef
7393 * @description a custom template for each cell in this column. The default
7394 * is ui-grid/uiGridCell. If you are using the cellNav feature, this template
7395 * must contain a div that can receive focus.
7398 if (!colDef.cellTemplate) {
7399 col.providedCellTemplate = 'ui-grid/uiGridCell';
7401 col.providedCellTemplate = colDef.cellTemplate;
7404 col.cellTemplatePromise = gridUtil.getTemplate(col.providedCellTemplate);
7405 templateGetPromises.push(col.cellTemplatePromise
7407 function (template) {
7408 col.cellTemplate = template.replace(uiGridConstants.CUSTOM_FILTERS, col.cellFilter ? "|" + col.cellFilter : "");
7411 throw new Error("Couldn't fetch/use colDef.cellTemplate '" + colDef.cellTemplate + "'");
7415 templateGetPromises.push(gridUtil.getTemplate(col.providedHeaderCellTemplate)
7417 function (template) {
7418 col.headerCellTemplate = template.replace(uiGridConstants.CUSTOM_FILTERS, col.headerCellFilter ? "|" + col.headerCellFilter : "");
7421 throw new Error("Couldn't fetch/use colDef.headerCellTemplate '" + colDef.headerCellTemplate + "'");
7425 // Create a promise for the compiled element function
7426 col.compiledElementFnDefer = $q.defer();
7428 return $q.all(templateGetPromises);
7433 //class definitions (moved to separate factories)
7441 var module = angular.module('ui.grid');
7443 function escapeRegExp(str) {
7444 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
7447 function QuickCache() {
7448 var c = function(get, set) {
7449 // Return the cached value of 'get' if it's stored
7450 if (get && c.cache[get]) {
7451 return c.cache[get];
7453 // Otherwise set it and return it
7454 else if (get && set) {
7456 return c.cache[get];
7465 c.clear = function () {
7474 * @name ui.grid.service:rowSearcher
7476 * @description Service for searching/filtering rows based on column value conditions.
7478 module.service('rowSearcher', ['gridUtil', 'uiGridConstants', function (gridUtil, uiGridConstants) {
7479 var defaultCondition = uiGridConstants.filter.STARTS_WITH;
7481 var rowSearcher = {};
7483 // rowSearcher.searchColumn = function searchColumn(condition, item) {
7486 // var col = self.fieldMap[condition.columnDisplay];
7491 // var sp = col.cellFilter.split(':');
7492 // var filter = col.cellFilter ? $filter(sp[0]) : null;
7493 // var value = item[condition.column] || item[col.field.split('.')[0]];
7494 // if (value === null || value === undefined) {
7497 // if (typeof filter === "function") {
7498 // var filterResults = filter(typeof value === "object" ? evalObject(value, col.field) : value, sp[1]).toString();
7499 // result = condition.regex.test(filterResults);
7502 // result = condition.regex.test(typeof value === "object" ? evalObject(value, col.field).toString() : value.toString());
7513 * @methodOf ui.grid.service:rowSearcher
7514 * @description Get the term from a filter
7515 * Trims leading and trailing whitespace
7516 * @param {object} filter object to use
7517 * @returns {object} Parsed term
7519 rowSearcher.getTerm = function getTerm(filter) {
7520 if (typeof(filter.term) === 'undefined') { return filter.term; }
7522 var term = filter.term;
7524 // Strip leading and trailing whitespace if the term is a string
7525 if (typeof(term) === 'string') {
7535 * @methodOf ui.grid.service:rowSearcher
7536 * @description Remove leading and trailing asterisk (*) from the filter's term
7537 * @param {object} filter object to use
7538 * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
7540 rowSearcher.stripTerm = function stripTerm(filter) {
7541 var term = rowSearcher.getTerm(filter);
7543 if (typeof(term) === 'string') {
7544 return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
7553 * @name guessCondition
7554 * @methodOf ui.grid.service:rowSearcher
7555 * @description Guess the condition for a filter based on its term
7557 * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
7558 * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
7559 * @param {object} filter object to use
7560 * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
7562 rowSearcher.guessCondition = function guessCondition(filter) {
7563 if (typeof(filter.term) === 'undefined' || !filter.term) {
7564 return defaultCondition;
7567 var term = rowSearcher.getTerm(filter);
7569 // Term starts with and ends with a *, use 'contains' condition
7570 // if (/^\*[\s\S]+?\*$/.test(term)) {
7571 // return uiGridConstants.filter.CONTAINS;
7573 // // Term starts with a *, use 'ends with' condition
7574 // else if (/^\*/.test(term)) {
7575 // return uiGridConstants.filter.ENDS_WITH;
7577 // // Term ends with a *, use 'starts with' condition
7578 // else if (/\*$/.test(term)) {
7579 // return uiGridConstants.filter.STARTS_WITH;
7581 // // Default to default condition
7583 // return defaultCondition;
7586 // If the term has *s then turn it into a regex
7587 if (/\*/.test(term)) {
7588 var regexpFlags = '';
7589 if (!filter.flags || !filter.flags.caseSensitive) {
7593 var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
7594 return new RegExp('^' + reText + '$', regexpFlags);
7596 // Otherwise default to default condition
7598 return defaultCondition;
7602 rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, termCache, i, filter) {
7603 // Cache typeof condition
7604 var conditionType = typeof(filter.condition);
7606 // Default to CONTAINS condition
7607 if (conditionType === 'undefined' || !filter.condition) {
7608 filter.condition = uiGridConstants.filter.CONTAINS;
7611 // Term to search for.
7612 var term = rowSearcher.stripTerm(filter);
7614 if (term === null || term === undefined || term === '') {
7618 // Get the column value for this row
7619 var value = grid.getCellValue(row, column);
7621 var regexpFlags = '';
7622 if (!filter.flags || !filter.flags.caseSensitive) {
7626 var cacheId = column.field + i;
7628 // If the filter's condition is a RegExp, then use it
7629 if (filter.condition instanceof RegExp) {
7630 if (!filter.condition.test(value)) {
7634 // If the filter's condition is a function, run it
7635 else if (conditionType === 'function') {
7636 return filter.condition(term, value, row, column);
7638 else if (filter.condition === uiGridConstants.filter.STARTS_WITH) {
7639 var startswithRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp('^' + term, regexpFlags));
7641 if (!startswithRE.test(value)) {
7645 else if (filter.condition === uiGridConstants.filter.ENDS_WITH) {
7646 var endswithRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp(term + '$', regexpFlags));
7648 if (!endswithRE.test(value)) {
7652 else if (filter.condition === uiGridConstants.filter.CONTAINS) {
7653 var containsRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp(term, regexpFlags));
7655 if (!containsRE.test(value)) {
7659 else if (filter.condition === uiGridConstants.filter.EXACT) {
7660 var exactRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp('^' + term + '$', regexpFlags));
7662 if (!exactRE.test(value)) {
7666 else if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
7667 if (value <= term) {
7671 else if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
7676 else if (filter.condition === uiGridConstants.filter.LESS_THAN) {
7677 if (value >= term) {
7681 else if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
7686 else if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
7687 if (!angular.equals(value, term)) {
7697 * @name useExternalFiltering
7698 * @propertyOf ui.grid.class:GridOptions
7699 * @description False by default. When enabled, this setting suppresses the internal filtering.
7700 * All UI logic will still operate, allowing filter conditions to be set and modified.
7702 * The external filter logic can listen for the `filterChange` event, which fires whenever
7703 * a filter has been adjusted.
7707 * @name searchColumn
7708 * @methodOf ui.grid.service:rowSearcher
7709 * @description Process filters on a given column against a given row. If the row meets the conditions on all the filters, return true.
7710 * @param {Grid} grid Grid to search in
7711 * @param {GridRow} row Row to search on
7712 * @param {GridCol} column Column with the filters to use
7713 * @returns {boolean} Whether the column matches or not.
7715 rowSearcher.searchColumn = function searchColumn(grid, row, column, termCache) {
7718 if (grid.options.useExternalFiltering) {
7722 if (typeof(column.filters) !== 'undefined' && column.filters && column.filters.length > 0) {
7723 filters = column.filters;
7725 // If filters array is not there, assume no filters for this column.
7726 // This array should have been built in GridColumn::updateColumnDef.
7730 for (var i in filters) {
7731 var filter = filters[i];
7735 term: 'blah', // Search term to search for, could be a string, integer, etc.
7736 condition: uiGridConstants.filter.CONTAINS // Type of match to do. Defaults to CONTAINS (i.e. looking in a string), but could be EXACT, GREATER_THAN, etc.
7737 flags: { // Flags for the conditions
7738 caseSensitive: false // Case-sensitivity defaults to false
7743 // Check for when no condition is supplied. In this case, guess the condition
7744 // to use based on the filter's term. Cache this result.
7745 if (!filter.condition) {
7746 // Cache custom conditions, building the RegExp takes time
7747 var conditionCacheId = 'cond-' + column.field + '-' + filter.term;
7748 var condition = termCache(conditionCacheId) ? termCache(conditionCacheId) : termCache(conditionCacheId, rowSearcher.guessCondition(filter));
7750 // Create a surrogate filter so as not to change
7751 // the actual columnDef.filters.
7753 // Copy over the search term
7755 // Use the guessed condition
7756 condition: condition,
7757 // Set flags, using passed flags if present
7758 flags: angular.extend({
7759 caseSensitive: false
7764 var ret = rowSearcher.runColumnFilter(grid, row, column, termCache, i, filter);
7773 // // No filter conditions, default to true
7781 * @methodOf ui.grid.service:rowSearcher
7782 * @description Run a search across
7783 * @param {Grid} grid Grid instance to search inside
7784 * @param {Array[GridRow]} rows GridRows to filter
7785 * @param {Array[GridColumn]} columns GridColumns with filters to process
7787 rowSearcher.search = function search(grid, rows, columns) {
7788 // Don't do anything if we weren't passed any rows
7793 // Create a term cache
7794 var termCache = new QuickCache();
7796 // Build filtered column list
7797 var filterCols = [];
7798 columns.forEach(function (col) {
7799 if (typeof(col.filters) !== 'undefined' && col.filters.length > 0) {
7800 filterCols.push(col);
7802 else if (typeof(col.filter) !== 'undefined' && col.filter && typeof(col.filter.term) !== 'undefined' && col.filter.term) {
7803 filterCols.push(col);
7807 if (filterCols.length > 0) {
7808 filterCols.forEach(function foreachFilterCol(col) {
7809 rows.forEach(function foreachRow(row) {
7810 if (row.forceInvisible || !rowSearcher.searchColumn(grid, row, col, termCache)) {
7811 row.visible = false;
7816 if (grid.api.core.raise.rowsVisibleChanged) {
7817 grid.api.core.raise.rowsVisibleChanged();
7820 // rows.forEach(function (row) {
7821 // var matchesAllColumns = true;
7823 // for (var i in filterCols) {
7824 // var col = filterCols[i];
7826 // if (!rowSearcher.searchColumn(grid, row, col, termCache)) {
7827 // matchesAllColumns = false;
7829 // // Stop processing other terms
7834 // // Row doesn't match all the terms, don't display it
7835 // if (!matchesAllColumns) {
7836 // row.visible = false;
7839 // row.visible = true;
7844 // Reset the term cache
7856 var module = angular.module('ui.grid');
7860 * @name ui.grid.class:RowSorter
7861 * @description RowSorter provides the default sorting mechanisms,
7862 * including guessing column types and applying appropriate sort
7867 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
7868 var currencyRegexStr =
7870 uiGridConstants.CURRENCY_SYMBOLS
7871 .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
7872 .join('|') + // Join all the symbols together with |s
7875 // /^[-+]?[£$¤¥]?[\d,.]+%?$/
7876 var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
7879 // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
7880 // this takes a piece of data from the cell and tries to determine its type and what sorting
7881 // function to use for it
7888 * @methodOf ui.grid.class:RowSorter
7890 * @description Assigns a sort function to use based on the itemType in the column
7891 * @param {string} itemType one of 'number', 'boolean', 'string', 'date', 'object'. And
7892 * error will be thrown for any other type.
7893 * @returns {function} a sort function that will sort that type
7895 rowSorter.guessSortFn = function guessSortFn(itemType) {
7898 return rowSorter.sortNumber;
7900 return rowSorter.sortBool;
7902 return rowSorter.sortAlpha;
7904 return rowSorter.sortDate;
7906 return rowSorter.basicSort;
7908 throw new Error('No sorting function found for type:' + itemType);
7915 * @methodOf ui.grid.class:RowSorter
7917 * @description Sorts nulls and undefined to the bottom (top when
7918 * descending). Called by each of the internal sorters before
7919 * attempting to sort. Note that this method is available on the core api
7920 * via gridApi.core.sortHandleNulls
7921 * @param {object} a sort value a
7922 * @param {object} b sort value b
7923 * @returns {number} null if there were no nulls/undefineds, otherwise returns
7924 * a sort value that should be passed back from the sort function
7926 rowSorter.handleNulls = function handleNulls(a, b) {
7927 // We want to allow zero values and false values to be evaluated in the sort function
7928 if ((!a && a !== 0 && a !== false) || (!b && b !== 0 && b !== false)) {
7929 // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
7930 if ((!a && a !== 0 && a !== false) && (!b && b !== 0 && b !== false)) {
7933 else if (!a && a !== 0 && a !== false) {
7936 else if (!b && b !== 0 && b !== false) {
7946 * @methodOf ui.grid.class:RowSorter
7948 * @description Sorts any values that provide the < method, including strings
7949 * or numbers. Handles nulls and undefined through calling handleNulls
7950 * @param {object} a sort value a
7951 * @param {object} b sort value b
7952 * @returns {number} normal sort function, returns -ve, 0, +ve
7954 rowSorter.basicSort = function basicSort(a, b) {
7955 var nulls = rowSorter.handleNulls(a, b);
7956 if ( nulls !== null ){
7972 * @methodOf ui.grid.class:RowSorter
7974 * @description Sorts numerical values. Handles nulls and undefined through calling handleNulls
7975 * @param {object} a sort value a
7976 * @param {object} b sort value b
7977 * @returns {number} normal sort function, returns -ve, 0, +ve
7979 rowSorter.sortNumber = function sortNumber(a, b) {
7980 var nulls = rowSorter.handleNulls(a, b);
7981 if ( nulls !== null ){
7991 * @methodOf ui.grid.class:RowSorter
7992 * @name sortNumberStr
7993 * @description Sorts numerical values that are stored in a string (i.e. parses them to numbers first).
7994 * Handles nulls and undefined through calling handleNulls
7995 * @param {object} a sort value a
7996 * @param {object} b sort value b
7997 * @returns {number} normal sort function, returns -ve, 0, +ve
7999 rowSorter.sortNumberStr = function sortNumberStr(a, b) {
8000 var nulls = rowSorter.handleNulls(a, b);
8001 if ( nulls !== null ){
8004 var numA, // The parsed number form of 'a'
8005 numB, // The parsed number form of 'b'
8009 // Try to parse 'a' to a float
8010 numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
8012 // If 'a' couldn't be parsed to float, flag it as bad
8017 // Try to parse 'b' to a float
8018 numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
8020 // If 'b' couldn't be parsed to float, flag it as bad
8025 // We want bad ones to get pushed to the bottom... which effectively is "greater than"
8045 * @methodOf ui.grid.class:RowSorter
8047 * @description Sorts string values. Handles nulls and undefined through calling handleNulls
8048 * @param {object} a sort value a
8049 * @param {object} b sort value b
8050 * @returns {number} normal sort function, returns -ve, 0, +ve
8052 rowSorter.sortAlpha = function sortAlpha(a, b) {
8053 var nulls = rowSorter.handleNulls(a, b);
8054 if ( nulls !== null ){
8057 var strA = a.toLowerCase(),
8058 strB = b.toLowerCase();
8060 return strA === strB ? 0 : (strA < strB ? -1 : 1);
8067 * @methodOf ui.grid.class:RowSorter
8069 * @description Sorts date values. Handles nulls and undefined through calling handleNulls
8070 * @param {object} a sort value a
8071 * @param {object} b sort value b
8072 * @returns {number} normal sort function, returns -ve, 0, +ve
8074 rowSorter.sortDate = function sortDate(a, b) {
8075 var nulls = rowSorter.handleNulls(a, b);
8076 if ( nulls !== null ){
8079 var timeA = a.getTime(),
8080 timeB = b.getTime();
8082 return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
8089 * @methodOf ui.grid.class:RowSorter
8091 * @description Sorts boolean values, true is considered larger than false.
8092 * Handles nulls and undefined through calling handleNulls
8093 * @param {object} a sort value a
8094 * @param {object} b sort value b
8095 * @returns {number} normal sort function, returns -ve, 0, +ve
8097 rowSorter.sortBool = function sortBool(a, b) {
8098 var nulls = rowSorter.handleNulls(a, b);
8099 if ( nulls !== null ){
8118 * @methodOf ui.grid.class:RowSorter
8120 * @description Get the sort function for the column. Looks first in
8121 * rowSorter.colSortFnCache using the column name, failing that it
8122 * looks at col.sortingAlgorithm (and puts it in the cache), failing that
8123 * it guesses the sort algorithm based on the data type.
8125 * The cache currently seems a bit pointless, as none of the work we do is
8126 * processor intensive enough to need caching. Presumably in future we might
8127 * inspect the row data itself to guess the sort function, and in that case
8128 * it would make sense to have a cache, the infrastructure is in place to allow
8131 * @param {Grid} grid the grid to consider
8132 * @param {GridCol} col the column to find a function for
8133 * @param {array} rows an array of grid rows. Currently unused, but presumably in future
8134 * we might inspect the rows themselves to decide what sort of data might be there
8135 * @returns {function} the sort function chosen for the column
8137 rowSorter.getSortFn = function getSortFn(grid, col, rows) {
8140 // See if we already figured out what to use to sort the column and have it in the cache
8141 if (rowSorter.colSortFnCache[col.colDef.name]) {
8142 sortFn = rowSorter.colSortFnCache[col.colDef.name];
8144 // If the column has its OWN sorting algorithm, use that
8145 else if (col.sortingAlgorithm !== undefined) {
8146 sortFn = col.sortingAlgorithm;
8147 rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
8149 // Try and guess what sort function to use
8151 // Guess the sort function
8152 sortFn = rowSorter.guessSortFn(col.colDef.type);
8154 // If we found a sort function, cache it
8156 rowSorter.colSortFnCache[col.colDef.name] = sortFn;
8159 // We assign the alpha sort because anything that is null/undefined will never get passed to
8160 // the actual sorting function. It will get caught in our null check and returned to be sorted
8161 // down to the bottom
8162 sortFn = rowSorter.sortAlpha;
8173 * @methodOf ui.grid.class:RowSorter
8174 * @name prioritySort
8175 * @description Used where multiple columns are present in the sort criteria,
8176 * we determine which column should take precedence in the sort by sorting
8177 * the columns based on their sort.priority
8179 * @param {gridColumn} a column a
8180 * @param {gridColumn} b column b
8181 * @returns {number} normal sort function, returns -ve, 0, +ve
8183 rowSorter.prioritySort = function (a, b) {
8184 // Both columns have a sort priority
8185 if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
8186 // A is higher priority
8187 if (a.sort.priority < b.sort.priority) {
8191 else if (a.sort.priority === b.sort.priority) {
8199 // Only A has a priority
8200 else if (a.sort.priority || a.sort.priority === 0) {
8203 // Only B has a priority
8204 else if (b.sort.priority || b.sort.priority === 0) {
8207 // Neither has a priority
8216 * @name useExternalSorting
8217 * @propertyOf ui.grid.class:GridOptions
8218 * @description Prevents the internal sorting from executing. Events will
8219 * still be fired when the sort changes, and the sort information on
8220 * the columns will be updated, allowing an external sorter (for example,
8221 * server sorting) to be implemented. Defaults to false.
8226 * @methodOf ui.grid.class:RowSorter
8228 * @description sorts the grid
8229 * @param {Object} grid the grid itself
8230 * @param {Object} rows the rows to be sorted
8231 * @param {Object} columns the columns in which to look
8234 rowSorter.sort = function rowSorterSort(grid, rows, columns) {
8235 // first make sure we are even supposed to do work
8240 if (grid.options.useExternalSorting){
8244 // Build the list of columns to sort by
8246 columns.forEach(function (col) {
8247 if (col.sort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
8252 // Sort the "sort columns" by their sort priority
8253 sortCols = sortCols.sort(rowSorter.prioritySort);
8255 // Now rows to sort by, maintain original order
8256 if (sortCols.length === 0) {
8260 // Re-usable variables
8263 // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
8264 // var d = data.slice(0);
8265 var r = rows.slice(0);
8267 // Now actually sort the data
8268 return rows.sort(function rowSortFn(rowA, rowB) {
8273 while (tem === 0 && idx < sortCols.length) {
8274 // grab the metadata for the rest of the logic
8275 col = sortCols[idx];
8276 direction = sortCols[idx].sort.direction;
8278 sortFn = rowSorter.getSortFn(grid, col, r);
8280 var propA = grid.getCellValue(rowA, col);
8281 var propB = grid.getCellValue(rowB, col);
8283 tem = sortFn(propA, propB);
8288 // Made it this far, we don't have to worry about null & undefined
8289 if (direction === uiGridConstants.ASC) {
8303 var module = angular.module('ui.grid');
8305 function getStyles (elem) {
8307 if (typeof(e.length) !== 'undefined' && e.length) {
8311 return e.ownerDocument.defaultView.getComputedStyle(e, null);
8314 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
8315 // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
8316 // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
8317 rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
8318 cssShow = { position: "absolute", visibility: "hidden", display: "block" };
8320 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
8321 var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
8322 // If we already have the right measurement, avoid augmentation
8324 // Otherwise initialize for horizontal or vertical properties
8325 name === 'width' ? 1 : 0,
8329 var sides = ['Top', 'Right', 'Bottom', 'Left'];
8331 for ( ; i < 4; i += 2 ) {
8332 var side = sides[i];
8333 // dump('side', side);
8335 // both box models exclude margin, so add it if we want it
8336 if ( extra === 'margin' ) {
8337 var marg = parseFloat(styles[extra + side]);
8342 // dump('val1', val);
8344 if ( isBorderBox ) {
8345 // border-box includes padding, so remove it if we want content
8346 if ( extra === 'content' ) {
8347 var padd = parseFloat(styles['padding' + side]);
8350 // dump('val2', val);
8354 // at this point, extra isn't border nor margin, so remove border
8355 if ( extra !== 'margin' ) {
8356 var bordermarg = parseFloat(styles['border' + side + 'Width']);
8357 if (!isNaN(bordermarg)) {
8359 // dump('val3', val);
8364 // at this point, extra isn't content, so add padding
8365 var nocontentPad = parseFloat(styles['padding' + side]);
8366 if (!isNaN(nocontentPad)) {
8367 val += nocontentPad;
8368 // dump('val4', val);
8371 // at this point, extra isn't content nor padding, so add border
8372 if ( extra !== 'padding') {
8373 var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
8374 if (!isNaN(nocontentnopad)) {
8375 val += nocontentnopad;
8376 // dump('val5', val);
8382 // dump('augVal', val);
8387 function getWidthOrHeight( elem, name, extra ) {
8388 // Start with offset property, which is equivalent to the border-box value
8389 var valueIsBorderBox = true,
8391 styles = getStyles(elem),
8392 isBorderBox = styles['boxSizing'] === 'border-box';
8394 // some non-html elements return undefined for offsetWidth, so check for null/undefined
8395 // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
8396 // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
8397 if ( val <= 0 || val == null ) {
8398 // Fall back to computed then uncomputed css if necessary
8400 if ( val < 0 || val == null ) {
8401 val = elem.style[ name ];
8404 // Computed unit is not pixels. Stop here and return.
8405 if ( rnumnonpx.test(val) ) {
8409 // we need the check for style in case a browser which returns unreliable values
8410 // for getComputedStyle silently falls back to the reliable elem.style
8411 valueIsBorderBox = isBorderBox &&
8412 ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
8414 // Normalize "", auto, and prepare for extra
8415 val = parseFloat( val ) || 0;
8418 // use the active box-sizing model to add/subtract irrelevant styles
8420 augmentWidthOrHeight(
8423 extra || ( isBorderBox ? "border" : "content" ),
8429 // dump('ret', ret, val);
8433 var uid = ['0', '0', '0'];
8434 var uidPrefix = 'uiGrid-';
8438 * @name ui.grid.service:GridUtil
8440 * @description Grid utility functions
8442 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$injector', '$q', '$interpolate', 'uiGridConstants',
8443 function ($log, $window, $document, $http, $templateCache, $timeout, $injector, $q, $interpolate, uiGridConstants) {
8446 getStyles: getStyles,
8450 * @name createBoundedWrapper
8451 * @methodOf ui.grid.service:GridUtil
8453 * @param {object} Object to bind 'this' to
8454 * @param {method} Method to bind
8455 * @returns {Function} The wrapper that performs the binding
8458 * Binds given method to given object.
8460 * By means of a wrapper, ensures that ``method`` is always bound to
8461 * ``object`` regardless of its calling environment.
8462 * Iow, inside ``method``, ``this`` always points to ``object``.
8464 * See http://alistapart.com/article/getoutbindingsituations
8467 createBoundedWrapper: function(object, method) {
8469 return method.apply(object, arguments);
8476 * @name readableColumnName
8477 * @methodOf ui.grid.service:GridUtil
8479 * @param {string} columnName Column name as a string
8480 * @returns {string} Column name appropriately capitalized and split apart
8483 <example module="app">
8484 <file name="app.js">
8485 var app = angular.module('app', ['ui.grid']);
8487 app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
8488 $scope.name = 'firstName';
8489 $scope.columnName = function(name) {
8490 return gridUtil.readableColumnName(name);
8494 <file name="index.html">
8495 <div ng-controller="MainCtrl">
8496 <strong>Column name:</strong> <input ng-model="name" />
8498 <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
8503 readableColumnName: function (columnName) {
8504 // Convert underscores to spaces
8505 if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
8507 if (typeof(columnName) !== 'string') {
8508 columnName = String(columnName);
8511 return columnName.replace(/_+/g, ' ')
8512 // Replace a completely all-capsed word with a first-letter-capitalized version
8513 .replace(/^[A-Z]+$/, function (match) {
8514 return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
8516 // Capitalize the first letter of words
8517 .replace(/(\w+)/g, function (match) {
8518 return angular.uppercase(match.charAt(0)) + match.slice(1);
8520 // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
8521 // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
8522 // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
8523 .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
8528 * @name getColumnsFromData
8529 * @methodOf ui.grid.service:GridUtil
8530 * @description Return a list of column names, given a data set
8532 * @param {string} data Data array for grid
8533 * @returns {Object} Column definitions with field accessor and column name
8538 { firstName: 'Bob', lastName: 'Jones' },
8539 { firstName: 'Frank', lastName: 'Smith' }
8542 var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
8556 getColumnsFromData: function (data, excludeProperties) {
8557 var columnDefs = [];
8559 if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
8560 if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
8564 angular.forEach(item,function (prop, propName) {
8565 if ( excludeProperties.indexOf(propName) === -1){
8578 * @methodOf ui.grid.service:GridUtil
8579 * @description Return a unique ID string
8581 * @returns {string} Unique string
8585 var id = GridUtil.newId();
8590 newId: (function() {
8591 var seedId = new Date().getTime();
8601 * @methodOf ui.grid.service:GridUtil
8602 * @description Get's template from cache / element / url
8604 * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
8605 * an jQuery/Angualr element, or a promise that returns the template contents to use.
8606 * @returns {object} a promise resolving to template contents
8610 GridUtil.getTemplate(url).then(function (contents) {
8615 getTemplate: function (template) {
8616 // Try to fetch the template out of the templateCache
8617 if ($templateCache.get(template)) {
8618 return s.postProcessTemplate($templateCache.get(template));
8621 // See if the template is itself a promise
8622 if (template.hasOwnProperty('then')) {
8623 return template.then(s.postProcessTemplate);
8626 // If the template is an element, return the element
8628 if (angular.element(template).length > 0) {
8629 return $q.when(template).then(s.postProcessTemplate);
8633 //do nothing; not valid html
8636 s.logDebug('fetching url', template);
8638 // Default to trying to fetch the template as a url with $http
8639 return $http({ method: 'GET', url: template})
8642 var templateHtml = result.data.trim();
8643 //put in templateCache for next call
8644 $templateCache.put(template, templateHtml);
8645 return templateHtml;
8648 throw new Error("Could not get template " + template + ": " + err);
8651 .then(s.postProcessTemplate);
8655 postProcessTemplate: function (template) {
8656 var startSym = $interpolate.startSymbol(),
8657 endSym = $interpolate.endSymbol();
8659 // If either of the interpolation symbols have been changed, we need to alter this template
8660 if (startSym !== '{{' || endSym !== '}}') {
8661 template = template.replace(/\{\{/g, startSym);
8662 template = template.replace(/\}\}/g, endSym);
8665 return $q.when(template);
8671 * @methodOf ui.grid.service:GridUtil
8672 * @description guesses the type of an argument
8674 * @param {string/number/bool/object} item variable to examine
8675 * @returns {string} one of the following
8682 guessType : function (item) {
8683 var itemType = typeof(item);
8685 // Check for numbers and booleans
8692 if (angular.isDate(item)) {
8702 * @name elementWidth
8703 * @methodOf ui.grid.service:GridUtil
8705 * @param {element} element DOM element
8706 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
8708 * @returns {number} Element width in pixels, accounting for any borders, etc.
8710 elementWidth: function (elem) {
8716 * @name elementHeight
8717 * @methodOf ui.grid.service:GridUtil
8719 * @param {element} element DOM element
8720 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
8722 * @returns {number} Element height in pixels, accounting for any borders, etc.
8724 elementHeight: function (elem) {
8728 // Thanks to http://stackoverflow.com/a/13382873/888165
8729 getScrollbarWidth: function() {
8730 var outer = document.createElement("div");
8731 outer.style.visibility = "hidden";
8732 outer.style.width = "100px";
8733 outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
8735 document.body.appendChild(outer);
8737 var widthNoScroll = outer.offsetWidth;
8739 outer.style.overflow = "scroll";
8742 var inner = document.createElement("div");
8743 inner.style.width = "100%";
8744 outer.appendChild(inner);
8746 var widthWithScroll = inner.offsetWidth;
8749 outer.parentNode.removeChild(outer);
8751 return widthNoScroll - widthWithScroll;
8754 swap: function( elem, options, callback, args ) {
8758 // Remember the old values, and insert the new ones
8759 for ( name in options ) {
8760 old[ name ] = elem.style[ name ];
8761 elem.style[ name ] = options[ name ];
8764 ret = callback.apply( elem, args || [] );
8766 // Revert the old values
8767 for ( name in options ) {
8768 elem.style[ name ] = old[ name ];
8774 fakeElement: function( elem, options, callback, args ) {
8776 newElement = angular.element(elem).clone()[0];
8778 for ( name in options ) {
8779 newElement.style[ name ] = options[ name ];
8782 angular.element(document.body).append(newElement);
8784 ret = callback.call( newElement, newElement );
8786 angular.element(newElement).remove();
8793 * @name normalizeWheelEvent
8794 * @methodOf ui.grid.service:GridUtil
8796 * @param {event} event A mouse wheel event
8798 * @returns {event} A normalized event
8801 * Given an event from this list:
8803 * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
8806 * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
8808 normalizeWheelEvent: function (event) {
8809 // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
8810 // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
8811 var lowestDelta, lowestDeltaXY;
8813 var orgEvent = event || window.event,
8814 args = [].slice.call(arguments, 1),
8822 // event = $.event.fix(orgEvent);
8823 // event.type = 'mousewheel';
8825 // NOTE: jQuery masks the event and stores it in the event as originalEvent
8826 if (orgEvent.originalEvent) {
8827 orgEvent = orgEvent.originalEvent;
8830 // Old school scrollwheel delta
8831 if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
8832 if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
8834 // At a minimum, setup the deltaY to be delta
8837 // Firefox < 17 related to DOMMouseScroll event
8838 if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
8840 deltaX = delta * -1;
8843 // New school wheel delta (wheel event)
8844 if ( orgEvent.deltaY ) {
8845 deltaY = orgEvent.deltaY * -1;
8848 if ( orgEvent.deltaX ) {
8849 deltaX = orgEvent.deltaX;
8850 delta = deltaX * -1;
8854 if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
8855 if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
8857 // Look for lowest delta to normalize the delta values
8858 absDelta = Math.abs(delta);
8859 if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
8860 absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
8861 if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
8863 // Get a whole value for the deltas
8864 fn = delta > 0 ? 'floor' : 'ceil';
8865 delta = Math[fn](delta / lowestDelta);
8866 deltaX = Math[fn](deltaX / lowestDeltaXY);
8867 deltaY = Math[fn](deltaY / lowestDeltaXY);
8876 // Stolen from Modernizr
8877 // TODO: make this, and everythign that flows from it, robust
8878 //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
8879 isTouchEnabled: function() {
8882 if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
8889 isNullOrUndefined: function(obj) {
8890 if (obj === undefined || obj === null) {
8896 endsWith: function(str, suffix) {
8897 if (!str || !suffix || typeof str !== "string") {
8900 return str.indexOf(suffix, str.length - suffix.length) !== -1;
8903 arrayContainsObjectWithProperty: function(array, propertyName, propertyValue) {
8905 angular.forEach(array, function (object) {
8906 if (object[propertyName] === propertyValue) {
8913 // Shim requestAnimationFrame
8914 requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
8915 $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
8917 return $timeout(fn, 10, false);
8920 numericAndNullSort: function (a, b) {
8921 if (a === null) { return 1; }
8922 if (b === null) { return -1; }
8923 if (a === null && b === null) { return 0; }
8927 // Disable ngAnimate animations on an element
8928 disableAnimations: function (element) {
8931 $animate = $injector.get('$animate');
8932 $animate.enabled(false, element);
8937 enableAnimations: function (element) {
8940 $animate = $injector.get('$animate');
8941 $animate.enabled(true, element);
8947 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
8948 nextUid: function nextUid() {
8949 var index = uid.length;
8954 digit = uid[index].charCodeAt(0);
8955 if (digit === 57 /*'9'*/) {
8957 return uidPrefix + uid.join('');
8959 if (digit === 90 /*'Z'*/) {
8962 uid[index] = String.fromCharCode(digit + 1);
8963 return uidPrefix + uid.join('');
8968 return uidPrefix + uid.join('');
8971 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
8972 hashKey: function hashKey(obj) {
8973 var objType = typeof obj,
8976 if (objType === 'object' && obj !== null) {
8977 if (typeof (key = obj.$$hashKey) === 'function') {
8978 // must invoke on object to keep the right this
8979 key = obj.$$hashKey();
8981 else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
8982 key = obj.$$hashKey;
8984 else if (key === undefined) {
8985 key = obj.$$hashKey = s.nextUid();
8992 return objType + ':' + key;
8995 resetUids: function () {
8996 uid = ['0', '0', '0'];
9001 * @methodOf ui.grid.service:GridUtil
9003 * @description wraps the $log method, allowing us to choose different
9004 * treatment within ui-grid if we so desired. At present we only log
9005 * error messages if uiGridConstants.LOG_ERROR_MESSAGES is set to true
9006 * @param {string} logMessage message to be logged to the console
9009 logError: function( logMessage ){
9010 if ( uiGridConstants.LOG_ERROR_MESSAGES ){
9011 $log.error( logMessage );
9017 * @methodOf ui.grid.service:GridUtil
9019 * @description wraps the $log method, allowing us to choose different
9020 * treatment within ui-grid if we so desired. At present we only log
9021 * warning messages if uiGridConstants.LOG_WARN_MESSAGES is set to true
9022 * @param {string} logMessage message to be logged to the console
9025 logWarn: function( logMessage ){
9026 if ( uiGridConstants.LOG_WARN_MESSAGES ){
9027 $log.warn( logMessage );
9033 * @methodOf ui.grid.service:GridUtil
9035 * @description wraps the $log method, allowing us to choose different
9036 * treatment within ui-grid if we so desired. At present we only log
9037 * debug messages if uiGridConstants.LOG_DEBUG_MESSAGES is set to true
9040 logDebug: function() {
9041 if ( uiGridConstants.LOG_DEBUG_MESSAGES ){
9042 $log.debug.apply($log, arguments);
9048 ['width', 'height'].forEach(function (name) {
9049 var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
9050 s['element' + capsName] = function (elem, extra) {
9052 if (e && typeof(e.length) !== 'undefined' && e.length) {
9057 var styles = getStyles(e);
9058 return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
9059 s.fakeElement(e, cssShow, function(newElm) {
9060 return getWidthOrHeight( newElm, name, extra );
9062 getWidthOrHeight( e, name, extra );
9069 s['outerElement' + capsName] = function (elem, margin) {
9070 return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
9074 // http://stackoverflow.com/a/24107550/888165
9075 s.closestElm = function closestElm(el, selector) {
9076 if (typeof(el.length) !== 'undefined' && el.length) {
9082 // find vendor prefix
9083 ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
9084 if (typeof document.body[fn] === 'function') {
9093 while (el !== null) {
9094 parent = el.parentElement;
9095 if (parent !== null && parent[matchesFn](selector)) {
9104 s.type = function (obj) {
9105 var text = Function.prototype.toString.call(obj.constructor);
9106 return text.match(/function (.*?)\(/)[1];
9109 s.getBorderSize = function getBorderSize(elem, borderType) {
9110 if (typeof(elem.length) !== 'undefined' && elem.length) {
9114 var styles = getStyles(elem);
9116 // If a specific border is supplied, like 'top', read the 'borderTop' style property
9118 borderType = 'border' + borderType.charAt(0).toUpperCase() + borderType.slice(1);
9121 borderType = 'border';
9124 borderType += 'Width';
9126 var val = parseInt(styles[borderType], 10);
9136 // http://stackoverflow.com/a/22948274/888165
9137 // TODO: Opera? Mobile?
9138 s.detectBrowser = function detectBrowser() {
9139 var userAgent = $window.navigator.userAgent;
9141 var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
9143 for (var key in browsers) {
9144 if (browsers[key].test(userAgent)) {
9154 * @name normalizeScrollLeft
9155 * @methodOf ui.grid.service:GridUtil
9157 * @param {element} element The element to get the `scrollLeft` from.
9159 * @returns {int} A normalized scrollLeft value for the current browser.
9162 * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
9164 s.normalizeScrollLeft = function normalizeScrollLeft(element) {
9165 if (typeof(element.length) !== 'undefined' && element.length) {
9166 element = element[0];
9169 var browser = s.detectBrowser();
9171 var scrollLeft = element.scrollLeft;
9173 var dir = s.getStyles(element)['direction'];
9175 // IE stays normal in RTL
9176 if (browser === 'ie') {
9179 // Chrome doesn't alter the scrollLeft value. So with RTL on a 400px-wide grid, the right-most position will still be 400 and the left-most will still be 0;
9180 else if (browser === 'chrome') {
9181 if (dir === 'rtl') {
9182 // Get the max scroll for the element
9183 var maxScrollLeft = element.scrollWidth - element.clientWidth;
9185 // Subtract the current scroll amount from the max scroll
9186 return maxScrollLeft - scrollLeft;
9192 // Firefox goes negative!
9193 else if (browser === 'firefox') {
9194 return Math.abs(scrollLeft);
9197 // TODO(c0bra): Handle other browsers? Android? iOS? Opera?
9204 * @name normalizeScrollLeft
9205 * @methodOf ui.grid.service:GridUtil
9207 * @param {element} element The element to normalize the `scrollLeft` value for
9208 * @param {int} scrollLeft The `scrollLeft` value to denormalize.
9210 * @returns {int} A normalized scrollLeft value for the current browser.
9213 * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
9215 s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft) {
9216 if (typeof(element.length) !== 'undefined' && element.length) {
9217 element = element[0];
9220 var browser = s.detectBrowser();
9222 var dir = s.getStyles(element)['direction'];
9224 // IE stays normal in RTL
9225 if (browser === 'ie') {
9228 // Chrome doesn't alter the scrollLeft value. So with RTL on a 400px-wide grid, the right-most position will still be 400 and the left-most will still be 0;
9229 else if (browser === 'chrome') {
9230 if (dir === 'rtl') {
9231 // Get the max scroll for the element
9232 var maxScrollLeft = element.scrollWidth - element.clientWidth;
9234 // Subtract the current scroll amount from the max scroll
9235 return maxScrollLeft - scrollLeft;
9241 // Firefox goes negative!
9242 else if (browser === 'firefox') {
9243 if (dir === 'rtl') {
9244 return scrollLeft * -1;
9251 // TODO(c0bra): Handle other browsers? Android? iOS? Opera?
9259 * @methodOf ui.grid.service:GridUtil
9261 * @param {string} path Path to evaluate
9263 * @returns {string} A path that is normalized.
9266 * Takes a field path and converts it to bracket notation to allow for special characters in path
9269 * gridUtil.preEval('property') == 'property'
9270 * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
9273 s.preEval = function (path) {
9274 var m = uiGridConstants.BRACKET_REGEXP.exec(path);
9276 return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
9278 path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
9279 var parts = path.split(uiGridConstants.DOT_REGEXP);
9280 var preparsed = [parts.shift()]; // first item must be var notation, thus skip
9281 angular.forEach(parts, function (part) {
9282 preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
9284 return preparsed.join('[\'');
9291 * @methodOf ui.grid.service:GridUtil
9293 * @param {function} func function to debounce
9294 * @param {number} wait milliseconds to delay
9295 * @param {bool} immediate execute before delay
9297 * @returns {function} A function that can be executed as debounced function
9300 * Copied from https://github.com/shahata/angular-debounce
9301 * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
9304 * var debouncedFunc = gridUtil.debounce(function(){alert('debounced');}, 500);
9310 s.debounce = function (func, wait, immediate) {
9311 var timeout, args, context, result;
9312 function debounce() {
9313 /* jshint validthis:true */
9316 var later = function () {
9319 result = func.apply(context, args);
9322 var callNow = immediate && !timeout;
9324 $timeout.cancel(timeout);
9326 timeout = $timeout(later, wait);
9328 result = func.apply(context, args);
9332 debounce.cancel = function () {
9333 $timeout.cancel(timeout);
9342 * @methodOf ui.grid.service:GridUtil
9344 * @param {function} func function to throttle
9345 * @param {number} wait milliseconds to delay after first trigger
9346 * @param {Object} params to use in throttle.
9348 * @returns {function} A function that can be executed as throttled function
9351 * Adapted from debounce function (above)
9352 * Potential keys for Params Object are:
9353 * trailing (bool) - whether to trigger after throttle time ends if called multiple times
9356 * var throttledFunc = gridUtil.throttle(function(){console.log('throttled');}, 500, {trailing: true});
9357 * throttledFunc(); //=> logs throttled
9358 * throttledFunc(); //=> queues attempt to log throttled for ~500ms (since trailing param is truthy)
9359 * throttledFunc(); //=> updates arguments to keep most-recent request, but does not do anything else.
9362 s.throttle = function(func, wait, options){
9363 options = options || {};
9364 var lastCall = 0, queued = null, context, args;
9366 function runFunc(endDate){
9367 lastCall = +new Date();
9368 func.apply(context, args);
9369 $timeout(function(){ queued = null; }, 0);
9373 /* jshint validthis:true */
9376 if (queued === null){
9377 var sinceLast = +new Date() - lastCall;
9378 if (sinceLast > wait){
9381 else if (options.trailing){
9382 queued = $timeout(runFunc, wait - sinceLast);
9391 // Add 'px' to the end of a number string if it doesn't have it already
9392 module.filter('px', function() {
9393 return function(str) {
9394 if (str.match(/^[\d\.]+$/)) {
9406 angular.module('ui.grid').config(['$provide', function($provide) {
9407 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9408 $delegate.add('da', {
9413 description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
9416 placeholder: 'Søg...',
9417 showingItems: 'Viste rækker:',
9418 selectedItems: 'Valgte rækker:',
9419 totalItems: 'Rækker totalt:',
9420 size: 'Side størrelse:',
9421 first: 'Første side',
9423 previous: 'Forrige side',
9427 text: 'Vælg kolonner:'
9430 hide: 'Skjul kolonne'
9433 count: 'samlede rækker: ',
9440 columns: 'Columns:',
9441 importerTitle: 'Import file',
9442 exporterAllAsCsv: 'Export all data as csv',
9443 exporterVisibleAsCsv: 'Export visible data as csv',
9444 exporterSelectedAsCsv: 'Export selected data as csv',
9445 exporterAllAsPdf: 'Export all data as pdf',
9446 exporterVisibleAsPdf: 'Export visible data as pdf',
9447 exporterSelectedAsPdf: 'Export selected data as pdf'
9450 noHeaders: 'Column names were unable to be derived, does the file have a header?',
9451 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9452 invalidCsv: 'File was unable to be processed, is it valid CSV?',
9453 invalidJson: 'File was unable to be processed, is it valid Json?',
9454 jsonNotArray: 'Imported json file must contain an array, aborting.'
9462 angular.module('ui.grid').config(['$provide', function ($provide) {
9463 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
9464 $delegate.add('de', {
9469 description: 'Ziehen Sie eine Spaltenüberschrift hierhin, um nach dieser Spalte zu gruppieren.'
9472 placeholder: 'Suche...',
9473 showingItems: 'Zeige Einträge:',
9474 selectedItems: 'Ausgewählte Einträge:',
9475 totalItems: 'Einträge gesamt:',
9476 size: 'Einträge pro Seite:',
9477 first: 'Erste Seite',
9478 next: 'Nächste Seite',
9479 previous: 'Vorherige Seite',
9480 last: 'Letzte Seite'
9483 text: 'Spalten auswählen:'
9486 ascending: 'aufsteigend sortieren',
9487 descending: 'absteigend sortieren',
9488 remove: 'Sortierung zurücksetzen'
9491 hide: 'Spalte ausblenden'
9494 count: 'Zeilen insgesamt: ',
9496 avg: 'Durchschnitt: ',
9501 columns: 'Spalten:',
9502 importerTitle: 'Datei importieren',
9503 exporterAllAsCsv: 'Alle Daten als CSV exportieren',
9504 exporterVisibleAsCsv: 'sichtbare Daten als CSV exportieren',
9505 exporterSelectedAsCsv: 'markierte Daten als CSV exportieren',
9506 exporterAllAsPdf: 'Alle Daten als PDF exportieren',
9507 exporterVisibleAsPdf: 'sichtbare Daten als PDF exportieren',
9508 exporterSelectedAsPdf: 'markierte Daten als CSV exportieren'
9511 noHeaders: 'Es konnten keine Spaltennamen ermittelt werden. Sind in der Datei Spaltendefinitionen enthalten?',
9512 noObjects: 'Es konnten keine Zeileninformationen gelesen werden, Sind in der Datei außer den Spaltendefinitionen auch Daten enthalten?',
9513 invalidCsv: 'Die Datei konnte nicht eingelesen werden, ist es eine gültige CSV-Datei?',
9514 invalidJson: 'Die Datei konnte nicht eingelesen werden. Enthält sie gültiges JSON?',
9515 jsonNotArray: 'Die importierte JSON-Datei muß ein Array enthalten. Breche Import ab.'
9524 angular.module('ui.grid').config(['$provide', function($provide) {
9525 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9526 $delegate.add('en', {
9531 description: 'Drag a column header here and drop it to group by that column.'
9534 placeholder: 'Search...',
9535 showingItems: 'Showing Items:',
9536 selectedItems: 'Selected Items:',
9537 totalItems: 'Total Items:',
9539 first: 'First Page',
9541 previous: 'Previous Page',
9545 text: 'Choose Columns:'
9548 ascending: 'Sort Ascending',
9549 descending: 'Sort Descending',
9550 remove: 'Remove Sort'
9556 count: 'total rows: ',
9563 pinLeft: 'Pin Left',
9564 pinRight: 'Pin Right',
9568 columns: 'Columns:',
9569 importerTitle: 'Import file',
9570 exporterAllAsCsv: 'Export all data as csv',
9571 exporterVisibleAsCsv: 'Export visible data as csv',
9572 exporterSelectedAsCsv: 'Export selected data as csv',
9573 exporterAllAsPdf: 'Export all data as pdf',
9574 exporterVisibleAsPdf: 'Export visible data as pdf',
9575 exporterSelectedAsPdf: 'Export selected data as pdf'
9578 noHeaders: 'Column names were unable to be derived, does the file have a header?',
9579 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9580 invalidCsv: 'File was unable to be processed, is it valid CSV?',
9581 invalidJson: 'File was unable to be processed, is it valid Json?',
9582 jsonNotArray: 'Imported json file must contain an array, aborting.'
9585 sizes: 'items per page',
9594 angular.module('ui.grid').config(['$provide', function($provide) {
9595 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9596 $delegate.add('es', {
9601 description: 'Arrastre un encabezado de columna aquí y suéltelo para agrupar por esa columna.'
9604 placeholder: 'Buscar...',
9605 showingItems: 'Artículos Mostrados:',
9606 selectedItems: 'Artículos Seleccionados:',
9607 totalItems: 'Artículos Totales:',
9608 size: 'Tamaño de Página:',
9609 first: 'Primera Página',
9610 next: 'Página Siguiente',
9611 previous: 'Página Anterior',
9612 last: 'Última Página'
9615 text: 'Elegir columnas:'
9618 ascending: 'Orden Ascendente',
9619 descending: 'Orden Descendente',
9620 remove: 'Sin Ordenar'
9623 hide: 'Ocultar la columna'
9626 count: 'filas totales: ',
9633 pinLeft: 'Fijar a la Izquierda',
9634 pinRight: 'Fijar a la Derecha',
9635 unpin: 'Quitar Fijación'
9638 columns: 'Columnas:',
9639 importerTitle: 'Importar archivo',
9640 exporterAllAsCsv: 'Exportar todo como csv',
9641 exporterVisibleAsCsv: 'Exportar vista como csv',
9642 exporterSelectedAsCsv: 'Exportar selección como csv',
9643 exporterAllAsPdf: 'Exportar todo como pdf',
9644 exporterVisibleAsPdf: 'Exportar vista como pdf',
9645 exporterSelectedAsPdf: 'Exportar selección como pdf'
9648 noHeaders: 'No fue posible derivar los nombres de las columnas, ¿tiene encabezados el archivo?',
9649 noObjects: 'No fue posible obtener registros, ¿contiene datos el archivo, aparte de los encabezados?',
9650 invalidCsv: 'No fue posible procesar el archivo, ¿es un CSV válido?',
9651 invalidJson: 'No fue posible procesar el archivo, ¿es un Json válido?',
9652 jsonNotArray: 'El archivo json importado debe contener un array, abortando.'
9661 angular.module('ui.grid').config(['$provide', function($provide) {
9662 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9663 $delegate.add('fa', {
9668 description: 'یک عنوان ستون اینجا را بردار و به گروهی از آن ستون بیانداز.'
9671 placeholder: 'جستجو...',
9672 showingItems: 'نمایش موردها:',
9673 selectedItems: 'موردهای انتخاب\u200cشده:',
9674 totalItems: 'همهٔ موردها:',
9675 size: 'اندازهٔ صفحه:',
9678 previous: 'صفحهٔ قبل',
9682 text: 'انتخاب ستون\u200cها:'
9685 hide: 'ستون پنهان کن'
9688 count: 'total rows: ',
9695 columns: 'Columns:',
9696 importerTitle: 'Import file',
9697 exporterAllAsCsv: 'Export all data as csv',
9698 exporterVisibleAsCsv: 'Export visible data as csv',
9699 exporterSelectedAsCsv: 'Export selected data as csv',
9700 exporterAllAsPdf: 'Export all data as pdf',
9701 exporterVisibleAsPdf: 'Export visible data as pdf',
9702 exporterSelectedAsPdf: 'Export selected data as pdf'
9705 noHeaders: 'Column names were unable to be derived, does the file have a header?',
9706 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9707 invalidCsv: 'File was unable to be processed, is it valid CSV?',
9708 invalidJson: 'File was unable to be processed, is it valid Json?',
9709 jsonNotArray: 'Imported json file must contain an array, aborting.'
9717 angular.module('ui.grid').config(['$provide', function($provide) {
9718 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9719 $delegate.add('fi', {
9724 description: 'Raahaa ja pudota otsikko tähän ryhmittääksesi sarakkeen mukaan.'
9727 placeholder: 'Hae...',
9728 showingItems: 'Näytetään rivejä:',
9729 selectedItems: 'Valitut rivit:',
9730 totalItems: 'Rivejä yht.:',
9732 first: 'Ensimmäinen sivu',
9733 next: 'Seuraava sivu',
9734 previous: 'Edellinen sivu',
9735 last: 'Viimeinen sivu'
9738 text: 'Valitse sarakkeet:'
9741 ascending: 'Järjestä nouseva',
9742 descending: 'Järjestä laskeva',
9743 remove: 'Poista järjestys'
9746 hide: 'Piilota sarake'
9749 count: 'Rivejä yht.: ',
9756 pinLeft: 'Lukitse vasemmalle',
9757 pinRight: 'Lukitse oikealle',
9758 unpin: 'Poista lukitus'
9761 columns: 'Sarakkeet:',
9762 importerTitle: 'Tuo tiedosto',
9763 exporterAllAsCsv: 'Vie tiedot csv-muodossa',
9764 exporterVisibleAsCsv: 'Vie näkyvä tieto csv-muodossa',
9765 exporterSelectedAsCsv: 'Vie valittu tieto csv-muodossa',
9766 exporterAllAsPdf: 'Vie tiedot pdf-muodossa',
9767 exporterVisibleAsPdf: 'Vie näkyvä tieto pdf-muodossa',
9768 exporterSelectedAsPdf: 'Vie valittu tieto pdf-muodossa'
9771 noHeaders: 'Sarakkeen nimiä ei voitu päätellä, onko tiedostossa otsikkoriviä?',
9772 noObjects: 'Tietoja ei voitu lukea, onko tiedostossa muuta kuin otsikkot?',
9773 invalidCsv: 'Tiedostoa ei voitu käsitellä, oliko se CSV-muodossa?',
9774 invalidJson: 'Tiedostoa ei voitu käsitellä, oliko se JSON-muodossa?',
9775 jsonNotArray: 'Tiedosto ei sisältänyt taulukkoa, lopetetaan.'
9784 angular.module('ui.grid').config(['$provide', function($provide) {
9785 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9786 $delegate.add('fr', {
9791 description: 'Faites glisser un en-tête de colonne ici et déposez-le vers un groupe par cette colonne.'
9794 placeholder: 'Recherche...',
9795 showingItems: 'Articles Affichage des:',
9796 selectedItems: 'Éléments Articles:',
9797 totalItems: 'Nombre total d\'articles:',
9798 size: 'Taille de page:',
9799 first: 'Première page',
9800 next: 'Page Suivante',
9801 previous: 'Page précédente',
9802 last: 'Dernière page'
9805 text: 'Choisir des colonnes:'
9808 ascending: 'Trier par ordre croissant',
9809 descending: 'Trier par ordre décroissant',
9810 remove: 'Enlever le tri'
9813 hide: 'Cacher la colonne'
9816 count: 'total lignes: ',
9823 pinLeft: 'Épingler à gauche',
9824 pinRight: 'Épingler à droite',
9828 columns: 'Colonnes:',
9829 importerTitle: 'Importer un fichier',
9830 exporterAllAsCsv: 'Exporter toutes les données en CSV',
9831 exporterVisibleAsCsv: 'Exporter les données visibles en CSV',
9832 exporterSelectedAsCsv: 'Exporter les données sélectionnées en CSV',
9833 exporterAllAsPdf: 'Exporter toutes les données en PDF',
9834 exporterVisibleAsPdf: 'Exporter les données visibles en PDF',
9835 exporterSelectedAsPdf: 'Exporter les données sélectionnées en PDF'
9838 noHeaders: 'Impossible de déterminer le nom des colonnes, le fichier possède-t-il un en-tête ?',
9839 noObjects: 'Aucun objet trouvé, le fichier possède-t-il des données autres que l\'en-tête ?',
9840 invalidCsv: 'Le fichier n\'a pas pu être traité, le CSV est-il valide ?',
9841 invalidJson: 'Le fichier n\'a pas pu être traité, le JSON est-il valide ?',
9842 jsonNotArray: 'Le fichier JSON importé doit contenir un tableau. Abandon.'
9850 angular.module('ui.grid').config(['$provide', function ($provide) {
9851 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
9852 $delegate.add('he', {
9857 description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
9860 placeholder: 'חפש...',
9861 showingItems: 'מציג:',
9862 selectedItems: 'סה"כ נבחרו:',
9863 totalItems: 'סה"כ רשומות:',
9864 size: 'תוצאות בדף:',
9867 previous: 'דף קודם',
9874 ascending: 'סדר עולה',
9875 descending: 'סדר יורד',
9882 count: 'total rows: ',
9889 columns: 'Columns:',
9890 importerTitle: 'Import file',
9891 exporterAllAsCsv: 'Export all data as csv',
9892 exporterVisibleAsCsv: 'Export visible data as csv',
9893 exporterSelectedAsCsv: 'Export selected data as csv',
9894 exporterAllAsPdf: 'Export all data as pdf',
9895 exporterVisibleAsPdf: 'Export visible data as pdf',
9896 exporterSelectedAsPdf: 'Export selected data as pdf'
9899 noHeaders: 'Column names were unable to be derived, does the file have a header?',
9900 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
9901 invalidCsv: 'File was unable to be processed, is it valid CSV?',
9902 invalidJson: 'File was unable to be processed, is it valid Json?',
9903 jsonNotArray: 'Imported json file must contain an array, aborting.'
9911 angular.module('ui.grid').config(['$provide', function($provide) {
9912 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9913 $delegate.add('it', {
9918 description: 'Trascina un\'intestazione all\'interno del gruppo della colonna.'
9921 placeholder: 'Ricerca...',
9922 showingItems: 'Mostra:',
9923 selectedItems: 'Selezionati:',
9924 totalItems: 'Totali:',
9925 size: 'Tot Pagine:',
9928 previous: 'Precedente',
9932 text: 'Scegli le colonne:'
9936 descending: 'Desc.',
9937 remove: 'Annulla ordinamento'
9943 count: 'righe totali: ',
9950 pinLeft: 'Blocca a sx',
9951 pinRight: 'Blocca a dx',
9952 unpin: 'Blocca in alto'
9955 columns: 'Colonne:',
9956 importerTitle: 'Importa',
9957 exporterAllAsCsv: 'Esporta tutti i dati in CSV',
9958 exporterVisibleAsCsv: 'Esporta i dati visibili in CSV',
9959 exporterSelectedAsCsv: 'Esporta i dati selezionati in CSV',
9960 exporterAllAsPdf: 'Esporta tutti i dati in PDF',
9961 exporterVisibleAsPdf: 'Esporta i dati visibili in PDF',
9962 exporterSelectedAsPdf: 'Esporta i dati selezionati in PDF'
9965 noHeaders: 'Impossibile reperire i nomi delle colonne, sicuro che siano indicati all\'interno del file?',
9966 noObjects: 'Impossibile reperire gli oggetti, sicuro che siano indicati all\'interno del file?',
9967 invalidCsv: 'Impossibile elaborare il file, sicuro che sia un CSV?',
9968 invalidJson: 'Impossibile elaborare il file, sicuro che sia un JSON valido?',
9969 jsonNotArray: 'Errore! Il file JSON da importare deve contenere un array.'
9977 angular.module('ui.grid').config(['$provide', function($provide) {
9978 $provide.decorator('i18nService', ['$delegate', function($delegate) {
9979 $delegate.add('nl', {
9984 description: 'Sleep hier een kolomnaam heen om op te groeperen.'
9987 placeholder: 'Zoeken...',
9988 showingItems: 'Getoonde items:',
9989 selectedItems: 'Geselecteerde items:',
9990 totalItems: 'Totaal aantal items:',
9991 size: 'Items per pagina:',
9992 first: 'Eerste pagina',
9993 next: 'Volgende pagina',
9994 previous: 'Vorige pagina',
9995 last: 'Laatste pagina'
9998 text: 'Kies kolommen:'
10001 ascending: 'Sorteer oplopend',
10002 descending: 'Sorteer aflopend',
10003 remove: 'Verwijder sortering'
10006 hide: 'Verberg kolom'
10009 count: 'Aantal rijen: ',
10011 avg: 'Gemiddelde: ',
10016 pinLeft: 'Zet links vast',
10017 pinRight: 'Zet rechts vast',
10021 columns: 'Kolommen:',
10022 importerTitle: 'Importeer bestand',
10023 exporterAllAsCsv: 'Exporteer alle data als csv',
10024 exporterVisibleAsCsv: 'Exporteer zichtbare data als csv',
10025 exporterSelectedAsCsv: 'Exporteer geselecteerde data als csv',
10026 exporterAllAsPdf: 'Exporteer alle data als pdf',
10027 exporterVisibleAsPdf: 'Exporteer zichtbare data als pdf',
10028 exporterSelectedAsPdf: 'Exporteer geselecteerde data als pdf'
10031 noHeaders: 'Kolomnamen kunnen niet worden afgeleid. Heeft het bestand een header?',
10032 noObjects: 'Objecten kunnen niet worden afgeleid. Bevat het bestand data naast de headers?',
10033 invalidCsv: 'Het bestand kan niet verwerkt worden. Is het een valide csv bestand?',
10034 invalidJson: 'Het bestand kan niet verwerkt worden. Is het valide json?',
10035 jsonNotArray: 'Het json bestand moet een array bevatten. De actie wordt geannuleerd.'
10043 angular.module('ui.grid').config(['$provide', function($provide) {
10044 $provide.decorator('i18nService', ['$delegate', function($delegate) {
10045 $delegate.add('pt-br', {
10050 description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
10053 placeholder: 'Procurar...',
10054 showingItems: 'Mostrando os Itens:',
10055 selectedItems: 'Items Selecionados:',
10056 totalItems: 'Total de Itens:',
10057 size: 'Tamanho da Página:',
10058 first: 'Primeira Página',
10059 next: 'Próxima Página',
10060 previous: 'Página Anterior',
10061 last: 'Última Página'
10064 text: 'Selecione as colunas:'
10067 ascending: 'Ordenar Ascendente',
10068 descending: 'Ordenar Descendente',
10069 remove: 'Remover Ordenação'
10072 hide: 'Esconder coluna'
10075 count: 'total de linhas: ',
10082 pinLeft: 'Fixar Esquerda',
10083 pinRight: 'Fixar Direita',
10084 unpin: 'Desprender'
10087 columns: 'Colunas:',
10088 exporterAllAsCsv: 'Exportar todos os dados como csv',
10089 exporterVisibleAsCsv: 'Exportar dados visíveis como csv',
10090 exporterSelectedAsCsv: 'Exportar dados selecionados como csv',
10091 exporterAllAsPdf: 'Exportar todos os dados como pdf',
10092 exporterVisibleAsPdf: 'Exportar dados visíveis como pdf',
10093 exporterSelectedAsPdf: 'Exportar dados selecionados como pdf'
10096 noHeaders: 'Nomes de colunas não puderam ser derivados. O arquivo tem um cabeçalho?',
10097 noObjects: 'Objetos não puderam ser derivados. Havia dados no arquivo, além dos cabeçalhos?',
10098 invalidCsv: 'Arquivo não pode ser processado. É um CSV válido?',
10099 invalidJson: 'Arquivo não pode ser processado. É um Json válido?',
10100 jsonNotArray: 'Arquivo json importado tem que conter um array. Abortando.'
10108 angular.module('ui.grid').config(['$provide', function($provide) {
10109 $provide.decorator('i18nService', ['$delegate', function($delegate) {
10110 $delegate.add('ru', {
10115 description: 'Для группировки по столбцу перетащите сюда его название.'
10118 placeholder: 'Поиск...',
10119 showingItems: 'Показать элементы:',
10120 selectedItems: 'Выбранные элементы:',
10121 totalItems: 'Всего элементов:',
10122 size: 'Размер страницы:',
10123 first: 'Первая страница',
10124 next: 'Следующая страница',
10125 previous: 'Предыдущая страница',
10126 last: 'Последняя страница'
10129 text: 'Выбрать столбцы:'
10132 ascending: 'По возрастанию',
10133 descending: 'По убыванию',
10134 remove: 'Убрать сортировку'
10137 hide: 'спрятать столбец'
10140 count: 'total rows: ',
10147 columns: 'Columns:',
10148 importerTitle: 'Import file',
10149 exporterAllAsCsv: 'Export all data as csv',
10150 exporterVisibleAsCsv: 'Export visible data as csv',
10151 exporterSelectedAsCsv: 'Export selected data as csv',
10152 exporterAllAsPdf: 'Export all data as pdf',
10153 exporterVisibleAsPdf: 'Export visible data as pdf',
10154 exporterSelectedAsPdf: 'Export selected data as pdf'
10157 noHeaders: 'Column names were unable to be derived, does the file have a header?',
10158 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
10159 invalidCsv: 'File was unable to be processed, is it valid CSV?',
10160 invalidJson: 'File was unable to be processed, is it valid Json?',
10161 jsonNotArray: 'Imported json file must contain an array, aborting.'
10169 angular.module('ui.grid').config(['$provide', function($provide) {
10170 $provide.decorator('i18nService', ['$delegate', function($delegate) {
10171 $delegate.add('sk', {
10176 description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
10179 placeholder: 'Hľadaj...',
10180 showingItems: 'Zobrazujem položky:',
10181 selectedItems: 'Vybraté položky:',
10182 totalItems: 'Počet položiek:',
10184 first: 'Prvá strana',
10185 next: 'Ďalšia strana',
10186 previous: 'Predchádzajúca strana',
10187 last: 'Posledná strana'
10190 text: 'Vyberte stĺpce:'
10193 ascending: 'Zotriediť vzostupne',
10194 descending: 'Zotriediť zostupne',
10195 remove: 'Vymazať triedenie'
10198 count: 'total rows: ',
10205 columns: 'Columns:',
10206 importerTitle: 'Import file',
10207 exporterAllAsCsv: 'Export all data as csv',
10208 exporterVisibleAsCsv: 'Export visible data as csv',
10209 exporterSelectedAsCsv: 'Export selected data as csv',
10210 exporterAllAsPdf: 'Export all data as pdf',
10211 exporterVisibleAsPdf: 'Export visible data as pdf',
10212 exporterSelectedAsPdf: 'Export selected data as pdf'
10215 noHeaders: 'Column names were unable to be derived, does the file have a header?',
10216 noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
10217 invalidCsv: 'File was unable to be processed, is it valid CSV?',
10218 invalidJson: 'File was unable to be processed, is it valid Json?',
10219 jsonNotArray: 'Imported json file must contain an array, aborting.'
10228 angular.module('ui.grid').config(['$provide', function($provide) {
10229 $provide.decorator('i18nService', ['$delegate', function($delegate) {
10230 $delegate.add('sv', {
10235 description: 'Dra en kolumnrubrik hit och släpp den för att gruppera efter den kolumnen.'
10238 placeholder: 'Sök...',
10239 showingItems: 'Visar artiklar:',
10240 selectedItems: 'Valda artiklar:',
10241 totalItems: 'Antal artiklar:',
10242 size: 'Sidstorlek:',
10243 first: 'Första sidan',
10244 next: 'Nästa sida',
10245 previous: 'Föregående sida',
10246 last: 'Sista sidan'
10249 text: 'Välj kolumner:'
10252 ascending: 'Sortera stigande',
10253 descending: 'Sortera fallande',
10254 remove: 'Inaktivera sortering'
10260 count: 'Antal rader: ',
10262 avg: 'Genomsnitt: ',
10267 pinLeft: 'Fäst vänster',
10268 pinRight: 'Fäst höger',
10272 columns: 'Kolumner:',
10273 importerTitle: 'Importera fil',
10274 exporterAllAsCsv: 'Exportera all data som CSV',
10275 exporterVisibleAsCsv: 'Exportera synlig data som CSV',
10276 exporterSelectedAsCsv: 'Exportera markerad data som CSV',
10277 exporterAllAsPdf: 'Exportera all data som PDF',
10278 exporterVisibleAsPdf: 'Exportera synlig data som PDF',
10279 exporterSelectedAsPdf: 'Exportera markerad data som PDF'
10282 noHeaders: 'Kolumnnamn kunde inte härledas. Har filen ett sidhuvud?',
10283 noObjects: 'Objekt kunde inte härledas. Har filen data undantaget sidhuvud?',
10284 invalidCsv: 'Filen kunde inte behandlas, är den en giltig CSV?',
10285 invalidJson: 'Filen kunde inte behandlas, är den en giltig JSON?',
10286 jsonNotArray: 'Importerad JSON-fil måste innehålla ett fält. Import avbruten.'
10289 sizes: 'Artiklar per sida',
10290 totalItems: 'Artiklar'
10300 * @name ui.grid.i18n
10304 * This module provides i18n functions to ui.grid and any application that wants to use it
10307 * <div doc-module-components="ui.grid.i18n"></div>
10311 var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
10312 var FILTER_ALIASES = ['t', 'uiTranslate'];
10314 var module = angular.module('ui.grid.i18n');
10319 * @name ui.grid.i18n.constant:i18nConstants
10321 * @description constants available in i18n module
10323 module.constant('i18nConstants', {
10324 MISSING: '[MISSING]',
10325 UPDATE_EVENT: '$uiI18n',
10327 LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
10328 // default to english
10332 // module.config(['$provide', function($provide) {
10333 // $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
10337 * @name ui.grid.i18n.service:i18nService
10339 * @description Services for i18n
10341 module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
10342 function ($log, i18nConstants, $rootScope) {
10347 get: function (lang) {
10348 return this._langs[lang.toLowerCase()];
10350 add: function (lang, strings) {
10351 var lower = lang.toLowerCase();
10352 if (!this._langs[lower]) {
10353 this._langs[lower] = {};
10355 angular.extend(this._langs[lower], strings);
10357 getAllLangs: function () {
10359 if (!this._langs) {
10363 for (var key in this._langs) {
10369 setCurrent: function (lang) {
10370 this.current = lang.toLowerCase();
10372 getCurrentLang: function () {
10373 return this.current;
10382 * @methodOf ui.grid.i18n.service:i18nService
10383 * @description Adds the languages and strings to the cache. Decorate this service to
10384 * add more translation strings
10385 * @param {string} lang language to add
10386 * @param {object} stringMaps of strings to add grouped by property names
10389 * i18nService.add('en', {
10392 * label2: 'some more items'
10396 * description: 'Drag a column header here and drop it to group by that column.'
10401 add: function (langs, stringMaps) {
10402 if (typeof(langs) === 'object') {
10403 angular.forEach(langs, function (lang) {
10405 langCache.add(lang, stringMaps);
10409 langCache.add(langs, stringMaps);
10415 * @name getAllLangs
10416 * @methodOf ui.grid.i18n.service:i18nService
10417 * @description return all currently loaded languages
10418 * @returns {array} string
10420 getAllLangs: function () {
10421 return langCache.getAllLangs();
10427 * @methodOf ui.grid.i18n.service:i18nService
10428 * @description return all currently loaded languages
10429 * @param {string} lang to return. If not specified, returns current language
10430 * @returns {object} the translation string maps for the language
10432 get: function (lang) {
10433 var language = lang ? lang : service.getCurrentLang();
10434 return langCache.get(language);
10439 * @name getSafeText
10440 * @methodOf ui.grid.i18n.service:i18nService
10441 * @description returns the text specified in the path or a Missing text if text is not found
10442 * @param {string} path property path to use for retrieving text from string map
10443 * @param {string} lang to return. If not specified, returns current language
10444 * @returns {object} the translation for the path
10447 * i18nService.getSafeText('sort.ascending')
10450 getSafeText: function (path, lang) {
10451 var language = lang ? lang : service.getCurrentLang();
10452 var trans = langCache.get(language);
10455 return i18nConstants.MISSING;
10458 var paths = path.split('.');
10459 var current = trans;
10461 for (var i = 0; i < paths.length; ++i) {
10462 if (current[paths[i]] === undefined || current[paths[i]] === null) {
10463 return i18nConstants.MISSING;
10465 current = current[paths[i]];
10475 * @name setCurrentLang
10476 * @methodOf ui.grid.i18n.service:i18nService
10477 * @description sets the current language to use in the application
10478 * $broadcasts the Update_Event on the $rootScope
10479 * @param {string} lang to set
10482 * i18nService.setCurrentLang('fr');
10486 setCurrentLang: function (lang) {
10488 langCache.setCurrent(lang);
10489 $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
10495 * @name getCurrentLang
10496 * @methodOf ui.grid.i18n.service:i18nService
10497 * @description returns the current language used in the application
10499 getCurrentLang: function () {
10500 var lang = langCache.getCurrentLang();
10502 lang = i18nConstants.DEFAULT_LANG;
10503 langCache.setCurrent(lang);
10514 var localeDirective = function (i18nService, i18nConstants) {
10516 compile: function () {
10518 pre: function ($scope, $elm, $attrs) {
10519 var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
10520 // check for watchable property
10521 var lang = $scope.$eval($attrs[alias]);
10523 $scope.$watch($attrs[alias], function () {
10524 i18nService.setCurrentLang(lang);
10526 } else if ($attrs.$$observers) {
10527 $attrs.$observe(alias, function () {
10528 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
10537 module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
10539 // directive syntax
10540 var uitDirective = function ($parse, i18nService, i18nConstants) {
10543 compile: function () {
10545 pre: function ($scope, $elm, $attrs) {
10546 var alias1 = DIRECTIVE_ALIASES[0],
10547 alias2 = DIRECTIVE_ALIASES[1];
10548 var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
10549 var missing = i18nConstants.MISSING + token;
10551 if ($attrs.$$observers) {
10552 var prop = $attrs[alias1] ? alias1 : alias2;
10553 observer = $attrs.$observe(prop, function (result) {
10555 $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
10559 var getter = $parse(token);
10560 var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
10562 observer($attrs[alias1] || $attrs[alias2]);
10564 // set text based on i18n current language
10565 $elm.html(getter(i18nService.get()) || missing);
10568 $scope.$on('$destroy', listener);
10570 $elm.html(getter(i18nService.get()) || missing);
10577 angular.forEach( DIRECTIVE_ALIASES, function ( alias ) {
10578 module.directive( alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective] );
10581 // optional filter syntax
10582 var uitFilter = function ($parse, i18nService, i18nConstants) {
10583 return function (data) {
10584 var getter = $parse(data);
10585 // set text based on i18n current language
10586 return getter(i18nService.get()) || i18nConstants.MISSING + data;
10590 angular.forEach( FILTER_ALIASES, function ( alias ) {
10591 module.filter( alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter] );
10597 angular.module('ui.grid').config(['$provide', function($provide) {
10598 $provide.decorator('i18nService', ['$delegate', function($delegate) {
10599 $delegate.add('zh-cn', {
10604 description: '拖曳表头到此处进行分组'
10608 showingItems: '已显示行数:',
10609 selectedItems: '已选择行数:',
10610 totalItems: '总行数:',
10642 importerTitle: '导入文件',
10643 exporterAllAsCsv: '导出全部数据到CSV',
10644 exporterVisibleAsCsv: '导出可见数据到CSV',
10645 exporterSelectedAsCsv: '导出已选数据到CSV',
10646 exporterAllAsPdf: '导出全部数据到PDF',
10647 exporterVisibleAsPdf: '导出可见数据到PDF',
10648 exporterSelectedAsPdf: '导出已选数据到PDF'
10651 noHeaders: '无法获取列名,确定文件包含表头?',
10652 noObjects: '无法获取数据,确定文件包含数据?',
10653 invalidCsv: '无法处理文件,确定是合法的CSV文件?',
10654 invalidJson: '无法处理文件,确定是合法的JSON文件?',
10655 jsonNotArray: '导入的文件不是JSON数组!'
10664 angular.module('ui.grid').config(['$provide', function($provide) {
10665 $provide.decorator('i18nService', ['$delegate', function($delegate) {
10666 $delegate.add('zh-tw', {
10671 description: '拖曳表頭到此處進行分組'
10675 showingItems: '已顯示行數:',
10676 selectedItems: '已選擇行數:',
10677 totalItems: '總行數:',
10709 importerTitle: '導入文件',
10710 exporterAllAsCsv: '導出全部數據到CSV',
10711 exporterVisibleAsCsv: '導出可見數據到CSV',
10712 exporterSelectedAsCsv: '導出已選數據到CSV',
10713 exporterAllAsPdf: '導出全部數據到PDF',
10714 exporterVisibleAsPdf: '導出可見數據到PDF',
10715 exporterSelectedAsPdf: '導出已選數據到PDF'
10718 noHeaders: '無法獲取列名,確定文件包含表頭?',
10719 noObjects: '無法獲取數據,確定文件包含數據?',
10720 invalidCsv: '無法處理文件,確定是合法的CSV文件?',
10721 invalidJson: '無法處理文件,確定是合法的JSON文件?',
10722 jsonNotArray: '導入的文件不是JSON數組!'
10734 * @name ui.grid.autoResize
10738 * #ui.grid.autoResize
10739 * This module provides auto-resizing functionality to ui-grid
10742 var module = angular.module('ui.grid.autoResize', ['ui.grid']);
10745 module.directive('uiGridAutoResize', ['$timeout', 'gridUtil', function ($timeout, gridUtil) {
10749 link: function ($scope, $elm, $attrs, uiGridCtrl) {
10750 var prevGridWidth, prevGridHeight;
10752 function getDimensions() {
10753 prevGridHeight = gridUtil.elementHeight($elm);
10754 prevGridWidth = gridUtil.elementWidth($elm);
10757 // Initialize the dimensions
10761 function startTimeout() {
10762 $timeout.cancel(canceler);
10764 canceler = $timeout(function () {
10765 var newGridHeight = gridUtil.elementHeight($elm);
10766 var newGridWidth = gridUtil.elementWidth($elm);
10768 if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
10769 uiGridCtrl.grid.gridHeight = newGridHeight;
10770 uiGridCtrl.grid.gridWidth = newGridWidth;
10772 uiGridCtrl.grid.refresh()
10773 .then(function () {
10787 $scope.$on('$destroy', function() {
10788 $timeout.cancel(canceler);
10796 var module = angular.module('ui.grid.cellNav', ['ui.grid']);
10798 function RowCol(row, col) {
10805 * @name ui.grid.cellNav.constant:uiGridCellNavConstants
10807 * @description constants available in cellNav
10809 module.constant('uiGridCellNavConstants', {
10810 FEATURE_NAME: 'gridCellNav',
10811 CELL_NAV_EVENT: 'cellNav',
10812 direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3},
10820 module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q',
10821 function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q) {
10824 * @name ui.grid.cellNav.object:CellNav
10825 * @description returns a CellNav prototype function
10826 * @param {object} rowContainer container for rows
10827 * @param {object} colContainer parent column container
10828 * @param {object} leftColContainer column container to the left of parent
10829 * @param {object} rightColContainer column container to the right of parent
10831 var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
10832 this.rows = rowContainer.visibleRowCache;
10833 this.columns = colContainer.visibleColumnCache;
10834 this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
10835 this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
10838 /** returns focusable columns of all containers */
10839 UiGridCellNav.prototype.getFocusableCols = function () {
10840 var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
10842 return allColumns.filter(function (col) {
10843 return col.colDef.allowCellFocus;
10847 UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
10848 switch (direction) {
10849 case uiGridCellNavConstants.direction.LEFT:
10850 return this.getRowColLeft(curRow, curCol);
10851 case uiGridCellNavConstants.direction.RIGHT:
10852 return this.getRowColRight(curRow, curCol);
10853 case uiGridCellNavConstants.direction.UP:
10854 return this.getRowColUp(curRow, curCol);
10855 case uiGridCellNavConstants.direction.DOWN:
10856 return this.getRowColDown(curRow, curCol);
10861 UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
10862 var focusableCols = this.getFocusableCols();
10863 var curColIndex = focusableCols.indexOf(curCol);
10864 var curRowIndex = this.rows.indexOf(curRow);
10866 //could not find column in focusable Columns so set it to 1
10867 if (curColIndex === -1) {
10871 var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
10873 //get column to left
10874 if (nextColIndex > curColIndex) {
10875 if (curRowIndex === 0) {
10876 return new RowCol(curRow, focusableCols[nextColIndex]); //return same row
10879 //up one row and far right column
10880 return new RowCol(this.rows[curRowIndex - 1], focusableCols[nextColIndex]);
10884 return new RowCol(curRow, focusableCols[nextColIndex]);
10888 UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
10889 var focusableCols = this.getFocusableCols();
10890 var curColIndex = focusableCols.indexOf(curCol);
10891 var curRowIndex = this.rows.indexOf(curRow);
10893 //could not find column in focusable Columns so set it to 0
10894 if (curColIndex === -1) {
10897 var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
10899 if (nextColIndex < curColIndex) {
10900 if (curRowIndex === this.rows.length - 1) {
10901 return new RowCol(curRow, focusableCols[nextColIndex]); //return same row
10904 //down one row and far left column
10905 return new RowCol(this.rows[curRowIndex + 1], focusableCols[nextColIndex]);
10909 return new RowCol(curRow, focusableCols[nextColIndex]);
10913 UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
10914 var focusableCols = this.getFocusableCols();
10915 var curColIndex = focusableCols.indexOf(curCol);
10916 var curRowIndex = this.rows.indexOf(curRow);
10918 //could not find column in focusable Columns so set it to 0
10919 if (curColIndex === -1) {
10923 if (curRowIndex === this.rows.length - 1) {
10924 return new RowCol(curRow, focusableCols[curColIndex]); //return same row
10928 return new RowCol(this.rows[curRowIndex + 1], focusableCols[curColIndex]);
10932 UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
10933 var focusableCols = this.getFocusableCols();
10934 var curColIndex = focusableCols.indexOf(curCol);
10935 var curRowIndex = this.rows.indexOf(curRow);
10937 //could not find column in focusable Columns so set it to 0
10938 if (curColIndex === -1) {
10942 if (curRowIndex === 0) {
10943 return new RowCol(curRow, focusableCols[curColIndex]); //return same row
10947 return new RowCol(this.rows[curRowIndex - 1], focusableCols[curColIndex]);
10951 return UiGridCellNav;
10956 * @name ui.grid.cellNav.service:uiGridCellNavService
10958 * @description Services for cell navigation features. If you don't like the key maps we use,
10959 * or the direction cells navigation, override with a service decorator (see angular docs)
10961 module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory',
10962 function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav) {
10966 initializeGrid: function (grid) {
10967 grid.registerColumnBuilder(service.cellNavColumnBuilder);
10969 //create variables for state
10971 grid.cellNav.lastRowCol = null;
10975 * @name ui.grid.cellNav.api:PublicApi
10977 * @description Public Api for cellNav feature
10985 * @eventOf ui.grid.cellNav.api:PublicApi
10986 * @description raised when the active cell is changed
10988 * gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
10990 * @param {object} newRowCol new position
10991 * @param {object} oldRowCol old position
10993 navigate: function (newRowCol, oldRowCol) {
11002 * @methodOf ui.grid.cellNav.api:PublicApi
11003 * @description brings the specified row and column into view
11004 * @param {Grid} grid the grid you'd like to act upon, usually available
11005 * from gridApi.grid
11006 * @param {object} $scope a scope we can broadcast events from
11007 * @param {object} rowEntity gridOptions.data[] array instance to make visible
11008 * @param {object} colDef to make visible
11010 scrollTo: function (grid, $scope, rowEntity, colDef) {
11011 service.scrollTo(grid, $scope, rowEntity, colDef);
11016 * @name getFocusedCell
11017 * @methodOf ui.grid.cellNav.api:PublicApi
11018 * @description returns the current (or last if Grid does not have focus) focused row and column
11019 * <br> value is null if no selection has occurred
11021 getFocusedCell: function () {
11022 return grid.cellNav.lastRowCol;
11028 grid.api.registerEventsFromObject(publicApi.events);
11030 grid.api.registerMethodsFromObject(publicApi.methods);
11036 * @name decorateRenderContainers
11037 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11038 * @description decorates grid renderContainers with cellNav functions
11040 decorateRenderContainers: function (grid) {
11042 var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
11043 var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
11045 if (leftContainer !== null) {
11046 grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
11048 if (rightContainer !== null) {
11049 grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
11052 grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
11057 * @name getDirection
11058 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11059 * @description determines which direction to for a given keyDown event
11060 * @returns {uiGridCellNavConstants.direction} direction
11062 getDirection: function (evt) {
11063 if (evt.keyCode === uiGridConstants.keymap.LEFT ||
11064 (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
11065 return uiGridCellNavConstants.direction.LEFT;
11067 if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
11068 evt.keyCode === uiGridConstants.keymap.TAB) {
11069 return uiGridCellNavConstants.direction.RIGHT;
11072 if (evt.keyCode === uiGridConstants.keymap.UP ||
11073 (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey)) {
11074 return uiGridCellNavConstants.direction.UP;
11077 if (evt.keyCode === uiGridConstants.keymap.DOWN ||
11078 evt.keyCode === uiGridConstants.keymap.ENTER) {
11079 return uiGridCellNavConstants.direction.DOWN;
11087 * @name cellNavColumnBuilder
11088 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11089 * @description columnBuilder function that adds cell navigation properties to grid column
11090 * @returns {promise} promise that will load any needed templates when resolved
11092 cellNavColumnBuilder: function (colDef, col, gridOptions) {
11097 * @name ui.grid.cellNav.api:ColumnDef
11099 * @description Column Definitions for cellNav feature, these are available to be
11100 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
11105 * @name allowCellFocus
11106 * @propertyOf ui.grid.cellNav.api:ColumnDef
11107 * @description Enable focus on a cell.
11108 * <br/>Defaults to true
11110 colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
11112 return $q.all(promises);
11117 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11119 * @description Scroll the grid such that the specified
11120 * row and column is in view
11121 * @param {Grid} grid the grid you'd like to act upon, usually available
11122 * from gridApi.grid
11123 * @param {object} $scope a scope we can broadcast events from
11124 * @param {object} rowEntity gridOptions.data[] array instance to make visible
11125 * @param {object} colDef to make visible
11127 scrollTo: function (grid, $scope, rowEntity, colDef) {
11128 var gridRow = null, gridCol = null;
11130 if (rowEntity !== null) {
11131 gridRow = grid.getRow(rowEntity);
11134 if (colDef !== null) {
11135 gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
11137 this.scrollToInternal(grid, $scope, gridRow, gridCol);
11142 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11143 * @name scrollToInternal
11144 * @description Like scrollTo, but takes gridRow and gridCol.
11145 * In calculating the scroll height we have to deal with wanting
11146 * 0% for the first row, and 100% for the last row. Normal maths
11147 * for a 10 row list would return 1/10 = 10% for the first row, so
11148 * we need to tweak the numbers to add an extra 10% somewhere. The
11149 * formula if we're trying to get to row 0 in a 10 row list (assuming our
11150 * index is zero based, so the last row is row 9) is:
11155 * To get to row 9 (i.e. the last row) in the same list, we want to
11158 * ( 9 + 1 ) / 10 = 100%
11160 * So we need to apportion one whole row within the overall grid scroll,
11163 * ( index + ( index / (total rows - 1) ) / total rows
11165 * @param {Grid} grid the grid you'd like to act upon, usually available
11166 * from gridApi.grid
11167 * @param {object} $scope a scope we can broadcast events from
11168 * @param {GridRow} gridRow row to make visible
11169 * @param {GridCol} gridCol column to make visible
11171 scrollToInternal: function (grid, $scope, gridRow, gridCol) {
11174 if (gridRow !== null) {
11175 var seekRowIndex = grid.renderContainers.body.visibleRowCache.indexOf(gridRow);
11176 var totalRows = grid.renderContainers.body.visibleRowCache.length;
11177 var percentage = ( seekRowIndex + ( seekRowIndex / ( totalRows - 1 ) ) ) / totalRows;
11178 args.y = { percentage: percentage };
11181 if (gridCol !== null) {
11182 args.x = { percentage: this.getLeftWidth(grid, gridCol) / this.getLeftWidth(grid, grid.renderContainers.body.visibleColumnCache[grid.renderContainers.body.visibleColumnCache.length - 1] ) };
11185 if (args.y || args.x) {
11186 $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
11192 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11193 * @name scrollToIfNecessary
11194 * @description Scrolls the grid to make a certain row and column combo visible,
11195 * in the case that it is not completely visible on the screen already.
11196 * @param {Grid} grid the grid you'd like to act upon, usually available
11197 * from gridApi.grid
11198 * @param {object} $scope a scope we can broadcast events from
11199 * @param {GridRow} gridRow row to make visible
11200 * @param {GridCol} gridCol column to make visible
11202 scrollToIfNecessary: function (grid, $scope, gridRow, gridCol) {
11205 // Alias the visible row and column caches
11206 var visRowCache = grid.renderContainers.body.visibleRowCache;
11207 var visColCache = grid.renderContainers.body.visibleColumnCache;
11209 /*-- Get the top, left, right, and bottom "scrolled" edges of the grid --*/
11211 // The top boundary is the current Y scroll position PLUS the header height, because the header can obscure rows when the grid is scrolled downwards
11212 var topBound = grid.renderContainers.body.prevScrollTop + grid.headerHeight;
11214 // Don't the let top boundary be less than 0
11215 topBound = (topBound < 0) ? 0 : topBound;
11217 // The left boundary is the current X scroll position
11218 var leftBound = grid.renderContainers.body.prevScrollLeft;
11220 // The bottom boundary is the current Y scroll position, plus the height of the grid, but minus the header height.
11221 // Basically this is the viewport height added on to the scroll position
11222 var bottomBound = grid.renderContainers.body.prevScrollTop + grid.gridHeight - grid.headerHeight;
11224 // If there's a horizontal scrollbar, remove its height from the bottom boundary, otherwise we'll be letting it obscure rows
11225 if (grid.horizontalScrollbarHeight) {
11226 bottomBound = bottomBound - grid.horizontalScrollbarHeight;
11229 // The right position is the current X scroll position minus the grid width
11230 var rightBound = grid.renderContainers.body.prevScrollLeft + grid.gridWidth;
11232 // If there's a vertical scrollbar, subtract it from the right boundary or we'll allow it to obscure cells
11233 if (grid.verticalScrollbarWidth) {
11234 rightBound = rightBound - grid.verticalScrollbarWidth;
11237 // We were given a row to scroll to
11238 if (gridRow !== null) {
11239 // This is the index of the row we want to scroll to, within the list of rows that can be visible
11240 var seekRowIndex = visRowCache.indexOf(gridRow);
11242 // Total vertical scroll length of the grid
11243 var scrollLength = (grid.renderContainers.body.getCanvasHeight() - grid.renderContainers.body.getViewportHeight());
11245 // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
11246 if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
11247 scrollLength = scrollLength + grid.horizontalScrollbarHeight;
11250 // This is the minimum amount of pixels we need to scroll vertical in order to see this row.
11251 var pixelsToSeeRow = ((seekRowIndex + 1) * grid.options.rowHeight);
11253 // Don't let the pixels required to see the row be less than zero
11254 pixelsToSeeRow = (pixelsToSeeRow < 0) ? 0 : pixelsToSeeRow;
11256 var scrollPixels, percentage;
11258 // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the grid...
11259 if (pixelsToSeeRow < topBound) {
11260 // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
11261 // to get the full position we need
11262 scrollPixels = grid.renderContainers.body.prevScrollTop - (topBound - pixelsToSeeRow);
11264 // Turn the scroll position into a percentage and make it an argument for a scroll event
11265 percentage = scrollPixels / scrollLength;
11266 args.y = { percentage: percentage };
11268 // Otherwise if the scroll position we need to see the row is MORE than the bottom boundary, i.e. obscured below the bottom of the grid...
11269 else if (pixelsToSeeRow > bottomBound) {
11270 // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
11271 // to get the full position we need
11272 scrollPixels = pixelsToSeeRow - bottomBound + grid.renderContainers.body.prevScrollTop;
11274 // Turn the scroll position into a percentage and make it an argument for a scroll event
11275 percentage = scrollPixels / scrollLength;
11276 args.y = { percentage: percentage };
11280 // We were given a column to scroll to
11281 if (gridCol !== null) {
11282 // This is the index of the row we want to scroll to, within the list of rows that can be visible
11283 var seekColumnIndex = visColCache.indexOf(gridCol);
11285 // Total vertical scroll length of the grid
11286 var horizScrollLength = (grid.renderContainers.body.getCanvasWidth() - grid.renderContainers.body.getViewportWidth());
11288 // Add the height of the native horizontal scrollbar to the scroll length, if it's there. Otherwise it will mask over the final row
11289 // if (grid.verticalScrollbarWidth && grid.verticalScrollbarWidth > 0) {
11290 // horizScrollLength = horizScrollLength + grid.verticalScrollbarWidth;
11293 // This is the minimum amount of pixels we need to scroll vertical in order to see this column
11294 var columnLeftEdge = 0;
11295 for (var i = 0; i < seekColumnIndex; i++) {
11296 var col = visColCache[i];
11297 columnLeftEdge += col.drawnWidth;
11299 columnLeftEdge = (columnLeftEdge < 0) ? 0 : columnLeftEdge;
11301 var columnRightEdge = columnLeftEdge + gridCol.drawnWidth;
11303 // Don't let the pixels required to see the column be less than zero
11304 columnRightEdge = (columnRightEdge < 0) ? 0 : columnRightEdge;
11306 var horizScrollPixels, horizPercentage;
11308 // If the scroll position we need to see the row is LESS than the top boundary, i.e. obscured above the top of the grid...
11309 if (columnLeftEdge < leftBound) {
11310 // Get the different between the top boundary and the required scroll position and subtract it from the current scroll position\
11311 // to get the full position we need
11312 horizScrollPixels = grid.renderContainers.body.prevScrollLeft - (leftBound - columnLeftEdge);
11314 // Turn the scroll position into a percentage and make it an argument for a scroll event
11315 horizPercentage = horizScrollPixels / horizScrollLength;
11316 horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
11317 args.x = { percentage: horizPercentage };
11319 // Otherwise if the scroll position we need to see the row is MORE than the bottom boundary, i.e. obscured below the bottom of the grid...
11320 else if (columnRightEdge > rightBound) {
11321 // Get the different between the bottom boundary and the required scroll position and add it to the current scroll position
11322 // to get the full position we need
11323 horizScrollPixels = columnRightEdge - rightBound + grid.renderContainers.body.prevScrollLeft;
11325 // Turn the scroll position into a percentage and make it an argument for a scroll event
11326 horizPercentage = horizScrollPixels / horizScrollLength;
11327 horizPercentage = (horizPercentage > 1) ? 1 : horizPercentage;
11328 args.x = { percentage: horizPercentage };
11332 // If we need to scroll on either the x or y axes, fire a scroll event
11333 if (args.y || args.x) {
11334 $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
11340 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
11341 * @name getLeftWidth
11342 * @description Get the current drawn width of the columns in the
11343 * grid up to the numbered column, and add an apportionment for the
11344 * column that we're on. So if we are on column 0, we want to scroll
11345 * 0% (i.e. exclude this column from calc). If we're on the last column
11346 * we want to scroll to 100% (i.e. include this column in the calc). So
11347 * we include (thisColIndex / totalNumberCols) % of this column width
11348 * @param {Grid} grid the grid you'd like to act upon, usually available
11349 * from gridApi.grid
11350 * @param {gridCol} upToCol the column to total up to and including
11352 getLeftWidth: function (grid, upToCol) {
11359 var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
11361 // total column widths up-to but not including the passed in column
11362 grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
11363 if ( index < lastIndex ){
11364 width += col.drawnWidth;
11368 // pro-rata the final column based on % of total columns.
11369 var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
11370 width += upToCol.drawnWidth * percentage;
11381 * @name ui.grid.cellNav.directive:uiCellNav
11385 * @description Adds cell navigation features to the grid columns
11388 <example module="app">
11389 <file name="app.js">
11390 var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
11392 app.controller('MainCtrl', ['$scope', function ($scope) {
11394 { name: 'Bob', title: 'CEO' },
11395 { name: 'Frank', title: 'Lowly Developer' }
11398 $scope.columnDefs = [
11404 <file name="index.html">
11405 <div ng-controller="MainCtrl">
11406 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
11411 module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants',
11412 function (gridUtil, uiGridCellNavService, uiGridCellNavConstants) {
11416 require: '^uiGrid',
11418 compile: function () {
11420 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11422 var grid = uiGridCtrl.grid;
11423 uiGridCellNavService.initializeGrid(grid);
11425 uiGridCtrl.cellNav = {};
11427 uiGridCtrl.cellNav.focusCell = function (row, col) {
11428 uiGridCtrl.cellNav.broadcastCellNav({ row: row, col: col });
11431 // gridUtil.logDebug('uiGridEdit preLink');
11432 uiGridCtrl.cellNav.broadcastCellNav = function (newRowCol) {
11433 $scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol);
11434 uiGridCtrl.cellNav.broadcastFocus(newRowCol);
11437 uiGridCtrl.cellNav.broadcastFocus = function (rowCol) {
11438 var row = rowCol.row,
11441 if (grid.cellNav.lastRowCol === null || (grid.cellNav.lastRowCol.row !== row || grid.cellNav.lastRowCol.col !== col)) {
11442 var newRowCol = new RowCol(row, col);
11443 grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol);
11444 grid.cellNav.lastRowCol = newRowCol;
11448 uiGridCtrl.cellNav.handleKeyDown = function (evt) {
11449 var direction = uiGridCellNavService.getDirection(evt);
11450 if (direction === null) {
11454 var containerId = 'body';
11455 if (evt.uiGridTargetRenderContainerId) {
11456 containerId = evt.uiGridTargetRenderContainerId;
11459 // Get the last-focused row+col combo
11460 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
11462 // Figure out which new row+combo we're navigating to
11463 var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
11465 rowCol.eventType = uiGridCellNavConstants.EVENT_TYPE.KEYDOWN;
11467 // Broadcast the navigation
11468 uiGridCtrl.cellNav.broadcastCellNav(rowCol);
11470 // Scroll to the new cell, if it's not completely visible within the render container's viewport
11471 uiGridCellNavService.scrollToIfNecessary(grid, $scope, rowCol.row, rowCol.col);
11473 evt.stopPropagation();
11474 evt.preventDefault();
11480 post: function ($scope, $elm, $attrs, uiGridCtrl) {
11487 module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants',
11488 function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants) {
11491 priority: -99999, //this needs to run very last
11492 require: ['^uiGrid', 'uiGridRenderContainer'],
11494 compile: function () {
11496 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11498 post: function ($scope, $elm, $attrs, controllers) {
11499 var uiGridCtrl = controllers[0],
11500 renderContainerCtrl = controllers[1];
11502 var containerId = renderContainerCtrl.containerId;
11504 var grid = uiGridCtrl.grid;
11506 // Needs to run last after all renderContainers are built
11507 uiGridCellNavService.decorateRenderContainers(grid);
11509 // Let the render container be focus-able
11510 $elm.attr("tabindex", -1);
11512 // Bind to keydown events in the render container
11513 $elm.on('keydown', function (evt) {
11514 evt.uiGridTargetRenderContainerId = containerId;
11515 return uiGridCtrl.cellNav.handleKeyDown(evt);
11518 // When there's a scroll event we need to make sure to re-focus the right row, because the cell contents may have changed
11519 $scope.$on(uiGridConstants.events.GRID_SCROLL, function (evt, args) {
11520 // Skip if there's no currently-focused cell
11521 if (uiGridCtrl.grid.api.cellNav.getFocusedCell() == null) {
11525 // We have to wrap in TWO timeouts so that we run AFTER the scroll event is resolved.
11526 $timeout(function () {
11527 $timeout(function () {
11528 // Get the last row+col combo
11529 var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
11531 // If the body element becomes active, re-focus on the render container so we can capture cellNav events again.
11532 // NOTE: this happens when we navigate LET from the left-most cell (RIGHT from the right-most) and have to re-render a new
11533 // set of cells. The cell element we are navigating to doesn't exist and focus gets lost. This will re-capture it, imperfectly...
11534 if ($document.activeElement === $document.body) {
11538 // Re-broadcast a cellNav event so we re-focus the right cell
11539 uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
11551 * @name ui.grid.cellNav.directive:uiGridCell
11554 * @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
11556 module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants',
11557 function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants) {
11559 priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
11561 require: '^uiGrid',
11563 link: function ($scope, $elm, $attrs, uiGridCtrl) {
11564 if (!$scope.col.colDef.allowCellFocus) {
11570 // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
11571 $elm.find('div').on('click', function (evt) {
11572 uiGridCtrl.cellNav.broadcastCellNav(new RowCol($scope.row, $scope.col));
11574 evt.stopPropagation();
11577 // This event is fired for all cells. If the cell matches, then focus is set
11578 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol) {
11579 if (rowCol.row === $scope.row &&
11580 rowCol.col === $scope.col) {
11583 // This cellNav event came from a keydown event so we can safely refocus
11584 if (rowCol.hasOwnProperty('eventType') && rowCol.eventType === uiGridCellNavConstants.EVENT_TYPE.KEYDOWN) {
11585 $elm.find('div')[0].focus();
11593 function setTabEnabled() {
11594 $elm.find('div').attr("tabindex", -1);
11597 function setFocused() {
11598 var div = $elm.find('div');
11599 div.addClass('ui-grid-cell-focus');
11602 function clearFocus() {
11603 var div = $elm.find('div');
11604 div.removeClass('ui-grid-cell-focus');
11607 $scope.$on('$destroy', function () {
11608 $elm.find('div').off('click');
11620 * @name ui.grid.edit
11624 * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
11628 * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
11629 * user to key data and then tab, arrow, or enter to the cells beside or below.
11631 * <div doc-module-components="ui.grid.edit"></div>
11634 var module = angular.module('ui.grid.edit', ['ui.grid']);
11638 * @name ui.grid.edit.constant:uiGridEditConstants
11640 * @description constants available in edit module
11642 module.constant('uiGridEditConstants', {
11643 EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
11644 //must be lowercase because template bulder converts to lower
11645 EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
11647 BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
11648 END_CELL_EDIT: 'uiGridEventEndCellEdit',
11649 CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
11655 * @name ui.grid.edit.service:uiGridEditService
11657 * @description Services for editing features
11659 module.service('uiGridEditService', ['$q', '$templateCache', 'uiGridConstants', 'gridUtil',
11660 function ($q, $templateCache, uiGridConstants, gridUtil) {
11664 initializeGrid: function (grid) {
11666 service.defaultGridOptions(grid.options);
11668 grid.registerColumnBuilder(service.editColumnBuilder);
11672 * @name ui.grid.edit.api:PublicApi
11674 * @description Public Api for edit feature
11681 * @name afterCellEdit
11682 * @eventOf ui.grid.edit.api:PublicApi
11683 * @description raised when cell editing is complete
11685 * gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
11687 * @param {object} rowEntity the options.data element that was edited
11688 * @param {object} colDef the column that was edited
11689 * @param {object} newValue new value
11690 * @param {object} oldValue old value
11692 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
11696 * @name beginCellEdit
11697 * @eventOf ui.grid.edit.api:PublicApi
11698 * @description raised when cell editing starts on a cell
11700 * gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
11702 * @param {object} rowEntity the options.data element that was edited
11703 * @param {object} colDef the column that was edited
11705 beginCellEdit: function (rowEntity, colDef) {
11709 * @name cancelCellEdit
11710 * @eventOf ui.grid.edit.api:PublicApi
11711 * @description raised when cell editing is cancelled on a cell
11713 * gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
11715 * @param {object} rowEntity the options.data element that was edited
11716 * @param {object} colDef the column that was edited
11718 cancelCellEdit: function (rowEntity, colDef) {
11727 grid.api.registerEventsFromObject(publicApi.events);
11728 //grid.api.registerMethodsFromObject(publicApi.methods);
11732 defaultGridOptions: function (gridOptions) {
11736 * @name ui.grid.edit.api:GridOptions
11738 * @description Options for configuring the edit feature, these are available to be
11739 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
11744 * @name enableCellEdit
11745 * @propertyOf ui.grid.edit.api:GridOptions
11746 * @description If defined, sets the default value for the editable flag on each individual colDefs
11747 * if their individual enableCellEdit configuration is not defined. Defaults to undefined.
11752 * @name cellEditableCondition
11753 * @propertyOf ui.grid.edit.api:GridOptions
11754 * @description If specified, either a value or function to be used by all columns before editing.
11755 * If falsy, then editing of cell is not allowed.
11758 * function($scope){
11759 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
11764 gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
11768 * @name editableCellTemplate
11769 * @propertyOf ui.grid.edit.api:GridOptions
11770 * @description If specified, cellTemplate to use as the editor for all columns.
11771 * <br/> defaults to 'ui-grid/cellTextEditor'
11776 * @name enableCellEditOnFocus
11777 * @propertyOf ui.grid.edit.api:GridOptions
11778 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
11779 * <br/>_requires cellNav feature and the edit feature to be enabled_
11781 //enableCellEditOnFocus can only be used if cellnav module is used
11782 gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ? false : gridOptions.enableCellEditOnFocus;
11787 * @name editColumnBuilder
11788 * @methodOf ui.grid.edit.service:uiGridEditService
11789 * @description columnBuilder function that adds edit properties to grid column
11790 * @returns {promise} promise that will load any needed templates when resolved
11792 editColumnBuilder: function (colDef, col, gridOptions) {
11798 * @name ui.grid.edit.api:ColumnDef
11800 * @description Column Definition for edit feature, these are available to be
11801 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
11806 * @name enableCellEdit
11807 * @propertyOf ui.grid.edit.api:ColumnDef
11808 * @description enable editing on column
11810 colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
11811 (colDef.type !== 'object') : gridOptions.enableCellEdit) : colDef.enableCellEdit;
11815 * @name cellEditableCondition
11816 * @propertyOf ui.grid.edit.api:ColumnDef
11817 * @description If specified, either a value or function evaluated before editing cell. If falsy, then editing of cell is not allowed.
11820 * function($scope){
11821 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
11826 colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition : colDef.cellEditableCondition;
11830 * @name editableCellTemplate
11831 * @propertyOf ui.grid.edit.api:ColumnDef
11832 * @description cell template to be used when editing this column. Can be Url or text template
11833 * <br/>Defaults to gridOptions.editableCellTemplate
11835 if (colDef.enableCellEdit) {
11836 colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
11838 promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
11840 function (template) {
11841 col.editableCellTemplate = template;
11844 // Todo handle response error here?
11845 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
11851 * @name enableCellEditOnFocus
11852 * @propertyOf ui.grid.edit.api:ColumnDef
11853 * @requires ui.grid.cellNav
11854 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
11855 * <br>_requires both the cellNav feature and the edit feature to be enabled_
11857 //enableCellEditOnFocus can only be used if cellnav module is used
11858 colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
11860 return $q.all(promises);
11865 * @name isStartEditKey
11866 * @methodOf ui.grid.edit.service:uiGridEditService
11867 * @description Determines if a keypress should start editing. Decorate this service to override with your
11868 * own key events. See service decorator in angular docs.
11869 * @param {Event} evt keydown event
11870 * @returns {boolean} true if an edit should start
11872 isStartEditKey: function (evt) {
11873 if (evt.keyCode === uiGridConstants.keymap.LEFT ||
11874 (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
11876 evt.keyCode === uiGridConstants.keymap.RIGHT ||
11877 evt.keyCode === uiGridConstants.keymap.TAB ||
11879 evt.keyCode === uiGridConstants.keymap.UP ||
11880 (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
11882 evt.keyCode === uiGridConstants.keymap.DOWN ||
11883 evt.keyCode === uiGridConstants.keymap.ENTER) {
11899 * @name ui.grid.edit.directive:uiGridEdit
11903 * @description Adds editing features to the ui-grid directive.
11906 <example module="app">
11907 <file name="app.js">
11908 var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
11910 app.controller('MainCtrl', ['$scope', function ($scope) {
11912 { name: 'Bob', title: 'CEO' },
11913 { name: 'Frank', title: 'Lowly Developer' }
11916 $scope.columnDefs = [
11917 {name: 'name', enableCellEdit: true},
11918 {name: 'title', enableCellEdit: true}
11922 <file name="index.html">
11923 <div ng-controller="MainCtrl">
11924 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
11929 module.directive('uiGridEdit', ['gridUtil', 'uiGridEditService', function (gridUtil, uiGridEditService) {
11933 require: '^uiGrid',
11935 compile: function () {
11937 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11938 uiGridEditService.initializeGrid(uiGridCtrl.grid);
11940 post: function ($scope, $elm, $attrs, uiGridCtrl) {
11949 * @name ui.grid.edit.directive:uiGridCell
11953 * @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
11956 * Binds edit start events to the uiGridCell element. When the events fire, the gridCell element is appended
11957 * with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
11959 * The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
11960 * and do the initial steps needed to edit the cell (setfocus on input element, etc).
11962 * When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
11963 * it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
11965 * If editableCellTemplate recognizes that the editing has been cancelled (esc key)
11966 * it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event. The original value
11967 * will be set back on the model by the uiGridCell directive.
11969 * Events that invoke editing:
11971 * - F2 keydown (when using cell selection)
11973 * Events that end editing:
11974 * - Dependent on the specific editableCellTemplate
11975 * - Standards should be blur and enter keydown
11977 * Events that cancel editing:
11978 * - Dependent on the specific editableCellTemplate
11979 * - Standards should be Esc keydown
11981 * Grid Events that end editing:
11982 * - uiGridConstants.events.GRID_SCROLL
11985 module.directive('uiGridCell',
11986 ['$compile', '$injector', 'uiGridConstants', 'uiGridEditConstants', 'gridUtil', '$parse', 'uiGridEditService',
11987 function ($compile, $injector, uiGridConstants, uiGridEditConstants, gridUtil, $parse, uiGridEditService) {
11989 priority: -100, // run after default uiGridCell directive
11992 require: '?^uiGrid',
11993 link: function ($scope, $elm, $attrs, uiGridCtrl) {
11994 if (!$scope.col.colDef.enableCellEdit) {
12000 var inEdit = false;
12001 var isFocusedBeforeEdit = false;
12004 registerBeginEditEvents();
12006 function registerBeginEditEvents() {
12007 $elm.on('dblclick', beginEdit);
12008 $elm.on('keydown', beginEditKeyDown);
12009 if ($scope.col.colDef.enableCellEditOnFocus) {
12010 $elm.find('div').on('focus', beginEditFocus);
12014 function cancelBeginEditEvents() {
12015 $elm.off('dblclick', beginEdit);
12016 $elm.off('keydown', beginEditKeyDown);
12017 if ($scope.col.colDef.enableCellEditOnFocus) {
12018 $elm.find('div').off('focus', beginEditFocus);
12022 function beginEditFocus(evt) {
12023 // gridUtil.logDebug('begin edit');
12024 if (uiGridCtrl && uiGridCtrl.cellNav) {
12025 // NOTE(c0bra): This is causing a loop where focusCell causes beginEditFocus to be called....
12026 uiGridCtrl.cellNav.focusCell($scope.row, $scope.col);
12029 evt.stopPropagation();
12033 // If the cellNagv module is installed and we can get the uiGridCellNavConstants value injected,
12034 // then if the column has enableCellEditOnFocus set to true, we need to listen for cellNav events
12035 // to this cell and start editing when the "focus" reaches us
12037 var uiGridCellNavConstants = $injector.get('uiGridCellNavConstants');
12039 if ($scope.col.colDef.enableCellEditOnFocus) {
12040 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol) {
12041 if (rowCol.row === $scope.row && rowCol.col === $scope.col) {
12052 function beginEditKeyDown(evt) {
12053 if (uiGridEditService.isStartEditKey(evt)) {
12058 function shouldEdit(col, row) {
12059 return !row.isSaving &&
12060 ( angular.isFunction(col.colDef.cellEditableCondition) ?
12061 col.colDef.cellEditableCondition($scope) :
12062 col.colDef.cellEditableCondition );
12068 * @name editDropdownOptionsArray
12069 * @propertyOf ui.grid.edit.api:ColumnDef
12070 * @description an array of values in the format
12071 * [ {id: xxx, value: xxx} ], which is populated
12072 * into the edit dropdown
12077 * @name editDropdownIdLabel
12078 * @propertyOf ui.grid.edit.api:ColumnDef
12079 * @description the label for the "id" field
12080 * in the editDropdownOptionsArray. Defaults
12084 * $scope.gridOptions = {
12086 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
12087 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
12088 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
12095 * @name editDropdownValueLabel
12096 * @propertyOf ui.grid.edit.api:ColumnDef
12097 * @description the label for the "value" field
12098 * in the editDropdownOptionsArray. Defaults
12102 * $scope.gridOptions = {
12104 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
12105 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
12106 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
12113 * @name editDropdownFilter
12114 * @propertyOf ui.grid.edit.api:ColumnDef
12115 * @description A filter that you would like to apply to the values in the options list
12116 * of the dropdown. For example if you were using angular-translate you might set this
12120 * $scope.gridOptions = {
12122 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
12123 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
12124 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
12129 function beginEdit() {
12130 // If we are already editing, then just skip this so we don't try editing twice...
12135 if (!shouldEdit($scope.col, $scope.row)) {
12139 cellModel = $parse($scope.row.getQualifiedColField($scope.col));
12140 //get original value from the cell
12141 origCellValue = cellModel($scope);
12143 html = $scope.col.editableCellTemplate;
12144 html = html.replace(uiGridConstants.MODEL_COL_FIELD, $scope.row.getQualifiedColField($scope.col));
12146 var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
12147 html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
12149 $scope.inputType = 'text';
12150 switch ($scope.col.colDef.type){
12152 $scope.inputType = 'checkbox';
12155 $scope.inputType = 'number';
12158 $scope.inputType = 'date';
12162 $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
12163 $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
12164 $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
12167 $scope.$apply(function () {
12169 cancelBeginEditEvents();
12170 var cellElement = angular.element(html);
12171 $elm.append(cellElement);
12172 $compile(cellElement)($scope.$new());
12173 var gridCellContentsEl = angular.element($elm.children()[0]);
12174 isFocusedBeforeEdit = gridCellContentsEl.hasClass('ui-grid-cell-focus');
12175 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
12178 //stop editing when grid is scrolled
12179 var deregOnGridScroll = $scope.$on(uiGridConstants.events.GRID_SCROLL, function () {
12181 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
12182 deregOnGridScroll();
12186 var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function (evt, retainFocus) {
12187 endEdit(retainFocus);
12188 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
12189 deregOnEndCellEdit();
12193 var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
12195 deregOnCancelCellEdit();
12198 $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT);
12199 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef);
12202 function endEdit(retainFocus) {
12206 var gridCellContentsEl = angular.element($elm.children()[0]);
12207 //remove edit element
12208 angular.element($elm.children()[1]).remove();
12209 gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
12210 if (retainFocus && isFocusedBeforeEdit) {
12211 gridCellContentsEl[0].focus();
12213 isFocusedBeforeEdit = false;
12215 registerBeginEditEvents();
12216 $scope.grid.api.core.notifyDataChange( $scope.grid, uiGridConstants.dataChange.EDIT );
12219 function cancelEdit() {
12223 cellModel.assign($scope, origCellValue);
12226 $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
12236 * @name ui.grid.edit.directive:uiGridEditor
12240 * @description input editor directive for editable fields.
12241 * Provides EndEdit and CancelEdit events
12243 * Events that end editing:
12244 * blur and enter keydown
12246 * Events that cancel editing:
12250 module.directive('uiGridEditor',
12251 ['uiGridConstants', 'uiGridEditConstants',
12252 function (uiGridConstants, uiGridEditConstants) {
12255 require: ['?^uiGrid', '?^uiGridRenderContainer'],
12256 compile: function () {
12258 pre: function ($scope, $elm, $attrs) {
12261 post: function ($scope, $elm, $attrs, controllers) {
12262 var uiGridCtrl, renderContainerCtrl;
12263 if (controllers[0]) { uiGridCtrl = controllers[0]; }
12264 if (controllers[1]) { renderContainerCtrl = controllers[1]; }
12266 //set focus at start of edit
12267 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
12270 $elm.on('blur', function (evt) {
12271 $scope.stopEdit(evt);
12276 $scope.deepEdit = false;
12278 $scope.stopEdit = function (evt) {
12279 if ($scope.inputForm && !$scope.inputForm.$valid) {
12280 evt.stopPropagation();
12281 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
12284 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
12286 $scope.deepEdit = false;
12289 $elm.on('click', function (evt) {
12290 $scope.deepEdit = true;
12293 $elm.on('keydown', function (evt) {
12294 switch (evt.keyCode) {
12295 case uiGridConstants.keymap.ESC:
12296 evt.stopPropagation();
12297 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
12299 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
12300 $scope.stopEdit(evt);
12302 case uiGridConstants.keymap.TAB:
12303 $scope.stopEdit(evt);
12307 if ($scope.deepEdit) {
12308 switch (evt.keyCode) {
12309 case uiGridConstants.keymap.LEFT:
12310 evt.stopPropagation();
12312 case uiGridConstants.keymap.RIGHT:
12313 evt.stopPropagation();
12315 case uiGridConstants.keymap.UP:
12316 evt.stopPropagation();
12318 case uiGridConstants.keymap.DOWN:
12319 evt.stopPropagation();
12323 // Pass the keydown event off to the cellNav service, if it exists
12324 else if (uiGridCtrl && uiGridCtrl.hasOwnProperty('cellNav') && renderContainerCtrl) {
12325 evt.uiGridTargetRenderContainerId = renderContainerCtrl.containerId;
12326 uiGridCtrl.cellNav.handleKeyDown(evt);
12339 * @name ui.grid.edit.directive:input
12343 * @description directive to provide binding between input[date] value and ng-model for angular 1.2
12344 * It is similar to input[date] directive of angular 1.3
12346 * Supported date format for input is 'yyyy-MM-dd'
12347 * The directive will set the $valid property of input element and the enclosing form to false if
12348 * model is invalid date or value of input is entered wrong.
12351 module.directive('input', ['$filter', function ($filter) {
12352 function parseDateString(dateString) {
12353 if (typeof(dateString) === 'undefined' || dateString === '') {
12356 var parts = dateString.split('-');
12357 if (parts.length !== 3) {
12360 var year = parseInt(parts[0], 10);
12361 var month = parseInt(parts[1], 10);
12362 var day = parseInt(parts[2], 10);
12364 if (month < 1 || year < 1 || day < 1) {
12367 return new Date(year, (month - 1), day);
12371 require: '?ngModel',
12372 link: function (scope, element, attrs, ngModel) {
12374 if (angular.version.minor === 2 && attrs.type && attrs.type === 'date' && ngModel) {
12376 ngModel.$formatters.push(function (modelValue) {
12377 ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
12378 return $filter('date')(modelValue, 'yyyy-MM-dd');
12381 ngModel.$parsers.push(function (viewValue) {
12382 if (viewValue && viewValue.length > 0) {
12383 var dateValue = parseDateString(viewValue);
12384 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
12388 ngModel.$setValidity(null, true);
12400 * @name ui.grid.edit.directive:uiGridEditDropdown
12404 * @description dropdown editor for editable fields.
12405 * Provides EndEdit and CancelEdit events
12407 * Events that end editing:
12408 * blur and enter keydown, and any left/right nav
12410 * Events that cancel editing:
12414 module.directive('uiGridEditDropdown',
12415 ['uiGridConstants', 'uiGridEditConstants',
12416 function (uiGridConstants, uiGridEditConstants) {
12419 compile: function () {
12421 pre: function ($scope, $elm, $attrs) {
12424 post: function ($scope, $elm, $attrs) {
12426 //set focus at start of edit
12427 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
12429 $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
12430 $elm.on('blur', function (evt) {
12431 $scope.stopEdit(evt);
12436 $scope.stopEdit = function (evt) {
12437 // no need to validate a dropdown - invalid values shouldn't be
12438 // available in the list
12439 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
12442 $elm.on('keydown', function (evt) {
12443 switch (evt.keyCode) {
12444 case uiGridConstants.keymap.ESC:
12445 evt.stopPropagation();
12446 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
12448 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
12449 $scope.stopEdit(evt);
12451 case uiGridConstants.keymap.LEFT:
12452 $scope.stopEdit(evt);
12454 case uiGridConstants.keymap.RIGHT:
12455 $scope.stopEdit(evt);
12457 case uiGridConstants.keymap.UP:
12458 evt.stopPropagation();
12460 case uiGridConstants.keymap.DOWN:
12461 evt.stopPropagation();
12463 case uiGridConstants.keymap.TAB:
12464 $scope.stopEdit(evt);
12482 * @name ui.grid.expandable
12485 * # ui.grid.expandable
12486 * This module provides the ability to create subgrids with the ability to expand a row
12487 * to show the subgrid.
12489 * <div doc-module-components="ui.grid.expandable"></div>
12491 var module = angular.module('ui.grid.expandable', ['ui.grid']);
12495 * @name ui.grid.edit.service:uiGridExpandableService
12497 * @description Services for the expandable grid
12499 module.service('uiGridExpandableService', ['gridUtil', '$compile', function (gridUtil, $compile) {
12501 initializeGrid: function (grid) {
12505 * @name enableExpandable
12506 * @propertyOf ui.grid.expandable.api:GridOptions
12507 * @description Whether or not to use expandable feature, allows you to turn off expandable on specific grids
12508 * within your application, or in specific modes on _this_ grid. Defaults to true.
12511 * $scope.gridOptions = {
12512 * enableExpandable: false
12516 grid.options.enableExpandable = grid.options.enableExpandable !== false;
12520 * @name expandableRowHeight
12521 * @propertyOf ui.grid.expandable.api:GridOptions
12522 * @description Height in pixels of the expanded subgrid. Defaults to
12526 * $scope.gridOptions = {
12527 * expandableRowHeight: 150
12531 grid.options.expandableRowHeight = grid.options.expandableRowHeight || 150;
12535 * @name expandableRowTemplate
12536 * @propertyOf ui.grid.expandable.api:GridOptions
12537 * @description Mandatory. The template for your expanded row
12540 * $scope.gridOptions = {
12541 * expandableRowTemplate: 'expandableRowTemplate.html'
12545 if ( grid.options.enableExpandable && !grid.options.expandableRowTemplate ){
12546 gridUtil.logError( 'You have not set the expandableRowTemplate, disabling expandable module' );
12547 grid.options.enableExpandable = false;
12552 * @name ui.grid.expandable.api:PublicApi
12554 * @description Public Api for expandable feature
12558 * @name ui.grid.expandable.api:GridOptions
12560 * @description Options for configuring the expandable feature, these are available to be
12561 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
12569 * @name rowExpandedStateChanged
12570 * @eventOf ui.grid.expandable.api:PublicApi
12571 * @description raised when cell editing is complete
12573 * gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){})
12575 * @param {GridRow} row the row that was expanded
12577 rowExpandedStateChanged: function (scope, row) {
12586 * @name toggleRowExpansion
12587 * @methodOf ui.grid.expandable.api:PublicApi
12588 * @description Toggle a specific row
12590 * gridApi.expandable.toggleRowExpansion(rowEntity);
12592 * @param {object} rowEntity the data entity for the row you want to expand
12594 toggleRowExpansion: function (rowEntity) {
12595 var row = grid.getRow(rowEntity);
12596 if (row !== null) {
12597 service.toggleRowExpansion(grid, row);
12603 * @name expandAllRows
12604 * @methodOf ui.grid.expandable.api:PublicApi
12605 * @description Expand all subgrids.
12607 * gridApi.expandable.expandAllRows();
12610 expandAllRows: function() {
12611 service.expandAllRows(grid);
12616 * @name collapseAllRows
12617 * @methodOf ui.grid.expandable.api:PublicApi
12618 * @description Collapse all subgrids.
12620 * gridApi.expandable.collapseAllRows();
12623 collapseAllRows: function() {
12624 service.collapseAllRows(grid);
12629 grid.api.registerEventsFromObject(publicApi.events);
12630 grid.api.registerMethodsFromObject(publicApi.methods);
12633 toggleRowExpansion: function (grid, row) {
12634 row.isExpanded = !row.isExpanded;
12636 if (row.isExpanded) {
12637 row.height = row.grid.options.rowHeight + grid.options.expandableRowHeight;
12640 row.height = row.grid.options.rowHeight;
12643 grid.api.expandable.raise.rowExpandedStateChanged(row);
12646 expandAllRows: function(grid, $scope) {
12647 angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
12648 if (!row.isExpanded) {
12649 service.toggleRowExpansion(grid, row);
12655 collapseAllRows: function(grid) {
12656 angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
12657 if (row.isExpanded) {
12658 service.toggleRowExpansion(grid, row);
12669 * @name enableExpandableRowHeader
12670 * @propertyOf ui.grid.expandable.api:GridOptions
12671 * @description Show a rowHeader to provide the expandable buttons. If set to false then implies
12672 * you're going to use a custom method for expanding and collapsing the subgrids. Defaults to true.
12675 * $scope.gridOptions = {
12676 * enableExpandableRowHeader: false
12680 module.directive('uiGridExpandable', ['uiGridExpandableService', '$templateCache',
12681 function (uiGridExpandableService, $templateCache) {
12685 require: '^uiGrid',
12687 compile: function () {
12689 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
12690 if ( uiGridCtrl.grid.options.enableExpandableRowHeader !== false ) {
12691 var expandableRowHeaderColDef = {name: 'expandableButtons', width: 40};
12692 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
12693 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
12695 uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
12697 post: function ($scope, $elm, $attrs, uiGridCtrl) {
12704 module.directive('uiGridExpandableRow',
12705 ['uiGridExpandableService', '$timeout', '$compile', 'uiGridConstants','gridUtil','$interval', '$log',
12706 function (uiGridExpandableService, $timeout, $compile, uiGridConstants, gridUtil, $interval, $log) {
12713 compile: function () {
12715 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
12716 gridUtil.getTemplate($scope.grid.options.expandableRowTemplate).then(
12717 function (template) {
12718 if ($scope.grid.options.expandableRowScope) {
12719 var expandableRowScope = $scope.grid.options.expandableRowScope;
12720 for (var property in expandableRowScope) {
12721 if (expandableRowScope.hasOwnProperty(property)) {
12722 $scope[property] = expandableRowScope[property];
12726 var expandedRowElement = $compile(template)($scope);
12727 $elm.append(expandedRowElement);
12728 $scope.row.expandedRendered = true;
12732 post: function ($scope, $elm, $attrs, uiGridCtrl) {
12733 $scope.$on('$destroy', function() {
12734 $scope.row.expandedRendered = false;
12742 module.directive('uiGridRow',
12743 ['$compile', 'gridUtil', '$templateCache',
12744 function ($compile, gridUtil, $templateCache) {
12748 compile: function ($elm, $attrs) {
12750 pre: function ($scope, $elm, $attrs, controllers) {
12752 $scope.expandableRow = {};
12754 $scope.expandableRow.shouldRenderExpand = function () {
12755 var ret = $scope.colContainer.name === 'body' && $scope.grid.options.enableExpandable !== false && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
12759 $scope.expandableRow.shouldRenderFiller = function () {
12760 var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
12764 function updateRowContainerWidth() {
12765 var grid = $scope.grid;
12767 angular.forEach(grid.columns, function (column) {
12768 if (column.renderContainer === 'left') {
12769 colWidth += column.width;
12772 colWidth = Math.floor(colWidth);
12773 return '.grid' + grid.id + ' .ui-grid-pinned-container-' + $scope.colContainer.name + ', .grid' + grid.id +
12774 ' .ui-grid-pinned-container-' + $scope.colContainer.name + ' .ui-grid-render-container-' + $scope.colContainer.name +
12775 ' .ui-grid-viewport .ui-grid-canvas .ui-grid-row { width: ' + colWidth + 'px; }';
12778 if ($scope.colContainer.name === 'left') {
12779 $scope.grid.registerStyleComputation({
12781 func: updateRowContainerWidth
12786 post: function ($scope, $elm, $attrs, controllers) {
12793 module.directive('uiGridViewport',
12794 ['$compile', 'gridUtil', '$templateCache',
12795 function ($compile, gridUtil, $templateCache) {
12799 compile: function ($elm, $attrs) {
12800 var rowRepeatDiv = angular.element($elm.children().children()[0]);
12801 var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
12802 var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
12803 rowRepeatDiv.append(expandedRowElement);
12804 rowRepeatDiv.append(expandedRowFillerElement);
12806 pre: function ($scope, $elm, $attrs, controllers) {
12808 post: function ($scope, $elm, $attrs, controllers) {
12817 /* global console */
12824 * @name ui.grid.exporter
12827 * # ui.grid.exporter
12828 * This module provides the ability to exporter data from the grid.
12830 * Data can be exported in a range of formats, and all data, visible
12831 * data, or selected rows can be exported, with all columns or visible
12834 * No UI is provided, the caller should provide their own UI/buttons
12835 * as appropriate, or enable the gridMenu
12840 * <div doc-module-components="ui.grid.exporter"></div>
12843 var module = angular.module('ui.grid.exporter', ['ui.grid']);
12847 * @name ui.grid.exporter.constant:uiGridExporterConstants
12849 * @description constants available in exporter module
12853 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
12855 * @description export all data, including data not visible. Can
12856 * be set for either rowTypes or colTypes
12860 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
12862 * @description export only visible data, including data not visible. Can
12863 * be set for either rowTypes or colTypes
12867 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
12869 * @description export all data, including data not visible. Can
12870 * be set only for rowTypes, selection of only some columns is
12873 module.constant('uiGridExporterConstants', {
12874 featureName: 'exporter',
12876 VISIBLE: 'visible',
12877 SELECTED: 'selected',
12878 CSV_CONTENT: 'CSV_CONTENT',
12879 LINK_LABEL: 'LINK_LABEL',
12880 BUTTON_LABEL: 'BUTTON_LABEL'
12885 * @name ui.grid.exporter.service:uiGridExporterService
12887 * @description Services for exporter feature
12889 module.service('uiGridExporterService', ['$q', 'uiGridExporterConstants', 'uiGridSelectionConstants', 'gridUtil', '$compile', '$interval', 'i18nService',
12890 function ($q, uiGridExporterConstants, uiGridSelectionConstants, gridUtil, $compile, $interval, i18nService) {
12894 initializeGrid: function (grid) {
12896 //add feature namespace and any properties to grid for needed state
12897 grid.exporter = {};
12898 this.defaultGridOptions(grid.options);
12902 * @name ui.grid.exporter.api:PublicApi
12904 * @description Public Api for exporter feature
12916 * @methodOf ui.grid.exporter.api:PublicApi
12917 * @description Exports rows from the grid in csv format,
12918 * the data exported is selected based on the provided options
12919 * @param {string} rowTypes which rows to export, valid values are
12920 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
12921 * uiGridExporterConstants.SELECTED
12922 * @param {string} colTypes which columns to export, valid values are
12923 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
12924 * @param {element} $elm (Optional) A UI element into which the
12925 * resulting download link will be placed.
12927 csvExport: function (rowTypes, colTypes, $elm) {
12928 service.csvExport(grid, rowTypes, colTypes, $elm);
12933 * @methodOf ui.grid.exporter.api:PublicApi
12934 * @description Exports rows from the grid in pdf format,
12935 * the data exported is selected based on the provided options
12936 * Note that this function has a dependency on pdfMake, all
12937 * going well this has been installed for you.
12938 * The resulting pdf opens in a new browser window.
12939 * @param {string} rowTypes which rows to export, valid values are
12940 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
12941 * uiGridExporterConstants.SELECTED
12942 * @param {string} colTypes which columns to export, valid values are
12943 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
12945 pdfExport: function (rowTypes, colTypes) {
12946 service.pdfExport(grid, rowTypes, colTypes);
12952 grid.api.registerEventsFromObject(publicApi.events);
12954 grid.api.registerMethodsFromObject(publicApi.methods);
12956 if (grid.api.core.addToGridMenu){
12957 service.addToMenu( grid );
12959 // order of registration is not guaranteed, register in a little while
12960 $interval( function() {
12961 if (grid.api.core.addToGridMenu){
12962 service.addToMenu( grid );
12969 defaultGridOptions: function (gridOptions) {
12970 //default option to true unless it was explicitly set to false
12973 * @name ui.grid.exporter.api:GridOptions
12975 * @description GridOptions for selection feature, these are available to be
12976 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
12980 * @name ui.grid.exporter.api:GridOptions.columnDef
12981 * @description ColumnDef settings for exporter
12985 * @name exporterSuppressMenu
12986 * @propertyOf ui.grid.exporter.api:GridOptions
12987 * @description Don't show the export menu button, implying the user
12988 * will roll their own UI for calling the exporter
12989 * <br/>Defaults to false
12991 gridOptions.exporterSuppressMenu = gridOptions.exporterSuppressMenu === true;
12994 * @name exporterLinkTemplate
12995 * @propertyOf ui.grid.exporter.api:GridOptions
12996 * @description A custom template to use for the resulting
12997 * link (for csv export)
12998 * <br/>Defaults to ui-grid/csvLink
13000 gridOptions.exporterLinkTemplate = gridOptions.exporterLinkTemplate ? gridOptions.exporterLinkTemplate : 'ui-grid/csvLink';
13003 * @name exporterHeaderTemplate
13004 * @propertyOf ui.grid.exporter.api:GridOptions
13005 * @description A custom template to use for the header
13006 * section, containing the button and csv download link. Not
13007 * needed if you've set suppressButton and are providing a custom
13008 * $elm into which the download link will go.
13009 * <br/>Defaults to ui-grid/exporterHeader
13011 gridOptions.exporterHeaderTemplate = gridOptions.exporterHeaderTemplate ? gridOptions.exporterHeaderTemplate : 'ui-grid/exporterHeader';
13014 * @name exporterLinkLabel
13015 * @propertyOf ui.grid.exporter.api:GridOptions
13016 * @description The text to show on the CSV download
13018 * <br/>Defaults to 'Download CSV'
13020 gridOptions.exporterLinkLabel = gridOptions.exporterLinkLabel ? gridOptions.exporterLinkLabel : 'Download CSV';
13023 * @name exporterMenuLabel
13024 * @propertyOf ui.grid.exporter.api:GridOptions
13025 * @description The text to show on the exporter menu button
13027 * <br/>Defaults to 'Export'
13029 gridOptions.exporterMenuLabel = gridOptions.exporterMenuLabel ? gridOptions.exporterMenuLabel : 'Export';
13032 * @name exporterSuppressColumns
13033 * @propertyOf ui.grid.exporter.api:GridOptions
13034 * @description Columns that should not be exported. The selectionRowHeader is already automatically
13035 * suppressed, but if you had a button column or some other "system" column that shouldn't be shown in the
13036 * output then add it in this list. You should provide an array of column names.
13037 * <br/>Defaults to: []
13039 * gridOptions.exporterSuppressColumns = [ 'buttons' ];
13042 gridOptions.exporterSuppressColumns = gridOptions.exporterSuppressColumns ? gridOptions.exporterSuppressColumns : [];
13045 * @name exporterCsvColumnSeparator
13046 * @propertyOf ui.grid.exporter.api:GridOptions
13047 * @description The character to use as column separator
13049 * <br/>Defaults to ','
13051 gridOptions.exporterCsvColumnSeparator = gridOptions.exporterCsvColumnSeparator ? gridOptions.exporterCsvColumnSeparator : ',';
13054 * @name exporterPdfDefaultStyle
13055 * @propertyOf ui.grid.exporter.api:GridOptions
13056 * @description The default style in pdfMake format
13057 * <br/>Defaults to:
13064 gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
13067 * @name exporterPdfTableStyle
13068 * @propertyOf ui.grid.exporter.api:GridOptions
13069 * @description The table style in pdfMake format
13070 * <br/>Defaults to:
13073 * margin: [0, 5, 0, 15]
13077 gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
13080 * @name exporterPdfTableHeaderStyle
13081 * @propertyOf ui.grid.exporter.api:GridOptions
13082 * @description The tableHeader style in pdfMake format
13083 * <br/>Defaults to:
13092 gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
13095 * @name exporterPdfHeader
13096 * @propertyOf ui.grid.exporter.api:GridOptions
13097 * @description The header section for pdf exports. Can be
13100 * gridOptions.exporterPdfHeader = 'My Header';
13102 * Can be a more complex object in pdfMake format:
13104 * gridOptions.exporterPdfHeader = {
13107 * { text: 'Right part', alignment: 'right' }
13111 * Or can be a function, allowing page numbers and the like
13113 * gridOptions.exporterPdfHeader: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
13116 gridOptions.exporterPdfHeader = gridOptions.exporterPdfHeader ? gridOptions.exporterPdfHeader : null;
13119 * @name exporterPdfFooter
13120 * @propertyOf ui.grid.exporter.api:GridOptions
13121 * @description The header section for pdf exports. Can be
13124 * gridOptions.exporterPdfFooter = 'My Footer';
13126 * Can be a more complex object in pdfMake format:
13128 * gridOptions.exporterPdfFooter = {
13131 * { text: 'Right part', alignment: 'right' }
13135 * Or can be a function, allowing page numbers and the like
13137 * gridOptions.exporterPdfFooter: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; };
13140 gridOptions.exporterPdfFooter = gridOptions.exporterPdfFooter ? gridOptions.exporterPdfFooter : null;
13143 * @name exporterPdfOrientation
13144 * @propertyOf ui.grid.exporter.api:GridOptions
13145 * @description The orientation, should be a valid pdfMake value,
13146 * 'landscape' or 'portrait'
13147 * <br/>Defaults to landscape
13149 gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
13152 * @name exporterPdfPageSize
13153 * @propertyOf ui.grid.exporter.api:GridOptions
13154 * @description The orientation, should be a valid pdfMake
13155 * paper size, usually 'A4' or 'LETTER'
13156 * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
13157 * <br/>Defaults to A4
13159 gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
13162 * @name exporterPdfMaxGridWidth
13163 * @propertyOf ui.grid.exporter.api:GridOptions
13164 * @description The maxium grid width - the current grid width
13165 * will be scaled to match this, with any fixed width columns
13166 * being adjusted accordingly.
13167 * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER
13169 gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
13172 * @name exporterPdfTableLayout
13173 * @propertyOf ui.grid.exporter.api:GridOptions
13174 * @description A tableLayout in pdfMake format,
13175 * controls gridlines and the like. We use the default
13177 * <br/>Defaults to null, which means no layout
13182 * @name exporterMenuCsv
13183 * @propertyOf ui.grid.exporter.api:GridOptions
13184 * @description Add csv export menu items to the ui-grid grid menu, if it's present. Defaults to true.
13186 gridOptions.exporterMenuCsv = gridOptions.exporterMenuCsv !== undefined ? gridOptions.exporterMenuCsv : true;
13190 * @name exporterMenuPdf
13191 * @propertyOf ui.grid.exporter.api:GridOptions
13192 * @description Add pdf export menu items to the ui-grid grid menu, if it's present. Defaults to true.
13194 gridOptions.exporterMenuPdf = gridOptions.exporterMenuPdf !== undefined ? gridOptions.exporterMenuPdf : true;
13198 * @name exporterPdfCustomFormatter
13199 * @propertyOf ui.grid.exporter.api:GridOptions
13200 * @description A custom callback routine that changes the pdf document, adding any
13201 * custom styling or content that is supported by pdfMake. Takes in the complete docDefinition, and
13202 * must return an updated docDefinition ready for pdfMake.
13204 * In this example we add a style to the style array, so that we can use it in our
13205 * footer definition.
13207 * gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
13208 * docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
13209 * return docDefinition;
13212 * gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
13215 gridOptions.exporterPdfCustomFormatter = ( gridOptions.exporterPdfCustomFormatter && typeof( gridOptions.exporterPdfCustomFormatter ) === 'function' ) ? gridOptions.exporterPdfCustomFormatter : function ( docDef ) { return docDef; };
13219 * @name exporterHeaderFilter
13220 * @propertyOf ui.grid.exporter.api:GridOptions
13221 * @description A function to apply to the header displayNames before exporting. Useful for internationalisation,
13222 * for example if you were using angular-translate you'd set this to `$translate.instant`. Note that this
13223 * call must be synchronous, it cannot be a call that returns a promise.
13226 * gridOptions.exporterHeaderFilter = function( displayName ){ return 'col: ' + displayName; };
13230 * gridOptions.exporterHeaderFilter = $translate.instant;
13236 * @name exporterFieldCallback
13237 * @propertyOf ui.grid.exporter.api:GridOptions
13238 * @description A function to call for each field before exporting it. Allows
13239 * massaging of raw data into a display format, for example if you have applied
13240 * filters to convert codes into decodes, or you require
13241 * a specific date format in the exported content.
13243 * The method is called once for each field exported, and provides the grid, the
13244 * gridCol and the GridRow for you to use as context in massaging the data.
13246 * Note that the format of the passed in value is along the lines of:
13248 * { value: 'cellValue', alignment: 'left' }
13251 * Your returned value needs to follow that format.
13253 * @param {Grid} grid provides the grid in case you have need of it
13254 * @param {GridRow} row the row from which the data comes
13255 * @param {GridCol} col the column from which the data comes
13256 * @param {object} value the value for your massaging
13257 * @returns {object} you must return the massaged value ready for exporting
13261 * gridOptions.exporterFieldCallback = function ( grid, row, col, value ){
13262 * if ( col.name === 'status' ){
13263 * value = { value: decodeStatus( value ) };
13269 gridOptions.exporterFieldCallback = gridOptions.exporterFieldCallback ? gridOptions.exporterFieldCallback : function( grid, row, col, value ) { return value; };
13276 * @methodOf ui.grid.exporter.service:uiGridExporterService
13277 * @description Adds export items to the grid menu,
13278 * allowing the user to select export options
13279 * @param {Grid} grid the grid from which data should be exported
13281 addToMenu: function ( grid ) {
13282 grid.api.core.addToGridMenu( grid, [
13284 title: i18nService.getSafeText('gridMenu.exporterAllAsCsv'),
13285 action: function ($event) {
13286 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
13288 shown: function() {
13289 return this.grid.options.exporterMenuCsv;
13293 title: i18nService.getSafeText('gridMenu.exporterVisibleAsCsv'),
13294 action: function ($event) {
13295 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
13297 shown: function() {
13298 return this.grid.options.exporterMenuCsv;
13302 title: i18nService.getSafeText('gridMenu.exporterSelectedAsCsv'),
13303 action: function ($event) {
13304 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
13306 shown: function() {
13307 return this.grid.options.exporterMenuCsv &&
13308 ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
13312 title: i18nService.getSafeText('gridMenu.exporterAllAsPdf'),
13313 action: function ($event) {
13314 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
13316 shown: function() {
13317 return this.grid.options.exporterMenuPdf;
13321 title: i18nService.getSafeText('gridMenu.exporterVisibleAsPdf'),
13322 action: function ($event) {
13323 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
13325 shown: function() {
13326 return this.grid.options.exporterMenuPdf;
13330 title: i18nService.getSafeText('gridMenu.exporterSelectedAsPdf'),
13331 action: function ($event) {
13332 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
13334 shown: function() {
13335 return this.grid.options.exporterMenuPdf &&
13336 ( this.grid.api.selection && this.grid.api.selection.getSelectedRows().length > 0 );
13345 * @name exporterCsvLinkElement
13346 * @propertyOf ui.grid.exporter.api:GridOptions
13347 * @description The element that the csv link should be placed into.
13348 * Mandatory if using the native UI.
13353 * @methodOf ui.grid.exporter.service:uiGridExporterService
13354 * @description Exports rows from the grid in csv format,
13355 * the data exported is selected based on the provided options
13356 * @param {Grid} grid the grid from which data should be exported
13357 * @param {string} rowTypes which rows to export, valid values are
13358 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13359 * uiGridExporterConstants.SELECTED
13360 * @param {string} colTypes which columns to export, valid values are
13361 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13362 * uiGridExporterConstants.SELECTED
13363 * @param {element} $elm (Optional) A UI element into which the
13364 * resulting download link will be placed.
13366 csvExport: function (grid, rowTypes, colTypes, $elm) {
13367 var exportColumnHeaders = this.getColumnHeaders(grid, colTypes);
13368 var exportData = this.getData(grid, rowTypes, colTypes);
13369 var csvContent = this.formatAsCsv(exportColumnHeaders, exportData, grid.options.exporterCsvColumnSeparator);
13371 if ( !$elm && grid.options.exporterCsvLinkElement ){
13372 $elm = grid.options.exporterCsvLinkElement;
13376 this.renderCsvLink(grid, csvContent, $elm);
13378 gridUtil.logError( 'Exporter asked to export as csv, but no element provided. Perhaps you should set gridOptions.exporterCsvLinkElement?')
13385 * @name getColumnHeaders
13386 * @methodOf ui.grid.exporter.service:uiGridExporterService
13387 * @description Gets the column headers from the grid to use
13388 * as a title row for the exported file, all headers have
13389 * headerCellFilters applied as appropriate.
13391 * Column headers are an array of objects, each object has
13392 * name, displayName, width and align attributes. Only name is
13393 * used for csv, all attributes are used for pdf.
13395 * @param {Grid} grid the grid from which data should be exported
13396 * @param {string} colTypes which columns to export, valid values are
13397 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13398 * uiGridExporterConstants.SELECTED
13400 getColumnHeaders: function (grid, colTypes) {
13402 angular.forEach(grid.columns, function( gridCol, index ) {
13403 if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
13404 gridCol.name !== uiGridSelectionConstants.selectionRowHeaderColName &&
13405 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
13407 name: gridCol.field,
13408 displayName: grid.options.exporterHeaderFilter ? grid.options.exporterHeaderFilter(gridCol.displayName) : gridCol.displayName,
13409 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
13410 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
13421 * @propertyOf ui.grid.exporter.api:GridOptions.columnDef
13422 * @name exporterPdfAlign
13423 * @description the alignment you'd like for this specific column when
13424 * exported into a pdf. Can be 'left', 'right', 'center' or any other
13425 * valid pdfMake alignment option.
13430 * @methodOf ui.grid.exporter.service:uiGridExporterService
13431 * @description Gets data from the grid based on the provided options,
13432 * all cells have cellFilters applied as appropriate
13433 * @param {Grid} grid the grid from which data should be exported
13434 * @param {string} rowTypes which rows to export, valid values are
13435 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13436 * uiGridExporterConstants.SELECTED
13437 * @param {string} colTypes which columns to export, valid values are
13438 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13439 * uiGridExporterConstants.SELECTED
13441 getData: function (grid, rowTypes, colTypes) {
13446 switch ( rowTypes ) {
13447 case uiGridExporterConstants.ALL:
13450 case uiGridExporterConstants.VISIBLE:
13451 rows = grid.getVisibleRows();
13453 case uiGridExporterConstants.SELECTED:
13454 if ( grid.api.selection ){
13455 rows = grid.api.selection.getSelectedGridRows();
13457 gridUtil.logError('selection feature must be enabled to allow selected rows to be exported');
13462 angular.forEach(rows, function( row, index ) {
13464 var extractedRow = [];
13465 angular.forEach(grid.columns, function( gridCol, index ) {
13466 if ( (gridCol.visible || colTypes === uiGridExporterConstants.ALL ) &&
13467 gridCol.name !== uiGridSelectionConstants.selectionRowHeaderColName &&
13468 grid.options.exporterSuppressColumns.indexOf( gridCol.name ) === -1 ){
13469 var extractedField = { value: grid.options.exporterFieldCallback( grid, row, gridCol, grid.getCellValue( row, gridCol ) ) };
13470 if ( gridCol.colDef.exporterPdfAlign ) {
13471 extractedField.alignment = gridCol.colDef.exporterPdfAlign;
13473 extractedRow.push(extractedField);
13477 data.push(extractedRow);
13486 * @name formatAsCSV
13487 * @methodOf ui.grid.exporter.service:uiGridExporterService
13488 * @description Formats the column headers and data as a CSV,
13489 * and sends that data to the user
13490 * @param {array} exportColumnHeaders an array of column headers,
13491 * where each header is an object with name, width and maybe alignment
13492 * @param {array} exportData an array of rows, where each row is
13493 * an array of column data
13494 * @returns {string} csv the formatted csv as a string
13496 formatAsCsv: function (exportColumnHeaders, exportData, separator) {
13499 var bareHeaders = exportColumnHeaders.map(function(header){return { value: header.displayName };});
13501 var csv = self.formatRowAsCsv(this, separator)(bareHeaders) + '\n';
13503 csv += exportData.map(this.formatRowAsCsv(this, separator)).join('\n');
13510 * @name formatRowAsCsv
13511 * @methodOf ui.grid.exporter.service:uiGridExporterService
13512 * @description Renders a single field as a csv field, including
13513 * quotes around the value
13514 * @param {exporterService} exporter pass in exporter
13515 * @param {array} row the row to be turned into a csv string
13516 * @returns {string} a csv-ified version of the row
13518 formatRowAsCsv: function (exporter, separator) {
13519 return function (row) {
13520 return row.map(exporter.formatFieldAsCsv).join(separator);
13526 * @name formatFieldAsCsv
13527 * @methodOf ui.grid.exporter.service:uiGridExporterService
13528 * @description Renders a single field as a csv field, including
13529 * quotes around the value
13530 * @param {field} field the field to be turned into a csv string,
13531 * may be of any type
13532 * @returns {string} a csv-ified version of the field
13534 formatFieldAsCsv: function (field) {
13535 if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
13538 if (typeof(field.value) === 'number') {
13539 return field.value;
13541 if (typeof(field.value) === 'boolean') {
13542 return (field.value ? 'TRUE' : 'FALSE') ;
13544 if (typeof(field.value) === 'string') {
13545 return '"' + field.value.replace(/"/g,'""') + '"';
13548 return JSON.stringify(field.value);
13553 * @name renderCsvLink
13554 * @methodOf ui.grid.exporter.service:uiGridExporterService
13555 * @description Creates a download link with the csv content,
13556 * putting it into the default exporter element, or into the element
13557 * passed in if provided
13558 * @param {Grid} grid the grid from which data should be exported
13559 * @param {string} csvContent the csv content that we'd like to
13560 * make available as a download link
13561 * @param {element} $elm (Optional) A UI element into which the
13562 * resulting download link will be placed. If not provided, the
13563 * link is put into the default exporter element.
13565 renderCsvLink: function (grid, csvContent, $elm) {
13566 var targetElm = $elm ? $elm : angular.element( grid.exporter.gridElm[0].querySelectorAll('.ui-grid-exporter-csv-link') );
13567 if ( angular.element( targetElm[0].querySelectorAll('.ui-grid-exporter-csv-link-span')) ) {
13568 angular.element( targetElm[0].querySelectorAll('.ui-grid-exporter-csv-link-span')).remove();
13571 var linkTemplate = gridUtil.getTemplate(grid.options.exporterLinkTemplate)
13572 .then(function (contents) {
13574 var template = angular.element(contents);
13576 template.children("a").html(
13577 template.children("a").html().replace(
13578 uiGridExporterConstants.LINK_LABEL, grid.options.exporterLinkLabel));
13580 template.children("a").attr("href",
13581 template.children("a").attr("href").replace(
13582 uiGridExporterConstants.CSV_CONTENT, encodeURIComponent(csvContent)));
13584 var newElm = $compile(template)(grid.exporter.$scope);
13585 targetElm.append(newElm);
13593 * @methodOf ui.grid.exporter.service:uiGridExporterService
13594 * @description Exports rows from the grid in pdf format,
13595 * the data exported is selected based on the provided options.
13596 * Note that this function has a dependency on pdfMake, which must
13597 * be installed. The resulting pdf opens in a new
13599 * @param {Grid} grid the grid from which data should be exported
13600 * @param {string} rowTypes which rows to export, valid values are
13601 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13602 * uiGridExporterConstants.SELECTED
13603 * @param {string} colTypes which columns to export, valid values are
13604 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
13605 * uiGridExporterConstants.SELECTED
13607 pdfExport: function (grid, rowTypes, colTypes) {
13608 var exportColumnHeaders = this.getColumnHeaders(grid, colTypes);
13609 var exportData = this.getData(grid, rowTypes, colTypes);
13610 var docDefinition = this.prepareAsPdf(grid, exportColumnHeaders, exportData);
13612 pdfMake.createPdf(docDefinition).open();
13618 * @name renderAsPdf
13619 * @methodOf ui.grid.exporter.service:uiGridExporterService
13620 * @description Renders the data into a pdf, and opens that pdf.
13622 * @param {Grid} grid the grid from which data should be exported
13623 * @param {array} exportColumnHeaders an array of column headers,
13624 * where each header is an object with name, width and maybe alignment
13625 * @param {array} exportData an array of rows, where each row is
13626 * an array of column data
13627 * @returns {object} a pdfMake format document definition, ready
13630 prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
13631 var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
13633 var headerColumns = exportColumnHeaders.map( function( header ) {
13634 return { text: header.displayName, style: 'tableHeader' };
13637 var stringData = exportData.map(this.formatRowAsPdf(this));
13639 var allData = [headerColumns].concat(stringData);
13641 var docDefinition = {
13642 pageOrientation: grid.options.exporterPdfOrientation,
13643 pageSize: grid.options.exporterPdfPageSize,
13645 style: 'tableStyle',
13648 widths: headerWidths,
13653 tableStyle: grid.options.exporterPdfTableStyle,
13654 tableHeader: grid.options.exporterPdfTableHeaderStyle
13656 defaultStyle: grid.options.exporterPdfDefaultStyle
13659 if ( grid.options.exporterPdfLayout ){
13660 docDefinition.layout = grid.options.exporterPdfLayout;
13663 if ( grid.options.exporterPdfHeader ){
13664 docDefinition.content.unshift( grid.options.exporterPdfHeader );
13667 if ( grid.options.exporterPdfFooter ){
13668 docDefinition.content.push( grid.options.exporterPdfFooter );
13671 if ( grid.options.exporterPdfCustomFormatter ){
13672 docDefinition = grid.options.exporterPdfCustomFormatter( docDefinition );
13674 return docDefinition;
13681 * @name calculatePdfHeaderWidths
13682 * @methodOf ui.grid.exporter.service:uiGridExporterService
13683 * @description Determines the column widths base on the
13684 * widths we got from the grid. If the column is drawn
13685 * then we have a drawnWidth. If the column is not visible
13686 * then we have '*', 'x%' or a width. When columns are
13687 * not visible they don't contribute to the overall gridWidth,
13688 * so we need to adjust to allow for extra columns
13690 * Our basic heuristic is to take the current gridWidth, plus
13691 * numeric columns and call this the base gridwidth.
13693 * To that we add 100 for any '*' column, and x% of the base gridWidth
13694 * for any column that is a %
13696 * @param {Grid} grid the grid from which data should be exported
13697 * @param {object} exportHeaders array of header information
13698 * @returns {object} an array of header widths
13700 calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
13701 var baseGridWidth = 0;
13702 angular.forEach(exportHeaders, function(value){
13703 if (typeof(value.width) === 'number'){
13704 baseGridWidth += value.width;
13708 var extraColumns = 0;
13709 angular.forEach(exportHeaders, function(value){
13710 if (value.width === '*'){
13711 extraColumns += 100;
13713 if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
13714 var percent = parseInt(value.width.match(/(\d)*%/)[0]);
13716 value.width = baseGridWidth * percent / 100;
13717 extraColumns += value.width;
13721 var gridWidth = baseGridWidth + extraColumns;
13723 return exportHeaders.map(function( header ) {
13724 return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
13731 * @name formatRowAsPdf
13732 * @methodOf ui.grid.exporter.service:uiGridExporterService
13733 * @description Renders a row in a format consumable by PDF,
13734 * mainly meaning casting everything to a string
13735 * @param {exporterService} exporter pass in exporter
13736 * @param {array} row the row to be turned into a csv string
13737 * @returns {string} a csv-ified version of the row
13739 formatRowAsPdf: function ( exporter ) {
13740 return function( row ) {
13741 return row.map(exporter.formatFieldAsPdfString);
13748 * @name formatFieldAsCsv
13749 * @methodOf ui.grid.exporter.service:uiGridExporterService
13750 * @description Renders a single field as a pdf-able field, which
13751 * is different from a csv field only in that strings don't have quotes
13753 * @param {field} field the field to be turned into a pdf string,
13754 * may be of any type
13755 * @returns {string} a string-ified version of the field
13757 formatFieldAsPdfString: function (field) {
13759 if (field.value == null) { // we want to catch anything null-ish, hence just == not ===
13761 } else if (typeof(field.value) === 'number') {
13762 returnVal = field.value.toString();
13763 } else if (typeof(field.value) === 'boolean') {
13764 returnVal = (field.value ? 'TRUE' : 'FALSE') ;
13765 } else if (typeof(field.value) === 'string') {
13766 returnVal = field.value.replace(/"/g,'""');
13768 returnVal = JSON.stringify(field.value).replace(/^"/,'').replace(/"$/,'');
13771 if (field.alignment && typeof(field.alignment) === 'string' ){
13772 returnVal = { text: returnVal, alignment: field.alignment };
13786 * @name ui.grid.exporter.directive:uiGridExporter
13790 * @description Adds exporter features to grid
13793 <example module="app">
13794 <file name="app.js">
13795 var app = angular.module('app', ['ui.grid', 'ui.grid.exporter']);
13797 app.controller('MainCtrl', ['$scope', function ($scope) {
13799 { name: 'Bob', title: 'CEO' },
13800 { name: 'Frank', title: 'Lowly Developer' }
13803 $scope.gridOptions = {
13804 enableGridMenu: true,
13805 exporterMenuCsv: false,
13807 {name: 'name', enableCellEdit: true},
13808 {name: 'title', enableCellEdit: true}
13814 <file name="index.html">
13815 <div ng-controller="MainCtrl">
13816 <div ui-grid="gridOptions" ui-grid-exporter></div>
13821 module.directive('uiGridExporter', ['uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
13822 function (uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
13826 require: '^uiGrid',
13828 link: function ($scope, $elm, $attrs, uiGridCtrl) {
13829 uiGridExporterService.initializeGrid(uiGridCtrl.grid);
13830 uiGridCtrl.grid.exporter.$scope = $scope;
13842 * @name ui.grid.importer
13845 * # ui.grid.importer
13846 * This module provides the ability to import data into the grid. It
13847 * uses the column defs to work out which data belongs in which column,
13848 * and creates entities from a configured class (typically a $resource).
13850 * If the rowEdit feature is enabled, it also calls save on those newly
13851 * created objects, and then displays any errors in the imported data.
13853 * Currently the importer imports only CSV and json files, although provision has been
13854 * made to process other file formats, and these can be added over time.
13856 * For json files, the properties within each object in the json must match the column names
13857 * (to put it another way, the importer doesn't process the json, it just copies the objects
13858 * within the json into a new instance of the specified object type)
13860 * For CSV import, the default column identification relies on each column in the
13861 * header row matching a column.name or column.displayName. Optionally, a column identification
13862 * callback can be used. This allows matching using other attributes, which is particularly
13863 * useful if your application has internationalised column headings (i.e. the headings that
13864 * the user sees don't match the column names).
13866 * The importer makes use of the grid menu as the UI for requesting an
13869 * <div ui-grid-importer></div>
13872 var module = angular.module('ui.grid.importer', ['ui.grid']);
13876 * @name ui.grid.importer.constant:uiGridImporterConstants
13878 * @description constants available in importer module
13881 module.constant('uiGridImporterConstants', {
13882 featureName: 'importer'
13887 * @name ui.grid.importer.service:uiGridImporterService
13889 * @description Services for importer feature
13891 module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window',
13892 function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) {
13896 initializeGrid: function ($scope, grid) {
13898 //add feature namespace and any properties to grid for needed state
13903 this.defaultGridOptions(grid.options);
13907 * @name ui.grid.importer.api:PublicApi
13909 * @description Public Api for importer feature
13921 * @methodOf ui.grid.importer.api:PublicApi
13922 * @description Imports a file into the grid using the file object
13923 * provided. Bypasses the grid menu
13924 * @param {Grid} grid the grid we're importing into
13925 * @param {File} fileObject the file we want to import, as a javascript
13928 importFile: function ( grid, fileObject ) {
13929 service.importFile( grid, fileObject );
13935 grid.api.registerEventsFromObject(publicApi.events);
13937 grid.api.registerMethodsFromObject(publicApi.methods);
13939 if ( grid.options.enableImporter && grid.options.importerShowMenu ){
13940 if ( grid.api.core.addToGridMenu ){
13941 service.addToMenu( grid );
13943 // order of registration is not guaranteed, register in a little while
13944 $interval( function() {
13945 if (grid.api.core.addToGridMenu){
13946 service.addToMenu( grid );
13954 defaultGridOptions: function (gridOptions) {
13955 //default option to true unless it was explicitly set to false
13958 * @name ui.grid.importer.api:GridOptions
13960 * @description GridOptions for importer feature, these are available to be
13961 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
13966 * @propertyOf ui.grid.importer.api:GridOptions
13967 * @name enableImporter
13968 * @description Whether or not importer is enabled. Automatically set
13969 * to false if the user's browser does not support the required fileApi.
13970 * Otherwise defaults to true.
13973 if (gridOptions.enableImporter || gridOptions.enableImporter === undefined) {
13974 if ( !($window.hasOwnProperty('File') && $window.hasOwnProperty('FileReader') && $window.hasOwnProperty('FileList') && $window.hasOwnProperty('Blob')) ) {
13975 gridUtil.logError('The File APIs are not fully supported in this browser, grid importer cannot be used.');
13976 gridOptions.enableImporter = false;
13978 gridOptions.enableImporter = true;
13981 gridOptions.enableImporter = false;
13986 * @name importerProcessHeaders
13987 * @methodOf ui.grid.importer.api:GridOptions
13988 * @description A callback function that will process headers using custom
13989 * logic. Set this callback function if the headers that your user will provide in their
13990 * import file don't necessarily match the grid header or field names. This might commonly
13991 * occur where your application is internationalised, and therefore the field names
13992 * that the user recognises are in a different language than the field names that
13993 * ui-grid knows about.
13995 * Defaults to the internal `processHeaders` method, which seeks to match using both
13996 * displayName and column.name. Any non-matching columns are discarded.
13998 * Your callback routine should respond by processing the header array, and returning an array
13999 * of matching column names. A null value in any given position means "don't import this column"
14002 * gridOptions.importerProcessHeaders: function( headerArray ) {
14003 * var myHeaderColumns = [];
14005 * headerArray.forEach( function( value, index ) {
14006 * thisCol = mySpecialLookupFunction( value );
14007 * myHeaderColumns.push( thisCol.name );
14010 * return myHeaderCols;
14013 * @param {Grid} grid the grid we're importing into
14014 * @param {array} headerArray an array of the text from the first row of the csv file,
14015 * which you need to match to column.names
14016 * @returns {array} array of matching column names, in the same order as the headerArray
14019 gridOptions.importerProcessHeaders = gridOptions.importerProcessHeaders || service.processHeaders;
14023 * @name importerHeaderFilter
14024 * @methodOf ui.grid.importer.api:GridOptions
14025 * @description A callback function that will filter (usually translate) a single
14026 * header. Used when you want to match the passed in column names to the column
14027 * displayName after the header filter.
14029 * Your callback routine needs to return the filtered header value.
14031 * gridOptions.importerHeaderFilter: function( displayName ) {
14032 * return $translate.instant( displayName );
14038 * gridOptions.importerHeaderFilter: $translate.instant
14040 * @param {string} displayName the displayName that we'd like to translate
14041 * @returns {string} the translated name
14044 gridOptions.importerHeaderFilter = gridOptions.importerHeaderFilter || function( displayName ) { return displayName; };
14048 * @name importerErrorCallback
14049 * @methodOf ui.grid.importer.api:GridOptions
14050 * @description A callback function that provides custom error handling, rather
14051 * than the standard grid behaviour of an alert box and a console message. You
14052 * might use this to internationalise the console log messages, or to write to a
14053 * custom logging routine that returned errors to the server.
14056 * gridOptions.importerErrorCallback: function( grid, errorKey, consoleMessage, context ) {
14057 * myUserDisplayRoutine( errorKey );
14058 * myLoggingRoutine( consoleMessage, context );
14061 * @param {Grid} grid the grid we're importing into, may be useful if you're positioning messages
14063 * @param {string} errorKey one of the i18n keys the importer can return - importer.noHeaders,
14064 * importer.noObjects, importer.invalidCsv, importer.invalidJson, importer.jsonNotArray
14065 * @param {string} consoleMessage the English console message that importer would have written
14066 * @param {object} context the context data that importer would have appended to that console message,
14067 * often the file content itself or the element that is in error
14070 if ( !gridOptions.importerErrorCallback || typeof(gridOptions.importerErrorCallback) !== 'function' ){
14071 delete gridOptions.importerErrorCallback;
14076 * @name importerDataAddCallback
14077 * @methodOf ui.grid.importer.api:GridOptions
14078 * @description A mandatory callback function that adds data to the source data array. The grid
14079 * generally doesn't add rows to the source data array, it is tidier to handle this through a user
14083 * gridOptions.importerDataAddCallback: function( grid, newObjects ) {
14084 * $scope.myData = $scope.myData.concat( newObjects );
14087 * @param {Grid} grid the grid we're importing into, may be useful in some way
14088 * @param {array} newObjects an array of new objects that you should add to your data
14091 if ( gridOptions.enableImporter === true && !gridOptions.importerDataAddCallback ) {
14092 gridUtil.logError("You have not set an importerDataAddCallback, importer is disabled");
14093 gridOptions.enableImporter = false;
14098 * @name importerNewObject
14099 * @propertyOf ui.grid.importer.api:GridOptions
14100 * @description An object on which we call `new` to create each new row before inserting it into
14101 * the data array. Typically this would be a $resource entity, which means that if you're using
14102 * the rowEdit feature, you can directly call save on this entity when the save event is triggered.
14104 * Defaults to a vanilla javascript object
14108 * gridOptions.importerNewObject = MyRes;
14115 * @propertyOf ui.grid.importer.api:GridOptions
14116 * @name importerShowMenu
14117 * @description Whether or not to show an item in the grid menu. Defaults to true.
14120 gridOptions.importerShowMenu = gridOptions.importerShowMenu !== false;
14124 * @methodOf ui.grid.importer.api:GridOptions
14125 * @name importerObjectCallback
14126 * @description A callback that massages the data for each object. For example,
14127 * you might have data stored as a code value, but display the decode. This callback
14128 * can be used to change the decoded value back into a code. Defaults to doing nothing.
14129 * @param {Grid} grid in case you need it
14130 * @param {object} newObject the new object as importer has created it, modify it
14131 * then return the modified version
14132 * @returns {object} the modified object
14135 * gridOptions.importerObjectCallback = function ( grid, newObject ) {
14136 * switch newObject.status {
14138 * newObject.status = 1;
14141 * newObject.status = 2;
14144 * return newObject;
14148 gridOptions.importerObjectCallback = gridOptions.importerObjectCallback || function( grid, newObject ) { return newObject; };
14155 * @methodOf ui.grid.importer.service:uiGridImporterService
14156 * @description Adds import menu item to the grid menu,
14157 * allowing the user to request import of a file
14158 * @param {Grid} grid the grid into which data should be imported
14160 addToMenu: function ( grid ) {
14161 grid.api.core.addToGridMenu( grid, [
14163 title: i18nService.getSafeText('gridMenu.importerTitle')
14166 templateUrl: 'ui-grid/importerMenuItemContainer',
14167 action: function ($event) {
14168 this.grid.api.importer.importAFile( grid );
14177 * @name importThisFile
14178 * @methodOf ui.grid.importer.service:uiGridImporterService
14179 * @description Imports the provided file into the grid using the file object
14180 * provided. Bypasses the grid menu
14181 * @param {Grid} grid the grid we're importing into
14182 * @param {File} fileObject the file we want to import, as returned from the File
14183 * javascript object
14185 importThisFile: function ( grid, fileObject ) {
14187 gridUtil.logError( 'No file object provided to importThisFile, should be impossible, aborting');
14191 var reader = new FileReader();
14193 switch ( fileObject.type ){
14194 case 'application/json':
14195 reader.onload = service.importJsonClosure( grid );
14198 reader.onload = service.importCsvClosure( grid );
14202 reader.readAsText( fileObject );
14209 * @methodOf ui.grid.importer.service:uiGridImporterService
14210 * @description Creates a function that imports a json file into the grid.
14211 * The json data is imported into new objects of type `gridOptions.importerNewObject`,
14212 * and ift he rowEdit feature is enabled the rows are marked as dirty
14213 * @param {Grid} grid the grid we want to import into
14214 * @param {FileObject} importFile the file that we want to import, as
14217 importJsonClosure: function( grid ) {
14218 return function( importFile ){
14219 var newObjects = [];
14222 angular.forEach( service.parseJson( grid, importFile ), function( value, index ) {
14223 newObject = service.newObject( grid );
14224 angular.extend( newObject, value );
14225 newObject = grid.options.importerObjectCallback( grid, newObject );
14226 newObjects.push( newObject );
14229 service.addObjects( grid, newObjects );
14238 * @methodOf ui.grid.importer.service:uiGridImporterService
14239 * @description Parses a json file, returns the parsed data.
14240 * Displays an error if file doesn't parse
14241 * @param {Grid} grid the grid that we want to import into
14242 * @param {FileObject} importFile the file that we want to import, as
14244 * @returns {array} array of objects from the imported json
14246 parseJson: function( grid, importFile ){
14249 loadedObjects = JSON.parse( importFile.target.result );
14251 service.alertError( grid, 'importer.invalidJson', 'File could not be processed, is it valid json? Content was: ', importFile.target.result );
14255 if ( !Array.isArray( loadedObjects ) ){
14256 service.alertError( grid, 'importer.jsonNotarray', 'Import failed, file is not an array, file was: ', importFile.target.result );
14259 return loadedObjects;
14267 * @name importCsvClosure
14268 * @methodOf ui.grid.importer.service:uiGridImporterService
14269 * @description Creates a function that imports a csv file into the grid
14270 * (allowing it to be used in the reader.onload event)
14271 * @param {Grid} grid the grid that we want to import into
14272 * @param {FileObject} importFile the file that we want to import, as
14275 importCsvClosure: function( grid ) {
14276 return function( importFile ){
14277 var importArray = service.parseCsv( importFile );
14278 if ( !importArray || importArray.length < 1 ){
14279 service.alertError( grid, 'importer.invalidCsv', 'File could not be processed, is it valid csv? Content was: ', importFile.target.result );
14283 var newObjects = service.createCsvObjects( grid, importArray );
14284 if ( !newObjects || newObjects.length === 0 ){
14285 service.alertError( grid, 'importer.noObjects', 'Objects were not able to be derived, content was: ', importFile.target.result );
14289 service.addObjects( grid, newObjects );
14297 * @methodOf ui.grid.importer.service:uiGridImporterService
14298 * @description Parses a csv file into an array of arrays, with the first
14299 * array being the headers, and the remaining arrays being the data.
14300 * The logic for this comes from https://github.com/thetalecrafter/excel.js/blob/master/src/csv.js,
14301 * which is noted as being under the MIT license. The code is modified to pass the jscs yoda condition
14303 * @param {FileObject} importFile the file that we want to import, as a
14306 parseCsv: function( importFile ) {
14307 var csv = importFile.target.result;
14309 // use the CSV-JS library to parse
14310 return CSV.parse(csv);
14316 * @name createCsvObjects
14317 * @methodOf ui.grid.importer.service:uiGridImporterService
14318 * @description Converts an array of arrays (representing the csv file)
14319 * into a set of objects. Uses the provided `gridOptions.importerNewObject`
14320 * to create the objects, and maps the header row into the individual columns
14321 * using either `gridOptions.importerProcessHeaders`, or by using a native method
14322 * of matching to either the displayName, column name or column field of
14323 * the columns in the column defs. The resulting objects will have attributes
14324 * that are named based on the column.field or column.name, in that order.
14325 * @param {Grid} grid the grid that we want to import into
14326 * @param {FileObject} importFile the file that we want to import, as a
14329 createCsvObjects: function( grid, importArray ){
14330 // pull off header row and turn into headers
14331 var headerMapping = grid.options.importerProcessHeaders( grid, importArray.shift() );
14332 if ( !headerMapping || headerMapping.length === 0 ){
14333 service.alertError( grid, 'importer.noHeaders', 'Column names could not be derived, content was: ', importArray );
14337 var newObjects = [];
14339 angular.forEach( importArray, function( row, index ) {
14340 newObject = service.newObject( grid );
14341 angular.forEach( row, function( field, index ){
14342 if ( headerMapping[index] !== null ){
14343 newObject[ headerMapping[index] ] = field;
14346 newObject = grid.options.importerObjectCallback( grid, newObject );
14347 newObjects.push( newObject );
14356 * @name processHeaders
14357 * @methodOf ui.grid.importer.service:uiGridImporterService
14358 * @description Determines the columns that the header row from
14359 * a csv (or other) file represents.
14360 * @param {Grid} grid the grid we're importing into
14361 * @param {array} headerRow the header row that we wish to match against
14362 * the column definitions
14363 * @returns {array} an array of the attribute names that should be used
14364 * for that column, based on matching the headers or creating the headers
14367 processHeaders: function( grid, headerRow ) {
14369 if ( !grid.options.columnDefs || grid.options.columnDefs.length === 0 ){
14370 // we are going to create new columnDefs for all these columns, so just remove
14371 // spaces from the names to create fields
14372 angular.forEach( headerRow, function( value, index ) {
14373 headers.push( value.replace( /[^0-9a-zA-Z\-_]/g, '_' ) );
14377 var lookupHash = service.flattenColumnDefs( grid, grid.options.columnDefs );
14378 angular.forEach( headerRow, function( value, index ) {
14379 if ( lookupHash[value] ) {
14380 headers.push( lookupHash[value] );
14381 } else if ( lookupHash[ value.toLowerCase() ] ) {
14382 headers.push( lookupHash[ value.toLowerCase() ] );
14384 headers.push( null );
14393 * @name flattenColumnDefs
14394 * @methodOf ui.grid.importer.service:uiGridImporterService
14395 * @description Runs through the column defs and creates a hash of
14396 * the displayName, name and field, and of each of those values forced to lower case,
14397 * with each pointing to the field or name
14398 * (whichever is present). Used to lookup column headers and decide what
14399 * attribute name to give to the resulting field.
14400 * @param {Grid} grid the grid we're importing into
14401 * @param {array} columnDefs the columnDefs that we should flatten
14402 * @returns {hash} the flattened version of the column def information, allowing
14403 * us to look up a value by `flattenedHash[ headerValue ]`
14405 flattenColumnDefs: function( grid, columnDefs ){
14406 var flattenedHash = {};
14407 angular.forEach( columnDefs, function( columnDef, index) {
14408 if ( columnDef.name ){
14409 flattenedHash[ columnDef.name ] = columnDef.field || columnDef.name;
14410 flattenedHash[ columnDef.name.toLowerCase() ] = columnDef.field || columnDef.name;
14413 if ( columnDef.field ){
14414 flattenedHash[ columnDef.field ] = columnDef.field || columnDef.name;
14415 flattenedHash[ columnDef.field.toLowerCase() ] = columnDef.field || columnDef.name;
14418 if ( columnDef.displayName ){
14419 flattenedHash[ columnDef.displayName ] = columnDef.field || columnDef.name;
14420 flattenedHash[ columnDef.displayName.toLowerCase() ] = columnDef.field || columnDef.name;
14423 if ( columnDef.displayName && grid.options.importerHeaderFilter ){
14424 flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName) ] = columnDef.field || columnDef.name;
14425 flattenedHash[ grid.options.importerHeaderFilter(columnDef.displayName).toLowerCase() ] = columnDef.field || columnDef.name;
14429 return flattenedHash;
14436 * @methodOf ui.grid.importer.service:uiGridImporterService
14437 * @description Inserts our new objects into the grid data, and
14438 * sets the rows to dirty if the rowEdit feature is being used
14440 * Does this by registering a watch on dataChanges, which essentially
14441 * is waiting on the result of the grid data watch, and downstream processing.
14443 * When the callback is called, it deregisters itself - we don't want to run
14444 * again next time data is added.
14446 * If we never get called, we deregister on destroy.
14448 * @param {Grid} grid the grid we're importing into
14449 * @param {array} newObjects the objects we want to insert into the grid data
14450 * @returns {object} the new object
14452 addObjects: function( grid, newObjects, $scope ){
14453 if ( grid.api.rowEdit ){
14454 var callbackId = grid.registerDataChangeCallback( function() {
14455 grid.api.rowEdit.setRowsDirty( grid, newObjects );
14456 grid.deregisterDataChangeCallback( callbackId );
14457 }, [uiGridConstants.dataChange.ROW] );
14459 var deregisterClosure = function() {
14460 grid.deregisterDataChangeCallback( callbackId );
14463 grid.importer.$scope.$on( '$destroy', deregisterClosure );
14466 grid.importer.$scope.$apply( grid.options.importerDataAddCallback( grid, newObjects ) );
14474 * @methodOf ui.grid.importer.service:uiGridImporterService
14475 * @description Makes a new object based on `gridOptions.importerNewObject`,
14476 * or based on an empty object if not present
14477 * @param {Grid} grid the grid we're importing into
14478 * @returns {object} the new object
14480 newObject: function( grid ){
14481 if ( typeof(grid.options) !== "undefined" && typeof(grid.options.importerNewObject) !== "undefined" ){
14482 return new grid.options.importerNewObject();
14492 * @methodOf ui.grid.importer.service:uiGridImporterService
14493 * @description Provides an internationalised user alert for the failure,
14494 * and logs a console message including diagnostic content.
14495 * Optionally, if the the `gridOptions.importerErrorCallback` routine
14496 * is defined, then calls that instead, allowing user specified error routines
14497 * @param {Grid} grid the grid we're importing into
14498 * @param {array} headerRow the header row that we wish to match against
14499 * the column definitions
14501 alertError: function( grid, alertI18nToken, consoleMessage, context ){
14502 if ( grid.options.importerErrorCallback ){
14503 grid.options.importerErrorCallback( grid, alertI18nToken, consoleMessage, context );
14505 $window.alert(i18nService.getSafeText( alertI18nToken ));
14506 gridUtil.logError(consoleMessage + context );
14518 * @name ui.grid.importer.directive:uiGridImporter
14522 * @description Adds importer features to grid
14525 module.directive('uiGridImporter', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
14526 function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
14530 require: '^uiGrid',
14532 link: function ($scope, $elm, $attrs, uiGridCtrl) {
14533 uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid);
14541 * @name ui.grid.importer.directive:uiGridImporterMenuItem
14545 * @description Handles the processing from the importer menu item - once a file is
14549 module.directive('uiGridImporterMenuItem', ['uiGridImporterConstants', 'uiGridImporterService', 'gridUtil', '$compile',
14550 function (uiGridImporterConstants, uiGridImporterService, gridUtil, $compile) {
14554 require: '^uiGrid',
14556 templateUrl: 'ui-grid/importerMenuItem',
14557 link: function ($scope, $elm, $attrs, uiGridCtrl) {
14558 var handleFileSelect = function( event ){
14559 if (event.srcElement.files.length === 1) {
14560 var fileObject = event.srcElement.files[0];
14561 uiGridImporterService.importThisFile( grid, fileObject );
14562 event.srcElement.form.reset();
14566 var fileChooser = $elm[0].querySelectorAll('.ui-grid-importer-file-chooser');
14567 var grid = uiGridCtrl.grid;
14569 if ( fileChooser.length !== 1 ){
14570 gridUtil.logError('Found > 1 or < 1 file choosers within the menu item, error, cannot continue');
14572 fileChooser[0].addEventListener('change', handleFileSelect, false); // TODO: why the false on the end? Google
14583 * @name ui.grid.infiniteScroll
14587 * #ui.grid.infiniteScroll
14588 * This module provides infinite scroll functionality to ui-grid
14591 var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
14594 * @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14596 * @description Service for infinite scroll features
14598 module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', function (gridUtil, $compile, $timeout) {
14604 * @name initializeGrid
14605 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14606 * @description This method register events and methods into grid public API
14609 initializeGrid: function(grid) {
14610 service.defaultGridOptions(grid.options);
14614 * @name ui.grid.infiniteScroll.api:PublicAPI
14616 * @description Public API for infinite scroll feature
14624 * @name needLoadMoreData
14625 * @eventOf ui.grid.infiniteScroll.api:PublicAPI
14626 * @description This event fires when scroll reached bottom percentage of grid
14627 * and needs to load data
14630 needLoadMoreData: function ($scope, fn) {
14640 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
14641 * @description This function is used as a promise when data finished loading.
14642 * See infinite_scroll ngdoc for example of usage
14645 dataLoaded: function() {
14646 grid.options.loadTimout = false;
14651 grid.options.loadTimout = false;
14652 grid.api.registerEventsFromObject(publicApi.events);
14653 grid.api.registerMethodsFromObject(publicApi.methods);
14655 defaultGridOptions: function (gridOptions) {
14656 //default option to true unless it was explicitly set to false
14659 * @name ui.grid.infiniteScroll.api:GridOptions
14661 * @description GridOptions for infinite scroll feature, these are available to be
14662 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
14667 * @name enableInfiniteScroll
14668 * @propertyOf ui.grid.infiniteScroll.api:GridOptions
14669 * @description Enable infinite scrolling for this grid
14670 * <br/>Defaults to true
14672 gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
14679 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14680 * @description This function fires 'needLoadMoreData' event
14683 loadData: function (grid) {
14684 grid.options.loadTimout = true;
14685 grid.api.infiniteScroll.raise.needLoadMoreData();
14690 * @name checkScroll
14691 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
14692 * @description This function checks scroll position inside grid and
14693 * calls 'loadData' function when scroll reaches 'infiniteScrollPercentage'
14696 checkScroll: function(grid, scrollTop) {
14698 /* Take infiniteScrollPercentage value or use 20% as default */
14699 var infiniteScrollPercentage = grid.options.infiniteScrollPercentage ? grid.options.infiniteScrollPercentage : 20;
14701 if (!grid.options.loadTimout && scrollTop <= infiniteScrollPercentage) {
14702 this.loadData(grid);
14709 * @name infiniteScrollPercentage
14710 * @propertyOf ui.grid.class:GridOptions
14711 * @description This setting controls at what percentage of the scroll more data
14712 * is requested by the infinite scroll
14719 * @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
14723 * @description Adds infinite scroll features to grid
14726 <example module="app">
14727 <file name="app.js">
14728 var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
14730 app.controller('MainCtrl', ['$scope', function ($scope) {
14732 { name: 'Alex', car: 'Toyota' },
14733 { name: 'Sam', car: 'Lexus' }
14736 $scope.columnDefs = [
14742 <file name="index.html">
14743 <div ng-controller="MainCtrl">
14744 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
14750 module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
14751 function (uiGridInfiniteScrollService) {
14755 require: '^uiGrid',
14756 compile: function($scope, $elm, $attr){
14758 pre: function($scope, $elm, $attr, uiGridCtrl) {
14759 uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid);
14761 post: function($scope, $elm, $attr) {
14768 module.directive('uiGridViewport',
14769 ['$compile', 'gridUtil', 'uiGridInfiniteScrollService', 'uiGridConstants',
14770 function ($compile, gridUtil, uiGridInfiniteScrollService, uiGridConstants) {
14774 link: function ($scope, $elm, $attr){
14775 if ($scope.grid.options.enableInfiniteScroll) {
14776 $scope.$on(uiGridConstants.events.GRID_SCROLL, function (evt, args) {
14778 var percentage = 100 - (args.y.percentage * 100);
14779 uiGridInfiniteScrollService.checkScroll($scope.grid, percentage);
14792 * @name ui.grid.moveColumns
14794 * # ui.grid.moveColumns
14795 * This module provides column moving capability to ui.grid. It enables to change the position of columns.
14796 * <div doc-module-components="ui.grid.moveColumns"></div>
14798 var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
14802 * @name ui.grid.moveColumns.service:uiGridMoveColumnService
14803 * @description Service for column moving feature.
14805 module.service('uiGridMoveColumnService', ['$q', '$timeout', '$log', function ($q, $timeout, $log) {
14808 initializeGrid: function (grid) {
14810 this.registerPublicApi(grid);
14811 this.defaultGridOptions(grid.options);
14812 grid.registerColumnBuilder(self.movableColumnBuilder);
14814 registerPublicApi: function (grid) {
14818 * @name ui.grid.moveColumns.api:PublicApi
14819 * @description Public Api for column moving feature.
14825 * @name columnPositionChanged
14826 * @eventOf ui.grid.moveColumns.api:PublicApi
14827 * @description raised when column is moved
14829 * gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
14831 * @param {object} colDef the column that was moved
14832 * @param {integer} originalPosition of the column
14833 * @param {integer} finalPosition of the column
14836 columnPositionChanged: function (colDef, originalPosition, newPosition) {
14844 * @methodOf ui.grid.moveColumns.api:PublicApi
14845 * @description Method can be used to change column position.
14847 * gridApi.colMovable.on.moveColumn(oldPosition, newPosition)
14849 * @param {integer} originalPosition of the column
14850 * @param {integer} finalPosition of the column
14853 moveColumn: function (originalPosition, finalPosition) {
14854 self.redrawColumnAtPosition(grid, originalPosition, finalPosition);
14859 grid.api.registerEventsFromObject(publicApi.events);
14860 grid.api.registerMethodsFromObject(publicApi.methods);
14862 defaultGridOptions: function (gridOptions) {
14865 * @name ui.grid.moveColumns.api:GridOptions
14867 * @description Options for configuring the move column feature, these are available to be
14868 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
14872 * @name enableColumnMoving
14873 * @propertyOf ui.grid.moveColumns.api:GridOptions
14874 * @description If defined, sets the default value for the colMovable flag on each individual colDefs
14875 * if their individual enableColumnMoving configuration is not defined. Defaults to true.
14877 gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
14879 movableColumnBuilder: function (colDef, col, gridOptions) {
14883 * @name ui.grid.moveColumns.api:ColumnDef
14885 * @description Column Definition for move column feature, these are available to be
14886 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
14890 * @name enableColumnMoving
14891 * @propertyOf ui.grid.moveColumns.api:ColumnDef
14892 * @description Enable column moving for the column.
14894 colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
14895 : colDef.enableColumnMoving;
14896 return $q.all(promises);
14898 redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
14899 var columns = grid.columns;
14901 //Function to find column position for a render index, ths is needed to take care of
14902 // invisible columns and row headers
14903 var findPositionForRenderIndex = function (index) {
14904 var position = index;
14905 for (var i = 0; i <= position; i++) {
14906 if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false)) {
14913 originalPosition = findPositionForRenderIndex(originalPosition);
14914 newPosition = findPositionForRenderIndex(newPosition);
14915 var originalColumn = columns[originalPosition];
14916 if (originalColumn.colDef.enableColumnMoving) {
14917 if (originalPosition > newPosition) {
14918 for (var i1 = originalPosition; i1 > newPosition; i1--) {
14919 columns[i1] = columns[i1 - 1];
14922 else if (newPosition > originalPosition) {
14923 for (var i2 = originalPosition; i2 < newPosition; i2++) {
14924 columns[i2] = columns[i2 + 1];
14927 columns[newPosition] = originalColumn;
14928 $timeout(function () {
14930 grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
14940 * @name ui.grid.moveColumns.directive:uiGridMoveColumns
14943 * @description Adds column moving features to the ui-grid directive.
14945 <example module="app">
14946 <file name="app.js">
14947 var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
14948 app.controller('MainCtrl', ['$scope', function ($scope) {
14950 { name: 'Bob', title: 'CEO', age: 45 },
14951 { name: 'Frank', title: 'Lowly Developer', age: 25 },
14952 { name: 'Jenny', title: 'Highly Developer', age: 35 }
14954 $scope.columnDefs = [
14961 <file name="main.css">
14967 <file name="index.html">
14968 <div ng-controller="MainCtrl">
14969 <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
14974 module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
14978 require: '^uiGrid',
14980 compile: function () {
14982 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
14983 uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
14985 post: function ($scope, $elm, $attrs, uiGridCtrl) {
14994 * @name ui.grid.moveColumns.directive:uiGridHeaderCell
14998 * @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
15000 * On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
15001 * In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
15002 * On mouseUp event column is repositioned at position where mouse is released and coned header cell is removed.
15004 * Events that invoke cloning of header cell:
15007 * Events that invoke movement of cloned header cell:
15010 * Events that invoke repositioning of column:
15013 module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document',
15014 function ($q, gridUtil, uiGridMoveColumnService, $document) {
15017 require: '^uiGrid',
15018 compile: function () {
15020 post: function ($scope, $elm, $attrs, uiGridCtrl) {
15021 if ($scope.col.colDef.enableColumnMoving) {
15023 var mouseDownHandler = function (evt) {
15024 if (evt.target.className !== 'ui-grid-icon-angle-down' && evt.target.tagName !== 'I') {
15026 //Cloning header cell and appending to current header cell.
15027 var movingElm = $elm.clone();
15028 $elm.append(movingElm);
15030 //Left of cloned element should be aligned to original header cell.
15031 movingElm.addClass('movingColumn');
15032 var movingElementStyles = {};
15033 var gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
15034 var elmLeft = $elm[0].getBoundingClientRect().left;
15035 movingElementStyles.left = (elmLeft - gridLeft) + 'px';
15036 var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
15037 var elmRight = $elm[0].getBoundingClientRect().right;
15039 if (elmRight > gridRight) {
15040 reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
15041 movingElementStyles.width = reducedWidth + 'px';
15043 //movingElementStyles.visibility = 'hidden';
15044 movingElm.css(movingElementStyles);
15046 //Setting some variables required for calculations.
15047 var previousMouseX = evt.pageX;
15048 var totalMouseMovement = 0;
15049 var rightMoveLimit = gridLeft + $scope.grid.getViewportWidth() - $scope.grid.verticalScrollbarWidth;
15051 //Clone element should move horizontally with mouse.
15052 var mouseMoveHandler = function (evt) {
15053 uiGridCtrl.fireEvent('hide-menu');
15054 var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
15055 var currentElmRight = movingElm[0].getBoundingClientRect().right;
15056 var changeValue = evt.pageX - previousMouseX;
15057 var newElementLeft;
15058 if (gridUtil.detectBrowser() === 'ie') {
15059 newElementLeft = currentElmLeft + changeValue;
15062 newElementLeft = currentElmLeft - gridLeft + changeValue;
15064 newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
15065 if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
15066 movingElm.css({visibility: 'visible', 'left': newElementLeft + 'px'});
15070 uiGridCtrl.fireScrollingEvent({ x: { pixels: changeValue * 2.5} });
15072 totalMouseMovement += changeValue;
15073 previousMouseX = evt.pageX;
15074 if (reducedWidth < $scope.col.drawnWidth) {
15075 reducedWidth += Math.abs(changeValue);
15076 movingElm.css({'width': reducedWidth + 'px'});
15080 // On scope destroy, remove the mouse event handlers from the document body
15081 $scope.$on('$destroy', function () {
15082 $document.off('mousemove', mouseMoveHandler);
15083 $document.off('mouseup', mouseUpHandler);
15086 $document.on('mousemove', mouseMoveHandler);
15087 var mouseUpHandler = function (evt) {
15088 var renderIndexDefer = $q.defer();
15091 $attrs.$observe('renderIndex', function (n, o) {
15092 renderIndex = $scope.$eval(n);
15093 renderIndexDefer.resolve();
15096 renderIndexDefer.promise.then(function () {
15098 //Remove the cloned element on mouse up.
15100 movingElm.remove();
15103 var renderedColumns = $scope.grid.renderContainers['body'].renderedColumns;
15105 //This method will calculate the number of columns hidden in lift due to scroll
15106 //renderContainer.prevColumnScrollIndex could also have been used but this is more accurate
15107 var scrolledColumnCount = 0;
15108 var columns = $scope.grid.columns;
15109 for (var i = 0; i < columns.length; i++) {
15110 if (columns[i].colDef.name !== renderedColumns[0].colDef.name) {
15111 scrolledColumnCount++;
15118 //Case where column should be moved to a position on its left
15119 if (totalMouseMovement < 0) {
15120 var totalColumnsLeftWidth = 0;
15121 for (var il = renderIndex - 1; il >= 0; il--) {
15122 totalColumnsLeftWidth += renderedColumns[il].drawnWidth;
15123 if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
15124 uiGridMoveColumnService.redrawColumnAtPosition
15125 ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + il + 1);
15129 //Case where column should be moved to beginning of the grid.
15130 if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
15131 uiGridMoveColumnService.redrawColumnAtPosition
15132 ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + 0);
15135 //Case where column should be moved to a position on its right
15136 else if (totalMouseMovement > 0) {
15137 var totalColumnsRightWidth = 0;
15138 for (var ir = renderIndex + 1; ir < renderedColumns.length; ir++) {
15139 totalColumnsRightWidth += renderedColumns[ir].drawnWidth;
15140 if (totalColumnsRightWidth > totalMouseMovement) {
15141 uiGridMoveColumnService.redrawColumnAtPosition
15142 ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + ir - 1);
15146 //Case where column should be moved to end of the grid.
15147 if (totalColumnsRightWidth < totalMouseMovement) {
15148 uiGridMoveColumnService.redrawColumnAtPosition
15149 ($scope.grid, scrolledColumnCount + renderIndex, scrolledColumnCount + renderedColumns.length - 1);
15152 else if (totalMouseMovement === 0) {
15153 if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
15154 //sort the current column
15156 if (evt.shiftKey) {
15160 // Sort this column then rebuild the grid's rows
15161 uiGridCtrl.grid.sortColumn($scope.col, add)
15162 .then(function () {
15163 if (uiGridCtrl.columnMenuScope) {
15164 uiGridCtrl.columnMenuScope.hideMenu();
15166 uiGridCtrl.grid.refresh();
15171 $document.off('mousemove', mouseMoveHandler);
15172 $document.off('mouseup', mouseUpHandler);
15176 $document.on('mouseup', mouseUpHandler);
15180 $elm.on('mousedown', mouseDownHandler);
15194 * @name ui.grid.pagination
15198 * #ui.grid.pagination
15199 * This module provides pagination support to ui-grid
15201 var module = angular.module('ui.grid.pagination', ['ui.grid']);
15205 * @name ui.grid.pagination.service:uiGridPaginationService
15207 * @description Service for the pagination feature
15209 module.service('uiGridPaginationService', function () {
15214 * @name initializeGrid
15215 * @methodOf ui.grid.pagination.service:uiGridPaginationService
15216 * @description Attaches the service to a certain grid
15217 * @param {Grid} grid The grid we want to work with
15219 initializeGrid: function (grid) {
15220 service.defaultGridOptions(grid.options);
15221 grid.pagination = {page: 1, totalPages: 1};
15225 * @name ui.grid.pagination.api:PublicAPI
15227 * @description Public API for the pagination feature
15235 * @methodOf ui.grid.pagination.api:PublicAPI
15236 * @description Returns the number of the current page
15238 getPage: function () {
15239 return grid.pagination.page;
15243 * @name getTotalPages
15244 * @methodOf ui.grid.pagination.api:PublicAPI
15245 * @description Returns the total number of pages
15247 getTotalPages: function () {
15248 return grid.pagination.totalPages;
15253 * @methodOf ui.grid.pagination.api:PublicAPI
15254 * @description Moves to the next page, if possible
15256 nextPage: function () {
15257 grid.pagination.page++;
15262 * @name previousPage
15263 * @methodOf ui.grid.pagination.api:PublicAPI
15264 * @description Moves to the previous page, if we're not on the first page
15266 previousPage: function () {
15267 grid.pagination.page = Math.max(1, grid.pagination.page - 1);
15270 seek: function (page) {
15271 if (!angular.isNumber(page) || page < 1) {
15272 throw 'Invalid page number: ' + page;
15275 grid.pagination.page = page;
15281 grid.api.registerMethodsFromObject(publicApi.methods);
15282 grid.registerRowsProcessor(function (renderableRows) {
15283 if (!grid.options.enablePagination) {
15284 return renderableRows;
15286 grid.pagination.totalPages = Math.max(
15288 Math.ceil(renderableRows.length / grid.options.rowsPerPage)
15291 var firstRow = (grid.pagination.page - 1) * grid.options.rowsPerPage;
15292 if (firstRow >= renderableRows.length) {
15293 grid.pagination.page = grid.pagination.totalPages;
15294 firstRow = (grid.pagination.page - 1) * grid.options.rowsPerPage;
15297 return renderableRows.slice(
15299 firstRow + grid.options.rowsPerPage
15304 defaultGridOptions: function (gridOptions) {
15307 * @name ui.grid.pagination.api:GridOptions
15309 * @description GridOptions for the pagination feature, these are available to be
15310 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15315 * @name enablePagination
15316 * @propertyOf ui.grid.pagination.api:GridOptions
15317 * @description Enable pagination for this grid
15318 * <br/>Defaults to true
15320 gridOptions.enablePagination = gridOptions.enablePagination !== false;
15324 * @name rowsPerPage
15325 * @propertyOf ui.grid.pagination.api:GridOptions
15326 * @description The number of rows that should be displayed per page
15327 * <br/>Defaults to 10
15329 gridOptions.rowsPerPage = angular.isNumber(gridOptions.rowsPerPage) ? gridOptions.rowsPerPage : 10;
15338 * @name ui.grid.pagination.directive:uiGridPagination
15342 * @description Adds pagination support to a grid.
15344 module.directive('uiGridPagination', ['uiGridPaginationService', function (uiGridPaginationService) {
15348 require: '^uiGrid',
15350 pre: function (scope, element, attrs, uiGridCtrl) {
15351 uiGridPaginationService.initializeGrid(uiGridCtrl.grid);
15362 * @name ui.grid.paging
15367 * This module provides paging support to ui-grid
15370 var module = angular.module('ui.grid.paging', ['ui.grid']);
15374 * @name ui.grid.paging.service:uiGridPagingService
15376 * @description Service for the paging feature
15378 module.service('uiGridPagingService', ['gridUtil',
15379 function (gridUtil) {
15383 * @name initializeGrid
15384 * @methodOf ui.grid.paging.service:uiGridPagingService
15385 * @description Attaches the service to a certain grid
15386 * @param {Grid} grid The grid we want to work with
15388 initializeGrid: function (grid) {
15389 service.defaultGridOptions(grid.options);
15393 * @name ui.grid.paging.api:PublicAPI
15395 * @description Public API for the paging feature
15402 * @name pagingChanged
15403 * @eventOf ui.grid.paging.api:PublicAPI
15404 * @description This event fires when the pageSize or currentPage changes
15405 * @param {currentPage} requested page number
15406 * @param {pageSize} requested page size
15408 pagingChanged: function (currentPage, pageSize) { }
15417 grid.api.registerEventsFromObject(publicApi.events);
15418 grid.api.registerMethodsFromObject(publicApi.methods);
15419 grid.registerRowsProcessor(function (renderableRows) {
15420 if (grid.options.useExternalPaging || !grid.options.enablePaging) {
15421 return renderableRows;
15423 //client side paging
15424 var pageSize = parseInt(grid.options.pagingPageSize, 10);
15425 var currentPage = parseInt(grid.options.pagingCurrentPage, 10);
15427 var firstRow = (currentPage - 1) * pageSize;
15428 return renderableRows.slice(firstRow, firstRow + pageSize);
15432 defaultGridOptions: function (gridOptions) {
15435 * @name enablePaging
15436 * @propertyOf ui.grid.class:GridOptions
15437 * @description Enables paging, defaults to true
15439 gridOptions.enablePaging = gridOptions.enablePaging !== false;
15442 * @name useExternalPaging
15443 * @propertyOf ui.grid.class:GridOptions
15444 * @description Disables client side paging. When true, handle the pagingChanged event and set data and totalItems
15445 * defaults to false
15447 gridOptions.useExternalPaging = gridOptions.useExternalPaging === true;
15451 * @propertyOf ui.grid.class:GridOptions
15452 * @description Total number of items, set automatically when client side paging, needs set by user for server side paging
15454 if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
15455 gridOptions.totalItems = 0;
15459 * @name pagingPageSizes
15460 * @propertyOf ui.grid.class:GridOptions
15461 * @description Array of page sizes
15462 * defaults to [250, 500, 1000]
15464 if (gridUtil.isNullOrUndefined(gridOptions.pagingPageSizes)) {
15465 gridOptions.pagingPageSizes = [250, 500, 1000];
15469 * @name pagingPageSize
15470 * @propertyOf ui.grid.class:GridOptions
15471 * @description Page size
15472 * defaults to the first item in pagingPageSizes, or 0 if pagingPageSizes is empty
15474 if (gridUtil.isNullOrUndefined(gridOptions.pagingPageSize)) {
15475 if (gridOptions.pagingPageSizes.length > 0) {
15476 gridOptions.pagingPageSize = gridOptions.pagingPageSizes[0];
15478 gridOptions.pagingPageSize = 0;
15483 * @name pagingCurrentPage
15484 * @propertyOf ui.grid.class:GridOptions
15485 * @description Current page number
15488 if (gridUtil.isNullOrUndefined(gridOptions.pagingCurrentPage)) {
15489 gridOptions.pagingCurrentPage = 1;
15494 * @methodOf ui.grid.paging.service:uiGridPagingService
15495 * @name uiGridPagingService
15496 * @description Raises pagingChanged and calls refresh for client side paging
15497 * @param {grid} the grid for which the paging changed
15498 * @param {currentPage} requested page number
15499 * @param {pageSize} requested page size
15501 onPagingChanged: function (grid, currentPage, pageSize) {
15502 grid.api.paging.raise.pagingChanged(currentPage, pageSize);
15503 if (!grid.options.useExternalPaging) {
15504 grid.refresh(); //client side paging
15514 * @name ui.grid.paging.directive:uiGridPaging
15518 * @description Adds paging features to grid
15520 <example module="app">
15521 <file name="app.js">
15522 var app = angular.module('app', ['ui.grid', 'ui.grid.paging']);
15524 app.controller('MainCtrl', ['$scope', function ($scope) {
15526 { name: 'Alex', car: 'Toyota' },
15527 { name: 'Sam', car: 'Lexus' },
15528 { name: 'Joe', car: 'Dodge' },
15529 { name: 'Bob', car: 'Buick' },
15530 { name: 'Cindy', car: 'Ford' },
15531 { name: 'Brian', car: 'Audi' },
15532 { name: 'Malcom', car: 'Mercedes Benz' },
15533 { name: 'Dave', car: 'Ford' },
15534 { name: 'Stacey', car: 'Audi' },
15535 { name: 'Amy', car: 'Acura' },
15536 { name: 'Scott', car: 'Toyota' },
15537 { name: 'Ryan', car: 'BMW' },
15540 $scope.gridOptions = {
15542 pagingPageSizes: [5, 10, 25],
15551 <file name="index.html">
15552 <div ng-controller="MainCtrl">
15553 <div ui-grid="gridOptions" ui-grid-paging></div>
15558 module.directive('uiGridPaging', ['gridUtil', 'uiGridPagingService',
15559 function (gridUtil, uiGridPagingService) {
15562 * @name pagingTemplate
15563 * @propertyOf ui.grid.class:GridOptions
15564 * @description a custom template for the pager. The default
15565 * is ui-grid/ui-grid-paging
15567 var defaultTemplate = 'ui-grid/ui-grid-paging';
15573 compile: function ($scope, $elm, $attr, uiGridCtrl) {
15575 pre: function ($scope, $elm, $attr, uiGridCtrl) {
15577 uiGridPagingService.initializeGrid(uiGridCtrl.grid);
15579 var pagingTemplate = uiGridCtrl.grid.options.pagingTemplate || defaultTemplate;
15580 gridUtil.getTemplate(pagingTemplate)
15581 .then(function (contents) {
15582 var template = angular.element(contents);
15583 $elm.append(template);
15584 uiGridCtrl.innerCompile(template);
15587 post: function ($scope, $elm, $attr, uiGridCtrl) {
15597 * @name ui.grid.paging.directive:uiGridPager
15600 * @description Panel for handling paging
15602 module.directive('uiGridPager', ['uiGridPagingService', 'uiGridConstants', 'gridUtil', 'i18nService',
15603 function (uiGridPagingService, uiGridConstants, gridUtil, i18nService) {
15607 require: '^uiGrid',
15608 link: function ($scope, $elm, $attr, uiGridCtrl) {
15610 $scope.sizesLabel = i18nService.getSafeText('paging.sizes');
15611 $scope.totalItemsLabel = i18nService.getSafeText('paging.totalItems');
15613 var options = $scope.grid.options;
15615 uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
15616 adjustment.height = adjustment.height - gridUtil.elementHeight($elm);
15620 uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
15621 if (!grid.options.useExternalPaging) {
15622 grid.options.totalItems = grid.rows.length;
15624 }, [uiGridConstants.dataChange.ROW]);
15626 var setShowing = function () {
15627 $scope.showingLow = ((options.pagingCurrentPage - 1) * options.pagingPageSize) + 1;
15628 $scope.showingHigh = Math.min(options.pagingCurrentPage * options.pagingPageSize, options.totalItems);
15631 var getMaxPages = function () {
15632 return (options.totalItems === 0) ? 1 : Math.ceil(options.totalItems / options.pagingPageSize);
15635 var deregT = $scope.$watch('grid.options.totalItems + grid.options.pagingPageSize', function () {
15636 $scope.currentMaxPages = getMaxPages();
15641 var deregP = $scope.$watch('grid.options.pagingCurrentPage + grid.options.pagingPageSize', function (newValues, oldValues) {
15642 if (newValues === oldValues) {
15646 if (!angular.isNumber(options.pagingCurrentPage) || options.pagingCurrentPage < 1) {
15647 options.pagingCurrentPage = 1;
15651 if (options.totalItems > 0 && options.pagingCurrentPage > getMaxPages()) {
15652 options.pagingCurrentPage = getMaxPages();
15657 uiGridPagingService.onPagingChanged($scope.grid, options.pagingCurrentPage, options.pagingPageSize);
15661 $scope.$on('$destroy', function() {
15666 $scope.pageForward = function () {
15667 if (options.totalItems > 0) {
15668 options.pagingCurrentPage = Math.min(options.pagingCurrentPage + 1, $scope.currentMaxPages);
15670 options.pagingCurrentPage++;
15674 $scope.pageBackward = function () {
15675 options.pagingCurrentPage = Math.max(options.pagingCurrentPage - 1, 1);
15678 $scope.pageToFirst = function () {
15679 options.pagingCurrentPage = 1;
15682 $scope.pageToLast = function () {
15683 options.pagingCurrentPage = $scope.currentMaxPages;
15686 $scope.cantPageForward = function () {
15687 if (options.totalItems > 0) {
15688 return options.pagingCurrentPage >= $scope.currentMaxPages;
15690 return options.data.length < 1;
15694 $scope.cantPageToLast = function () {
15695 if (options.totalItems > 0) {
15696 return $scope.cantPageForward();
15702 $scope.cantPageBackward = function () {
15703 return options.pagingCurrentPage <= 1;
15715 * @name ui.grid.pinning
15718 * # ui.grid.pinning
15719 * This module provides column pinning to the end user via menu options in the column header
15723 * <div doc-module-components="ui.grid.pinning"></div>
15726 var module = angular.module('ui.grid.pinning', ['ui.grid']);
15728 module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', function (gridUtil, GridRenderContainer, i18nService) {
15731 initializeGrid: function (grid) {
15732 service.defaultGridOptions(grid.options);
15734 // Register a column builder to add new menu items for pinning left and right
15735 grid.registerColumnBuilder(service.pinningColumnBuilder);
15738 defaultGridOptions: function (gridOptions) {
15739 //default option to true unless it was explicitly set to false
15742 * @name ui.grid.pinning.api:GridOptions
15744 * @description GridOptions for pinning feature, these are available to be
15745 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15750 * @name enableRowSelection
15751 * @propertyOf ui.grid.pinning.api:GridOptions
15752 * @description Enable pinning for the entire grid.
15753 * <br/>Defaults to true
15755 gridOptions.enablePinning = gridOptions.enablePinning !== false;
15759 pinningColumnBuilder: function (colDef, col, gridOptions) {
15760 //default to true unless gridOptions or colDef is explicitly false
15764 * @name ui.grid.pinning.api:ColumnDef
15766 * @description ColumnDef for pinning feature, these are available to be
15767 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15772 * @name enablePinning
15773 * @propertyOf ui.grid.pinning.api:ColumnDef
15774 * @description Enable pinning for the individual column.
15775 * <br/>Defaults to true
15777 colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
15783 * @propertyOf ui.grid.pinning.api:ColumnDef
15784 * @description Column is pinned left when grid is rendered
15785 * <br/>Defaults to false
15790 * @name pinnedRight
15791 * @propertyOf ui.grid.pinning.api:ColumnDef
15792 * @description Column is pinned right when grid is rendered
15793 * <br/>Defaults to false
15795 if (colDef.pinnedLeft) {
15796 if (col.width === '*') {
15797 // Need to refresh so the width can be calculated.
15799 .then(function () {
15800 col.renderContainer = 'left';
15801 // Need to calculate the width. If col.drawnWidth is used instead then the width
15802 // will be 100% if it's the first column, 50% if it's the second etc.
15803 col.width = col.grid.canvasWidth / col.grid.columns.length;
15804 col.grid.createLeftContainer();
15808 col.renderContainer = 'left';
15809 col.grid.createLeftContainer();
15812 else if (colDef.pinnedRight) {
15813 if (col.width === '*') {
15814 // Need to refresh so the width can be calculated.
15816 .then(function () {
15817 col.renderContainer = 'right';
15818 // Need to calculate the width. If col.drawnWidth is used instead then the width
15819 // will be 100% if it's the first column, 50% if it's the second etc.
15820 col.width = col.grid.canvasWidth / col.grid.columns.length;
15821 col.grid.createRightContainer();
15825 col.renderContainer = 'right';
15826 col.grid.createRightContainer();
15830 if (!colDef.enablePinning) {
15834 var pinColumnLeftAction = {
15835 name: 'ui.grid.pinning.pinLeft',
15836 title: i18nService.get().pinning.pinLeft,
15837 icon: 'ui-grid-icon-left-open',
15838 shown: function () {
15839 return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
15841 action: function () {
15842 this.context.col.renderContainer = 'left';
15843 this.context.col.width = this.context.col.drawnWidth;
15844 this.context.col.grid.createLeftContainer();
15846 // Need to call refresh twice; once to move our column over to the new render container and then
15847 // a second time to update the grid viewport dimensions with our adjustments
15849 .then(function () {
15850 col.grid.refresh();
15855 var pinColumnRightAction = {
15856 name: 'ui.grid.pinning.pinRight',
15857 title: i18nService.get().pinning.pinRight,
15858 icon: 'ui-grid-icon-right-open',
15859 shown: function () {
15860 return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
15862 action: function () {
15863 this.context.col.renderContainer = 'right';
15864 this.context.col.width = this.context.col.drawnWidth;
15865 this.context.col.grid.createRightContainer();
15868 // Need to call refresh twice; once to move our column over to the new render container and then
15869 // a second time to update the grid viewport dimensions with our adjustments
15871 .then(function () {
15872 col.grid.refresh();
15877 var removePinAction = {
15878 name: 'ui.grid.pinning.unpin',
15879 title: i18nService.get().pinning.unpin,
15880 icon: 'ui-grid-icon-cancel',
15881 shown: function () {
15882 return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
15884 action: function () {
15885 this.context.col.renderContainer = null;
15887 // Need to call refresh twice; once to move our column over to the new render container and then
15888 // a second time to update the grid viewport dimensions with our adjustments
15890 .then(function () {
15891 col.grid.refresh();
15896 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinLeft')) {
15897 col.menuItems.push(pinColumnLeftAction);
15899 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.pinRight')) {
15900 col.menuItems.push(pinColumnRightAction);
15902 if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) {
15903 col.menuItems.push(removePinAction);
15911 module.directive('uiGridPinning', ['gridUtil', 'uiGridPinningService',
15912 function (gridUtil, uiGridPinningService) {
15916 compile: function () {
15918 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
15919 uiGridPinningService.initializeGrid(uiGridCtrl.grid);
15921 post: function ($scope, $elm, $attrs, uiGridCtrl) {
15934 var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
15936 module.constant('columnBounds', {
15941 module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$timeout',
15942 function (gridUtil, $q, $timeout) {
15945 defaultGridOptions: function(gridOptions){
15946 //default option to true unless it was explicitly set to false
15949 * @name ui.grid.resizeColumns.api:GridOptions
15951 * @description GridOptions for resizeColumns feature, these are available to be
15952 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
15957 * @name enableColumnResizing
15958 * @propertyOf ui.grid.resizeColumns.api:GridOptions
15959 * @description Enable column resizing on the entire grid
15960 * <br/>Defaults to true
15962 gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
15965 //use old name if it is explicitly false
15966 if (gridOptions.enableColumnResize === false){
15967 gridOptions.enableColumnResizing = false;
15971 colResizerColumnBuilder: function (colDef, col, gridOptions) {
15976 * @name ui.grid.resizeColumns.api:ColumnDef
15978 * @description ColumnDef for resizeColumns feature, these are available to be
15979 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
15984 * @name enableColumnResizing
15985 * @propertyOf ui.grid.resizeColumns.api:ColumnDef
15986 * @description Enable column resizing on an individual column
15987 * <br/>Defaults to GridOptions.enableColumnResizing
15989 //default to true unless gridOptions or colDef is explicitly false
15990 colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
15993 //legacy support of old option name
15994 if (colDef.enableColumnResize === false){
15995 colDef.enableColumnResizing = false;
15998 return $q.all(promises);
16001 registerPublicApi: function (grid) {
16004 * @name ui.grid.resizeColumns.api:PublicApi
16005 * @description Public Api for column resize feature.
16011 * @name columnSizeChanged
16012 * @eventOf ui.grid.resizeColumns.api:PublicApi
16013 * @description raised when column is resized
16015 * gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
16017 * @param {object} colDef the column that was resized
16018 * @param {integer} delta of the column size change
16021 columnSizeChanged: function (colDef, deltaChange) {
16026 grid.api.registerEventsFromObject(publicApi.events);
16029 fireColumnSizeChanged: function (grid, colDef, deltaChange) {
16030 $timeout(function () {
16031 grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
16043 * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
16047 * Enables resizing for all columns on the grid. If, for some reason, you want to use the ui-grid-resize-columns directive, but not allow column resizing, you can explicitly set the
16048 * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
16051 <doc:example module="app">
16054 var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
16056 app.controller('MainCtrl', ['$scope', function ($scope) {
16057 $scope.gridOpts = {
16059 { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
16060 { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
16061 { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
16062 { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
16068 <div ng-controller="MainCtrl">
16069 <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
16077 module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
16081 require: '^uiGrid',
16083 compile: function () {
16085 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
16086 uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
16087 uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
16088 uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
16090 post: function ($scope, $elm, $attrs, uiGridCtrl) {
16097 // Extend the uiGridHeaderCell directive
16098 module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', function (gridUtil, $templateCache, $compile, $q) {
16100 // Run after the original uiGridHeaderCell
16102 require: '^uiGrid',
16104 compile: function() {
16106 post: function ($scope, $elm, $attrs, uiGridCtrl) {
16107 if (uiGridCtrl.grid.options.enableColumnResizing) {
16108 var renderIndexDefer = $q.defer();
16110 $attrs.$observe('renderIndex', function (n, o) {
16111 $scope.renderIndex = $scope.$eval(n);
16113 renderIndexDefer.resolve();
16116 renderIndexDefer.promise.then(function() {
16117 var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
16119 var resizerLeft = angular.element(columnResizerElm).clone();
16120 var resizerRight = angular.element(columnResizerElm).clone();
16122 resizerLeft.attr('position', 'left');
16123 resizerRight.attr('position', 'right');
16125 var col = $scope.col;
16126 var renderContainer = col.getRenderContainer();
16129 // Get the column to the left of this one
16130 var otherCol = renderContainer.renderedColumns[$scope.renderIndex - 1];
16132 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
16133 if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
16134 $elm.prepend(resizerLeft);
16135 $compile(resizerLeft)($scope);
16138 // Don't append the right resizer if this column has resizing disabled
16139 if ($scope.col.colDef.enableColumnResizing !== false) {
16140 $elm.append(resizerRight);
16141 $compile(resizerRight)($scope);
16155 * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
16160 * Draggable handle that controls column resizing.
16163 <doc:example module="app">
16166 var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
16168 app.controller('MainCtrl', ['$scope', function ($scope) {
16169 $scope.gridOpts = {
16170 enableColumnResizing: true,
16172 { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
16173 { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
16174 { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
16175 { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
16181 <div ng-controller="MainCtrl">
16182 <div class="testGrid" ui-grid="gridOpts"></div>
16186 // TODO: e2e specs?
16187 // TODO: Obey minWidth and maxWIdth;
16189 // TODO: post-resize a horizontal scroll event should be fired
16193 module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'columnBounds', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, columnBounds, uiGridResizeColumnsService) {
16194 var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
16203 require: '?^uiGrid',
16204 link: function ($scope, $elm, $attrs, uiGridCtrl) {
16210 //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
16211 if (uiGridCtrl.grid.isRTL()) {
16212 $scope.position = 'left';
16213 rtlMultiplier = -1;
16216 if ($scope.position === 'left') {
16217 $elm.addClass('left');
16219 else if ($scope.position === 'right') {
16220 $elm.addClass('right');
16223 // Resize all the other columns around col
16224 function resizeAroundColumn(col) {
16225 // Get this column's render container
16226 var renderContainer = col.getRenderContainer();
16228 renderContainer.visibleColumnCache.forEach(function (column) {
16229 // Skip the column we just resized
16230 if (column === col) { return; }
16232 var colDef = column.colDef;
16233 if (!colDef.width || (angular.isString(colDef.width) && (colDef.width.indexOf('*') !== -1 || colDef.width.indexOf('%') !== -1))) {
16234 column.width = column.drawnWidth;
16239 // Build the columns then refresh the grid canvas
16240 // takes an argument representing the diff along the X-axis that the resize had
16241 function buildColumnsAndRefresh(xDiff) {
16242 // Build the columns
16243 uiGridCtrl.grid.buildColumns()
16245 // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
16246 uiGridCtrl.grid.refreshCanvas(true);
16250 function mousemove(event, args) {
16251 if (event.originalEvent) { event = event.originalEvent; }
16252 event.preventDefault();
16254 x = event.clientX - gridLeft;
16256 if (x < 0) { x = 0; }
16257 else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
16259 // The other column to resize (the one next to this one)
16260 var col = $scope.col;
16261 var renderContainer = col.getRenderContainer();
16263 if ($scope.position === 'left') {
16264 // Get the column to the left of this one
16265 col = renderContainer.renderedColumns[$scope.renderIndex - 1];
16266 otherCol = $scope.col;
16268 else if ($scope.position === 'right') {
16269 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16272 // Don't resize if it's disabled on this column
16273 if (col.colDef.enableColumnResizing === false) {
16277 if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
16278 uiGridCtrl.grid.element.addClass('column-resizing');
16281 // Get the diff along the X axis
16282 var xDiff = x - startX;
16284 // Get the width that this mouse would give the column
16285 var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
16287 // If the new width would be less than the column's allowably minimum width, don't allow it
16288 if (col.colDef.minWidth && newWidth < col.colDef.minWidth) {
16289 x = x + (col.colDef.minWidth - newWidth) * rtlMultiplier;
16291 else if (!col.colDef.minWidth && columnBounds.minWidth && newWidth < columnBounds.minWidth) {
16292 x = x + (col.colDef.minWidth - newWidth);
16294 else if (col.colDef.maxWidth && newWidth > col.colDef.maxWidth) {
16295 x = x + (col.colDef.maxWidth - newWidth) * rtlMultiplier;
16298 resizeOverlay.css({ left: x + 'px' });
16300 uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
16303 function mouseup(event, args) {
16304 if (event.originalEvent) { event = event.originalEvent; }
16305 event.preventDefault();
16307 uiGridCtrl.grid.element.removeClass('column-resizing');
16309 resizeOverlay.remove();
16311 // Resize the column
16312 x = event.clientX - gridLeft;
16313 var xDiff = x - startX;
16316 $document.off('mouseup', mouseup);
16317 $document.off('mousemove', mousemove);
16321 // The other column to resize (the one next to this one)
16322 var col = $scope.col;
16323 var renderContainer = col.getRenderContainer();
16326 if ($scope.position === 'left') {
16327 // Get the column to the left of this one
16328 col = renderContainer.renderedColumns[$scope.renderIndex - 1];
16329 otherCol = $scope.col;
16331 else if ($scope.position === 'right') {
16332 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16335 // Don't resize if it's disabled on this column
16336 if (col.colDef.enableColumnResizing === false) {
16340 // Get the new width
16341 var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
16343 // If the new width is less than the minimum width, make it the minimum width
16344 if (col.colDef.minWidth && newWidth < col.colDef.minWidth) {
16345 newWidth = col.colDef.minWidth;
16347 else if (!col.colDef.minWidth && columnBounds.minWidth && newWidth < columnBounds.minWidth) {
16348 newWidth = columnBounds.minWidth;
16351 if (col.colDef.maxWidth && newWidth > col.colDef.maxWidth) {
16352 newWidth = col.colDef.maxWidth;
16355 col.width = newWidth;
16357 // All other columns because fixed to their drawn width, if they aren't already
16358 resizeAroundColumn(col);
16360 buildColumnsAndRefresh(xDiff);
16362 uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
16364 $document.off('mouseup', mouseup);
16365 $document.off('mousemove', mousemove);
16368 $elm.on('mousedown', function(event, args) {
16369 if (event.originalEvent) { event = event.originalEvent; }
16370 event.stopPropagation();
16372 // Get the left offset of the grid
16373 // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
16374 gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
16376 // Get the starting X position, which is the X coordinate of the click minus the grid's offset
16377 startX = event.clientX - gridLeft;
16379 // Append the resizer overlay
16380 uiGridCtrl.grid.element.append(resizeOverlay);
16382 // Place the resizer overlay at the start position
16383 resizeOverlay.css({ left: startX });
16385 // Add handlers for mouse move and up events
16386 $document.on('mouseup', mouseup);
16387 $document.on('mousemove', mousemove);
16390 // On doubleclick, resize to fit all rendered cells
16391 $elm.on('dblclick', function(event, args) {
16392 event.stopPropagation();
16394 var col = $scope.col;
16395 var renderContainer = col.getRenderContainer();
16397 var otherCol, multiplier;
16399 // If we're the left-positioned resizer then we need to resize the column to the left of our column, and not our column itself
16400 if ($scope.position === 'left') {
16401 col = renderContainer.renderedColumns[$scope.renderIndex - 1];
16402 otherCol = $scope.col;
16405 else if ($scope.position === 'right') {
16406 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16407 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
16411 // Go through the rendered rows and find out the max size for the data in this column
16415 // Get the parent render container element
16416 var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
16418 // Get the cell contents so we measure correctly. For the header cell we have to account for the sort icon and the menu buttons, if present
16419 var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
16420 Array.prototype.forEach.call(cells, function (cell) {
16421 // Get the cell width
16422 // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
16424 // Account for the menu button if it exists
16426 if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
16427 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
16430 gridUtil.fakeElement(cell, {}, function(newElm) {
16431 // Make the element float since it's a div and can expand to fill its container
16432 var e = angular.element(newElm);
16433 e.attr('style', 'float: left');
16435 var width = gridUtil.elementWidth(e);
16438 var menuButtonWidth = gridUtil.elementWidth(menuButton);
16439 width = width + menuButtonWidth;
16442 if (width > maxWidth) {
16444 xDiff = maxWidth - width;
16449 // If the new width is less than the minimum width, make it the minimum width
16450 if (col.colDef.minWidth && maxWidth < col.colDef.minWidth) {
16451 maxWidth = col.colDef.minWidth;
16453 else if (!col.colDef.minWidth && columnBounds.minWidth && maxWidth < columnBounds.minWidth) {
16454 maxWidth = columnBounds.minWidth;
16457 if (col.colDef.maxWidth && maxWidth > col.colDef.maxWidth) {
16458 maxWidth = col.colDef.maxWidth;
16461 col.width = parseInt(maxWidth, 10);
16463 // All other columns because fixed to their drawn width, if they aren't already
16464 resizeAroundColumn(col);
16466 buildColumnsAndRefresh(xDiff);
16468 uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
16471 $elm.on('$destroy', function() {
16472 $elm.off('mousedown');
16473 $elm.off('dblclick');
16474 $document.off('mousemove', mousemove);
16475 $document.off('mouseup', mouseup);
16489 * @name ui.grid.rowEdit
16492 * # ui.grid.rowEdit
16493 * This module extends the edit feature to provide tracking and saving of rows
16494 * of data. The tutorial provides more information on how this feature is best
16495 * used {@link tutorial/205_row_editable here}.
16497 * This feature depends on usage of the ui-grid-edit feature, and also benefits
16498 * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
16503 var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
16507 * @name ui.grid.rowEdit.constant:uiGridRowEditConstants
16509 * @description constants available in row edit module
16511 module.constant('uiGridRowEditConstants', {
16516 * @name ui.grid.rowEdit.service:uiGridRowEditService
16518 * @description Services for row editing features
16520 module.service('uiGridRowEditService', ['$interval', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
16521 function ($interval, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
16525 initializeGrid: function (scope, grid) {
16528 * @name ui.grid.rowEdit.api:PublicApi
16530 * @description Public Api for rowEdit feature
16540 * @eventOf ui.grid.rowEdit.api:PublicApi
16542 * @description raised when a row is ready for saving. Once your
16543 * row has saved you may need to use angular.extend to update the
16544 * data entity with any changed data from your save (for example,
16545 * lock version information if you're using optimistic locking,
16546 * or last update time/user information).
16548 * Your method should call setSavePromise somewhere in the body before
16549 * returning control. The feature will then wait, with the gridRow greyed out
16550 * whilst this promise is being resolved.
16553 * gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
16555 * and somewhere within the event handler:
16557 * gridApi.rowEdit.setSavePromise( grid, rowEntity, savePromise)
16559 * @param {object} rowEntity the options.data element that was edited
16560 * @returns {promise} Your saveRow method should return a promise, the
16561 * promise should either be resolved (implying successful save), or
16562 * rejected (implying an error).
16564 saveRow: function (rowEntity) {
16572 * @methodOf ui.grid.rowEdit.api:PublicApi
16573 * @name setSavePromise
16574 * @description Sets the promise associated with the row save, mandatory that
16575 * the saveRow event handler calls this method somewhere before returning.
16577 * gridApi.rowEdit.setSavePromise(grid, rowEntity)
16579 * @param {object} grid the grid for which dirty rows should be returned
16580 * @param {object} rowEntity a data row from the grid for which a save has
16582 * @param {promise} savePromise the promise that will be resolved when the
16583 * save is successful, or rejected if the save fails
16586 setSavePromise: function (grid, rowEntity, savePromise) {
16587 service.setSavePromise(grid, rowEntity, savePromise);
16591 * @methodOf ui.grid.rowEdit.api:PublicApi
16592 * @name getDirtyRows
16593 * @description Returns all currently dirty rows
16595 * gridApi.rowEdit.getDirtyRows(grid)
16597 * @param {object} grid the grid for which dirty rows should be returned
16598 * @returns {array} An array of gridRows that are currently dirty
16601 getDirtyRows: function (grid) {
16602 return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : [];
16606 * @methodOf ui.grid.rowEdit.api:PublicApi
16607 * @name getErrorRows
16608 * @description Returns all currently errored rows
16610 * gridApi.rowEdit.getErrorRows(grid)
16612 * @param {object} grid the grid for which errored rows should be returned
16613 * @returns {array} An array of gridRows that are currently in error
16616 getErrorRows: function (grid) {
16617 return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : [];
16621 * @methodOf ui.grid.rowEdit.api:PublicApi
16622 * @name flushDirtyRows
16623 * @description Triggers a save event for all currently dirty rows, could
16624 * be used where user presses a save button or navigates away from the page
16626 * gridApi.rowEdit.flushDirtyRows(grid)
16628 * @param {object} grid the grid for which dirty rows should be flushed
16629 * @returns {promise} a promise that represents the aggregate of all
16630 * of the individual save promises - i.e. it will be resolved when all
16631 * the individual save promises have been resolved.
16634 flushDirtyRows: function (grid) {
16635 return service.flushDirtyRows(grid);
16640 * @methodOf ui.grid.rowEdit.api:PublicApi
16641 * @name setRowsDirty
16642 * @description Sets each of the rows passed in dataRows
16643 * to be dirty. note that if you have only just inserted the
16644 * rows into your data you will need to wait for a $digest cycle
16645 * before the gridRows are present - so often you would wrap this
16646 * call in a $interval or $timeout
16648 * $interval( function() {
16649 * gridApi.rowEdit.setRowsDirty(grid, myDataRows);
16652 * @param {object} grid the grid for which rows should be set dirty
16653 * @param {array} dataRows the data entities for which the gridRows
16654 * should be set dirty.
16657 setRowsDirty: function (grid, dataRows) {
16658 service.setRowsDirty(grid, dataRows);
16664 grid.api.registerEventsFromObject(publicApi.events);
16665 grid.api.registerMethodsFromObject(publicApi.methods);
16667 grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
16668 grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
16669 grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
16670 grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
16672 if ( grid.api.cellNav ) {
16673 grid.api.cellNav.on.navigate( scope, service.navigate );
16679 defaultGridOptions: function (gridOptions) {
16683 * @name ui.grid.rowEdit.api:GridOptions
16685 * @description Options for configuring the rowEdit feature, these are available to be
16686 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
16694 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16696 * @description Returns a function that saves the specified row from the grid,
16697 * and returns a promise
16698 * @param {object} grid the grid for which dirty rows should be flushed
16699 * @param {GridRow} gridRow the row that should be saved
16700 * @returns {function} the saveRow function returns a function. That function
16701 * in turn, when called, returns a promise relating to the save callback
16703 saveRow: function ( grid, gridRow ) {
16706 return function() {
16707 gridRow.isSaving = true;
16709 var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
16711 if ( gridRow.rowEditSavePromise ){
16712 gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
16714 gridUtil.logError( 'A promise was not returned when saveRow event was raised, either nobody is listening to event, or event handler did not return a promise' );
16723 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16724 * @name setSavePromise
16725 * @description Sets the promise associated with the row save, mandatory that
16726 * the saveRow event handler calls this method somewhere before returning.
16728 * gridApi.rowEdit.setSavePromise(grid, rowEntity)
16730 * @param {object} grid the grid for which dirty rows should be returned
16731 * @param {object} rowEntity a data row from the grid for which a save has
16733 * @param {promise} savePromise the promise that will be resolved when the
16734 * save is successful, or rejected if the save fails
16737 setSavePromise: function (grid, rowEntity, savePromise) {
16738 var gridRow = grid.getRow( rowEntity );
16739 gridRow.rowEditSavePromise = savePromise;
16745 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16746 * @name processSuccessPromise
16747 * @description Returns a function that processes the successful
16748 * resolution of a save promise
16749 * @param {object} grid the grid for which the promise should be processed
16750 * @param {GridRow} gridRow the row that has been saved
16751 * @returns {function} the success handling function
16753 processSuccessPromise: function ( grid, gridRow ) {
16756 return function() {
16757 delete gridRow.isSaving;
16758 delete gridRow.isDirty;
16759 delete gridRow.isError;
16760 delete gridRow.rowEditSaveTimer;
16761 self.removeRow( grid.rowEdit.errorRows, gridRow );
16762 self.removeRow( grid.rowEdit.dirtyRows, gridRow );
16769 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16770 * @name processErrorPromise
16771 * @description Returns a function that processes the failed
16772 * resolution of a save promise
16773 * @param {object} grid the grid for which the promise should be processed
16774 * @param {GridRow} gridRow the row that is now in error
16775 * @returns {function} the error handling function
16777 processErrorPromise: function ( grid, gridRow ) {
16778 return function() {
16779 delete gridRow.isSaving;
16780 delete gridRow.rowEditSaveTimer;
16782 gridRow.isError = true;
16784 if (!grid.rowEdit.errorRows){
16785 grid.rowEdit.errorRows = [];
16787 if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){
16788 grid.rowEdit.errorRows.push( gridRow );
16796 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16798 * @description Removes a row from a cache of rows - either
16799 * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows. If the row
16800 * is not present silently does nothing.
16801 * @param {array} rowArray the array from which to remove the row
16802 * @param {GridRow} gridRow the row that should be removed
16804 removeRow: function( rowArray, removeGridRow ){
16805 angular.forEach( rowArray, function( gridRow, index ){
16806 if ( gridRow.uid === removeGridRow.uid ){
16807 rowArray.splice( index, 1);
16815 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16816 * @name isRowPresent
16817 * @description Checks whether a row is already present
16818 * in the given array
16819 * @param {array} rowArray the array in which to look for the row
16820 * @param {GridRow} gridRow the row that should be looked for
16822 isRowPresent: function( rowArray, removeGridRow ){
16823 var present = false;
16824 angular.forEach( rowArray, function( gridRow, index ){
16825 if ( gridRow.uid === removeGridRow.uid ){
16835 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16836 * @name flushDirtyRows
16837 * @description Triggers a save event for all currently dirty rows, could
16838 * be used where user presses a save button or navigates away from the page
16840 * gridApi.rowEdit.flushDirtyRows(grid)
16842 * @param {object} grid the grid for which dirty rows should be flushed
16843 * @returns {promise} a promise that represents the aggregate of all
16844 * of the individual save promises - i.e. it will be resolved when all
16845 * the individual save promises have been resolved.
16848 flushDirtyRows: function(grid){
16850 angular.forEach(grid.rowEdit.dirtyRows, function( gridRow ){
16851 service.saveRow( grid, gridRow )();
16852 promises.push( gridRow.rowEditSavePromise );
16855 return $q.all( promises );
16861 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16862 * @name endEditCell
16863 * @description Receives an afterCellEdit event from the edit function,
16864 * and sets flags as appropriate. Only the rowEntity parameter
16865 * is processed, although other params are available. Grid
16866 * is automatically provided by the gridApi.
16867 * @param {object} rowEntity the data entity for which the cell
16870 endEditCell: function( rowEntity, colDef, newValue, previousValue ){
16871 var grid = this.grid;
16872 var gridRow = grid.getRow( rowEntity );
16873 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
16875 if ( newValue !== previousValue || gridRow.isDirty ){
16876 if ( !grid.rowEdit.dirtyRows ){
16877 grid.rowEdit.dirtyRows = [];
16880 if ( !gridRow.isDirty ){
16881 gridRow.isDirty = true;
16882 grid.rowEdit.dirtyRows.push( gridRow );
16885 delete gridRow.isError;
16887 service.considerSetTimer( grid, gridRow );
16894 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16895 * @name beginEditCell
16896 * @description Receives a beginCellEdit event from the edit function,
16897 * and cancels any rowEditSaveTimers if present, as the user is still editing
16898 * this row. Only the rowEntity parameter
16899 * is processed, although other params are available. Grid
16900 * is automatically provided by the gridApi.
16901 * @param {object} rowEntity the data entity for which the cell
16902 * editing has commenced
16904 beginEditCell: function( rowEntity, colDef ){
16905 var grid = this.grid;
16906 var gridRow = grid.getRow( rowEntity );
16907 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
16909 service.cancelTimer( grid, gridRow );
16915 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16916 * @name cancelEditCell
16917 * @description Receives a cancelCellEdit event from the edit function,
16918 * and if the row was already dirty, restarts the save timer. If the row
16919 * was not already dirty, then it's not dirty now either and does nothing.
16921 * Only the rowEntity parameter
16922 * is processed, although other params are available. Grid
16923 * is automatically provided by the gridApi.
16925 * @param {object} rowEntity the data entity for which the cell
16926 * editing was cancelled
16928 cancelEditCell: function( rowEntity, colDef ){
16929 var grid = this.grid;
16930 var gridRow = grid.getRow( rowEntity );
16931 if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
16933 service.considerSetTimer( grid, gridRow );
16939 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16941 * @description cellNav tells us that the selected cell has changed. If
16942 * the new row had a timer running, then stop it similar to in a beginCellEdit
16943 * call. If the old row is dirty and not the same as the new row, then
16944 * start a timer on it.
16945 * @param {object} newRowCol the row and column that were selected
16946 * @param {object} oldRowCol the row and column that was left
16949 navigate: function( newRowCol, oldRowCol ){
16950 var grid = this.grid;
16951 if ( newRowCol.row.rowEditSaveTimer ){
16952 service.cancelTimer( grid, newRowCol.row );
16955 if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
16956 service.considerSetTimer( grid, oldRowCol.row );
16963 * @propertyOf ui.grid.rowEdit.api:GridOptions
16964 * @name rowEditWaitInterval
16965 * @description How long the grid should wait for another change on this row
16966 * before triggering a save (in milliseconds). If set to -1, then saves are
16967 * never triggered by timer (implying that the user will call flushDirtyRows()
16971 * Setting the wait interval to 4 seconds
16973 * $scope.gridOptions = { rowEditWaitInterval: 4000 }
16979 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
16980 * @name considerSetTimer
16981 * @description Consider setting a timer on this row (if it is dirty). if there is a timer running
16982 * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
16983 * dirty and not currently saving then set a new timer
16984 * @param {object} grid the grid for which we are processing
16985 * @param {GridRow} gridRow the row for which the timer should be adjusted
16988 considerSetTimer: function( grid, gridRow ){
16989 service.cancelTimer( grid, gridRow );
16991 if ( gridRow.isDirty && !gridRow.isSaving ){
16992 if ( grid.options.rowEditWaitInterval !== -1 ){
16993 var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
16994 gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
17002 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
17003 * @name cancelTimer
17004 * @description cancel the $interval for any timer running on this row
17005 * then delete the timer itself
17006 * @param {object} grid the grid for which we are processing
17007 * @param {GridRow} gridRow the row for which the timer should be adjusted
17010 cancelTimer: function( grid, gridRow ){
17011 if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
17012 $interval.cancel(gridRow.rowEditSaveTimer);
17013 delete gridRow.rowEditSaveTimer;
17020 * @methodOf ui.grid.rowEdit.api:PublicApi
17021 * @name setRowsDirty
17022 * @description Sets each of the rows passed in dataRows
17023 * to be dirty. note that if you have only just inserted the
17024 * rows into your data you will need to wait for a $digest cycle
17025 * before the gridRows are present - so often you would wrap this
17026 * call in a $interval or $timeout
17028 * $interval( function() {
17029 * gridApi.rowEdit.setRowsDirty(grid, myDataRows);
17032 * @param {object} grid the grid for which rows should be set dirty
17033 * @param {array} dataRows the data entities for which the gridRows
17034 * should be set dirty.
17037 setRowsDirty: function( grid, myDataRows ) {
17039 myDataRows.forEach( function( value, index ){
17040 gridRow = grid.getRow( value );
17042 if ( !grid.rowEdit.dirtyRows ){
17043 grid.rowEdit.dirtyRows = [];
17046 if ( !gridRow.isDirty ){
17047 gridRow.isDirty = true;
17048 grid.rowEdit.dirtyRows.push( gridRow );
17051 delete gridRow.isError;
17053 service.considerSetTimer( grid, gridRow );
17055 gridUtil.logError( "requested row not found in rowEdit.setRowsDirty, row was: " + value );
17069 * @name ui.grid.rowEdit.directive:uiGridEdit
17073 * @description Adds row editing features to the ui-grid-edit directive.
17076 module.directive('uiGridRowEdit', ['gridUtil', 'uiGridRowEditService', 'uiGridEditConstants',
17077 function (gridUtil, uiGridRowEditService, uiGridEditConstants) {
17081 require: '^uiGrid',
17083 compile: function () {
17085 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17086 uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
17088 post: function ($scope, $elm, $attrs, uiGridCtrl) {
17098 * @name ui.grid.rowEdit.directive:uiGridViewport
17101 * @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
17102 * for the grid row to allow coloring of saving and error rows
17104 module.directive('uiGridViewport',
17105 ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
17106 function ($compile, uiGridConstants, gridUtil, $parse) {
17108 priority: -200, // run after default directive
17110 compile: function ($elm, $attrs) {
17111 var rowRepeatDiv = angular.element($elm.children().children()[0]);
17113 var existingNgClass = rowRepeatDiv.attr("ng-class");
17114 var newNgClass = '';
17115 if ( existingNgClass ) {
17116 newNgClass = existingNgClass.slice(0, -1) + ", 'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
17118 newNgClass = "{'ui-grid-row-dirty': row.isDirty, 'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}";
17120 rowRepeatDiv.attr("ng-class", newNgClass);
17123 pre: function ($scope, $elm, $attrs, controllers) {
17126 post: function ($scope, $elm, $attrs, controllers) {
17140 * @name ui.grid.selection
17143 * # ui.grid.selection
17144 * This module provides row selection
17148 * <div doc-module-components="ui.grid.selection"></div>
17151 var module = angular.module('ui.grid.selection', ['ui.grid']);
17155 * @name ui.grid.selection.constant:uiGridSelectionConstants
17157 * @description constants available in selection module
17159 module.constant('uiGridSelectionConstants', {
17160 featureName: "selection",
17161 selectionRowHeaderColName: 'selectionRowHeaderCol'
17166 * @name ui.grid.selection.service:uiGridSelectionService
17168 * @description Services for selection features
17170 module.service('uiGridSelectionService', ['$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
17171 function ($q, $templateCache, uiGridSelectionConstants, gridUtil) {
17175 initializeGrid: function (grid) {
17177 //add feature namespace and any properties to grid for needed state
17178 grid.selection = {};
17179 grid.selection.lastSelectedRow = null;
17180 grid.selection.selectAll = false;
17182 service.defaultGridOptions(grid.options);
17186 * @name ui.grid.selection.api:PublicApi
17188 * @description Public Api for selection feature
17195 * @name rowSelectionChanged
17196 * @eventOf ui.grid.selection.api:PublicApi
17197 * @description is raised after the row.isSelected state is changed
17198 * @param {GridRow} row the row that was selected/deselected
17200 rowSelectionChanged: function (scope, row) {
17204 * @name rowSelectionChangedBatch
17205 * @eventOf ui.grid.selection.api:PublicApi
17206 * @description is raised after the row.isSelected state is changed
17207 * in bulk, if the `enableSelectionBatchEvent` option is set to true
17208 * (which it is by default). This allows more efficient processing
17210 * @param {array} rows the rows that were selected/deselected
17212 rowSelectionChangedBatch: function (scope, rows) {
17220 * @name toggleRowSelection
17221 * @methodOf ui.grid.selection.api:PublicApi
17222 * @description Toggles data row as selected or unselected
17223 * @param {object} rowEntity gridOptions.data[] array instance
17225 toggleRowSelection: function (rowEntity) {
17226 var row = grid.getRow(rowEntity);
17227 if (row !== null) {
17228 service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17234 * @methodOf ui.grid.selection.api:PublicApi
17235 * @description Select the data row
17236 * @param {object} rowEntity gridOptions.data[] array instance
17238 selectRow: function (rowEntity) {
17239 var row = grid.getRow(rowEntity);
17240 if (row !== null && !row.isSelected) {
17241 service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17246 * @name selectRowByVisibleIndex
17247 * @methodOf ui.grid.selection.api:PublicApi
17248 * @description Select the specified row by visible index (i.e. if you
17249 * specify row 0 you'll get the first visible row selected). In this context
17250 * visible means of those rows that are theoretically visible (i.e. not filtered),
17251 * rather than rows currently rendered on the screen.
17252 * @param {number} index index within the rowsVisible array
17254 selectRowByVisibleIndex: function ( rowNum ) {
17255 var row = grid.renderContainers.body.visibleRowCache[rowNum];
17256 if (row !== null && !row.isSelected) {
17257 service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17262 * @name unSelectRow
17263 * @methodOf ui.grid.selection.api:PublicApi
17264 * @description UnSelect the data row
17265 * @param {object} rowEntity gridOptions.data[] array instance
17267 unSelectRow: function (rowEntity) {
17268 var row = grid.getRow(rowEntity);
17269 if (row !== null && row.isSelected) {
17270 service.toggleRowSelection(grid, row, grid.options.multiSelect, grid.options.noUnselect);
17275 * @name selectAllRows
17276 * @methodOf ui.grid.selection.api:PublicApi
17277 * @description Selects all rows. Does nothing if multiSelect = false
17279 selectAllRows: function () {
17280 if (grid.options.multiSelect === false) {
17284 var changedRows = [];
17285 grid.rows.forEach(function (row) {
17286 if ( !row.isSelected ){
17287 row.isSelected = true;
17288 service.decideRaiseSelectionEvent( grid, row, changedRows );
17291 service.decideRaiseSelectionBatchEvent( grid, changedRows );
17295 * @name selectAllVisibleRows
17296 * @methodOf ui.grid.selection.api:PublicApi
17297 * @description Selects all visible rows. Does nothing if multiSelect = false
17299 selectAllVisibleRows: function () {
17300 if (grid.options.multiSelect === false) {
17304 var changedRows = [];
17305 grid.rows.forEach(function (row) {
17307 if (!row.isSelected){
17308 row.isSelected = true;
17309 service.decideRaiseSelectionEvent( grid, row, changedRows );
17312 if (row.isSelected){
17313 row.isSelected = false;
17314 service.decideRaiseSelectionEvent( grid, row, changedRows );
17318 service.decideRaiseSelectionBatchEvent( grid, changedRows );
17322 * @name clearSelectedRows
17323 * @methodOf ui.grid.selection.api:PublicApi
17324 * @description Unselects all rows
17326 clearSelectedRows: function () {
17327 service.clearSelectedRows(grid);
17331 * @name getSelectedRows
17332 * @methodOf ui.grid.selection.api:PublicApi
17333 * @description returns all selectedRow's entity references
17335 getSelectedRows: function () {
17336 return service.getSelectedRows(grid).map(function (gridRow) {
17337 return gridRow.entity;
17342 * @name getSelectedGridRows
17343 * @methodOf ui.grid.selection.api:PublicApi
17344 * @description returns all selectedRow's as gridRows
17346 getSelectedGridRows: function () {
17347 return service.getSelectedRows(grid);
17351 * @name setMultiSelect
17352 * @methodOf ui.grid.selection.api:PublicApi
17353 * @description Sets the current gridOption.multiSelect to true or false
17354 * @param {bool} multiSelect true to allow multiple rows
17356 setMultiSelect: function (multiSelect) {
17357 grid.options.multiSelect = multiSelect;
17361 * @name setModifierKeysToMultiSelect
17362 * @methodOf ui.grid.selection.api:PublicApi
17363 * @description Sets the current gridOption.modifierKeysToMultiSelect to true or false
17364 * @param {bool} modifierKeysToMultiSelect true to only allow multiple rows when using ctrlKey or shiftKey is used
17366 setModifierKeysToMultiSelect: function (modifierKeysToMultiSelect) {
17367 grid.options.modifierKeysToMultiSelect = modifierKeysToMultiSelect;
17371 * @name getSelectAllState
17372 * @methodOf ui.grid.selection.api:PublicApi
17373 * @description Returns whether or not the selectAll checkbox is currently ticked. The
17374 * grid doesn't automatically select rows when you add extra data - so when you add data
17375 * you need to explicitly check whether the selectAll is set, and then call setVisible rows
17378 getSelectAllState: function () {
17379 return grid.selection.selectAll;
17386 grid.api.registerEventsFromObject(publicApi.events);
17388 grid.api.registerMethodsFromObject(publicApi.methods);
17392 defaultGridOptions: function (gridOptions) {
17393 //default option to true unless it was explicitly set to false
17396 * @name ui.grid.selection.api:GridOptions
17398 * @description GridOptions for selection feature, these are available to be
17399 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
17404 * @name enableRowSelection
17405 * @propertyOf ui.grid.selection.api:GridOptions
17406 * @description Enable row selection for entire grid.
17407 * <br/>Defaults to true
17409 gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
17412 * @name multiSelect
17413 * @propertyOf ui.grid.selection.api:GridOptions
17414 * @description Enable multiple row selection for entire grid
17415 * <br/>Defaults to true
17417 gridOptions.multiSelect = gridOptions.multiSelect !== false;
17421 * @propertyOf ui.grid.selection.api:GridOptions
17422 * @description Prevent a row from being unselected. Works in conjunction
17423 * with `multiselect = false` and `gridApi.selection.selectRow()` to allow
17424 * you to create a single selection only grid - a row is always selected, you
17425 * can only select different rows, you can't unselect the row.
17426 * <br/>Defaults to false
17428 gridOptions.noUnselect = gridOptions.noUnselect === true;
17431 * @name modifierKeysToMultiSelect
17432 * @propertyOf ui.grid.selection.api:GridOptions
17433 * @description Enable multiple row selection only when using the ctrlKey or shiftKey. Requires multiSelect to be true.
17434 * <br/>Defaults to false
17436 gridOptions.modifierKeysToMultiSelect = gridOptions.modifierKeysToMultiSelect === true;
17439 * @name enableRowHeaderSelection
17440 * @propertyOf ui.grid.selection.api:GridOptions
17441 * @description Enable a row header to be used for selection
17442 * <br/>Defaults to true
17444 gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
17447 * @name enableSelectAll
17448 * @propertyOf ui.grid.selection.api:GridOptions
17449 * @description Enable the select all checkbox at the top of the selectionRowHeader
17450 * <br/>Defaults to true
17452 gridOptions.enableSelectAll = gridOptions.enableSelectAll !== false;
17455 * @name enableSelectionBatchEvent
17456 * @propertyOf ui.grid.selection.api:GridOptions
17457 * @description If selected rows are changed in bulk, either via the API or
17458 * via the selectAll checkbox, then a separate event is fired. Setting this
17459 * option to false will cause the rowSelectionChanged event to be called multiple times
17461 * <br/>Defaults to true
17463 gridOptions.enableSelectionBatchEvent = gridOptions.enableSelectionBatchEvent !== false;
17466 * @name selectionRowHeaderWidth
17467 * @propertyOf ui.grid.selection.api:GridOptions
17468 * @description can be used to set a custom width for the row header selection column
17469 * <br/>Defaults to 30px
17471 gridOptions.selectionRowHeaderWidth = angular.isDefined(gridOptions.selectionRowHeaderWidth) ? gridOptions.selectionRowHeaderWidth : 30;
17476 * @name toggleRowSelection
17477 * @methodOf ui.grid.selection.service:uiGridSelectionService
17478 * @description Toggles row as selected or unselected
17479 * @param {Grid} grid grid object
17480 * @param {GridRow} row row to select or deselect
17481 * @param {bool} multiSelect if false, only one row at time can be selected
17482 * @param {bool} noUnselect if true then rows cannot be unselected
17484 toggleRowSelection: function (grid, row, multiSelect, noUnselect) {
17485 var selected = row.isSelected;
17487 if (!multiSelect && !selected) {
17488 service.clearSelectedRows(grid);
17489 } else if (!multiSelect && selected) {
17490 var selectedRows = service.getSelectedRows(grid);
17491 if (selectedRows.length > 1) {
17492 selected = false; // Enable reselect of the row
17493 service.clearSelectedRows(grid);
17497 if (selected && noUnselect){
17498 // don't deselect the row
17500 row.isSelected = !selected;
17501 if (row.isSelected === true) {
17502 grid.selection.lastSelectedRow = row;
17504 grid.api.selection.raise.rowSelectionChanged(row);
17509 * @name shiftSelect
17510 * @methodOf ui.grid.selection.service:uiGridSelectionService
17511 * @description selects a group of rows from the last selected row using the shift key
17512 * @param {Grid} grid grid object
17513 * @param {GridRow} clicked row
17514 * @param {bool} multiSelect if false, does nothing this is for multiSelect only
17516 shiftSelect: function (grid, row, multiSelect) {
17517 if (!multiSelect) {
17520 var selectedRows = service.getSelectedRows(grid);
17521 var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
17522 var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
17523 //reverse select direction
17524 if (fromRow > toRow) {
17530 var changedRows = [];
17531 for (var i = fromRow; i <= toRow; i++) {
17532 var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
17534 if ( !rowToSelect.isSelected ){
17535 rowToSelect.isSelected = true;
17536 grid.selection.lastSelectedRow = rowToSelect;
17537 service.decideRaiseSelectionEvent( grid, rowToSelect, changedRows );
17541 service.decideRaiseSelectionBatchEvent( grid, changedRows );
17545 * @name getSelectedRows
17546 * @methodOf ui.grid.selection.service:uiGridSelectionService
17547 * @description Returns all the selected rows
17548 * @param {Grid} grid grid object
17550 getSelectedRows: function (grid) {
17551 return grid.rows.filter(function (row) {
17552 return row.isSelected;
17558 * @name clearSelectedRows
17559 * @methodOf ui.grid.selection.service:uiGridSelectionService
17560 * @description Clears all selected rows
17561 * @param {Grid} grid grid object
17563 clearSelectedRows: function (grid) {
17564 var changedRows = [];
17565 service.getSelectedRows(grid).forEach(function (row) {
17566 if ( row.isSelected ){
17567 row.isSelected = false;
17568 service.decideRaiseSelectionEvent( grid, row, changedRows );
17571 service.decideRaiseSelectionBatchEvent( grid, changedRows );
17576 * @name decideRaiseSelectionEvent
17577 * @methodOf ui.grid.selection.service:uiGridSelectionService
17578 * @description Decides whether to raise a single event or a batch event
17579 * @param {Grid} grid grid object
17580 * @param {GridRow} row row that has changed
17581 * @param {array} changedRows an array to which we can append the changed
17582 * row if we're doing batch events
17584 decideRaiseSelectionEvent: function( grid, row, changedRows ){
17585 if ( !grid.options.enableSelectionBatchEvent ){
17586 grid.api.selection.raise.rowSelectionChanged(row);
17588 changedRows.push(row);
17594 * @name raiseSelectionEvent
17595 * @methodOf ui.grid.selection.service:uiGridSelectionService
17596 * @description Decides whether we need to raise a batch event, and
17597 * raises it if we do.
17598 * @param {Grid} grid grid object
17599 * @param {array} changedRows an array of changed rows, only populated
17600 * if we're doing batch events
17602 decideRaiseSelectionBatchEvent: function( grid, changedRows ){
17603 if ( changedRows.length > 0 ){
17604 grid.api.selection.raise.rowSelectionChangedBatch(changedRows);
17615 * @name ui.grid.selection.directive:uiGridSelection
17619 * @description Adds selection features to grid
17622 <example module="app">
17623 <file name="app.js">
17624 var app = angular.module('app', ['ui.grid', 'ui.grid.selection']);
17626 app.controller('MainCtrl', ['$scope', function ($scope) {
17628 { name: 'Bob', title: 'CEO' },
17629 { name: 'Frank', title: 'Lowly Developer' }
17632 $scope.columnDefs = [
17633 {name: 'name', enableCellEdit: true},
17634 {name: 'title', enableCellEdit: true}
17638 <file name="index.html">
17639 <div ng-controller="MainCtrl">
17640 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
17645 module.directive('uiGridSelection', ['uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache',
17646 function (uiGridSelectionConstants, uiGridSelectionService, $templateCache) {
17650 require: '^uiGrid',
17652 compile: function () {
17654 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
17655 uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
17656 if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
17657 var selectionRowHeaderDef = {
17658 name: uiGridSelectionConstants.selectionRowHeaderColName,
17660 width: uiGridCtrl.grid.options.selectionRowHeaderWidth,
17661 cellTemplate: 'ui-grid/selectionRowHeader',
17662 headerCellTemplate: 'ui-grid/selectionHeaderCell',
17663 enableColumnResizing: false,
17664 enableColumnMenu: false
17667 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef);
17670 post: function ($scope, $elm, $attrs, uiGridCtrl) {
17678 module.directive('uiGridSelectionRowHeaderButtons', ['$templateCache', 'uiGridSelectionService',
17679 function ($templateCache, uiGridSelectionService) {
17683 template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
17685 require: '^uiGrid',
17686 link: function($scope, $elm, $attrs, uiGridCtrl) {
17687 var self = uiGridCtrl.grid;
17688 $scope.selectButtonClick = function(row, evt) {
17689 if (evt.shiftKey) {
17690 uiGridSelectionService.shiftSelect(self, row, self.options.multiSelect);
17692 else if (evt.ctrlKey || evt.metaKey) {
17693 uiGridSelectionService.toggleRowSelection(self, row, self.options.multiSelect, self.options.noUnselect);
17696 uiGridSelectionService.toggleRowSelection(self, row, (self.options.multiSelect && !self.options.modifierKeysToMultiSelect), self.options.noUnselect);
17703 module.directive('uiGridSelectionSelectAllButtons', ['$templateCache', 'uiGridSelectionService',
17704 function ($templateCache, uiGridSelectionService) {
17708 template: $templateCache.get('ui-grid/selectionSelectAllButtons'),
17710 link: function($scope, $elm, $attrs, uiGridCtrl) {
17711 var self = $scope.col.grid;
17713 $scope.headerButtonClick = function(row, evt) {
17714 if ( self.selection.selectAll ){
17715 uiGridSelectionService.clearSelectedRows(self);
17716 if ( self.options.noUnselect ){
17717 self.api.selection.selectRowByVisibleIndex(0);
17719 self.selection.selectAll = false;
17721 if ( self.options.multiSelect ){
17722 self.api.selection.selectAllVisibleRows();
17723 self.selection.selectAll = true;
17733 * @name ui.grid.selection.directive:uiGridViewport
17736 * @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
17739 module.directive('uiGridViewport',
17740 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
17741 function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
17743 priority: -200, // run after default directive
17745 compile: function ($elm, $attrs) {
17746 var rowRepeatDiv = angular.element($elm.children().children()[0]);
17748 var existingNgClass = rowRepeatDiv.attr("ng-class");
17749 var newNgClass = '';
17750 if ( existingNgClass ) {
17751 newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-row-selected': row.isSelected}";
17753 newNgClass = "{'ui-grid-row-selected': row.isSelected}";
17755 rowRepeatDiv.attr("ng-class", newNgClass);
17758 pre: function ($scope, $elm, $attrs, controllers) {
17761 post: function ($scope, $elm, $attrs, controllers) {
17770 * @name ui.grid.selection.directive:uiGridCell
17774 * @description Stacks on top of ui.grid.uiGridCell to provide selection feature
17776 module.directive('uiGridCell',
17777 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', 'gridUtil', '$parse', 'uiGridSelectionService',
17778 function ($compile, uiGridConstants, uiGridSelectionConstants, gridUtil, $parse, uiGridSelectionService) {
17780 priority: -200, // run after default uiGridCell directive
17783 link: function ($scope, $elm, $attrs) {
17785 if ($scope.grid.options.enableRowSelection && !$scope.grid.options.enableRowHeaderSelection) {
17786 $elm.addClass('ui-grid-disable-selection');
17787 registerRowSelectionEvents();
17790 function registerRowSelectionEvents() {
17791 var touchStartTime = 0;
17792 var touchTimeout = 300;
17793 var selectCells = function(evt){
17794 if (evt.shiftKey) {
17795 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, $scope.grid.options.multiSelect);
17797 else if (evt.ctrlKey || evt.metaKey) {
17798 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, $scope.grid.options.multiSelect, $scope.grid.options.noUnselect);
17801 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, ($scope.grid.options.multiSelect && !$scope.grid.options.modifierKeysToMultiSelect), $scope.grid.options.noUnselect);
17806 $elm.on('touchstart', function(event) {
17807 touchStartTime = (new Date()).getTime();
17810 $elm.on('touchend', function (evt) {
17811 var touchEndTime = (new Date()).getTime();
17812 var touchTime = touchEndTime - touchStartTime;
17814 if (touchTime < touchTimeout ) {
17820 $elm.on('click', function (evt) {
17830 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
17833 $templateCache.put('ui-grid/ui-grid-footer',
17834 "<div class=\"ui-grid-footer-panel\"><div ui-grid-group-panel ng-show=\"grid.options.showGroupPanel\"></div><div class=\"ui-grid-footer ui-grid-footer-viewport\"><div class=\"ui-grid-footer-canvas\"><div ng-repeat=\"col in colContainer.renderedColumns track by col.colDef.name\" ui-grid-footer-cell col=\"col\" render-index=\"$index\" class=\"ui-grid-footer-cell clearfix\" ng-style=\"$index === 0 && colContainer.columnStyle($index)\"></div></div></div></div>"
17838 $templateCache.put('ui-grid/ui-grid-group-panel',
17839 "<div class=\"ui-grid-group-panel\"><div ui-t=\"groupPanel.description\" class=\"description\" ng-show=\"groupings.length == 0\"></div><ul ng-show=\"groupings.length > 0\" class=\"ngGroupList\"><li class=\"ngGroupItem\" ng-repeat=\"group in configGroups\"><span class=\"ngGroupElement\"><span class=\"ngGroupName\">{{group.displayName}} <span ng-click=\"removeGroup($index)\" class=\"ngRemoveGroup\">x</span></span> <span ng-hide=\"$last\" class=\"ngGroupArrow\"></span></span></li></ul></div>"
17843 $templateCache.put('ui-grid/ui-grid-header',
17844 "<div class=\"ui-grid-header\"><div class=\"ui-grid-top-panel\"><div ui-grid-group-panel ng-show=\"grid.options.showGroupPanel\"></div><div class=\"ui-grid-header-viewport\"><div class=\"ui-grid-header-canvas\"><div class=\"ui-grid-header-cell clearfix\" ng-repeat=\"col in colContainer.renderedColumns track by col.colDef.name\" ui-grid-header-cell col=\"col\" render-index=\"$index\" ng-style=\"$index === 0 && colContainer.columnStyle($index)\"></div></div></div><div ui-grid-menu></div></div></div>"
17848 $templateCache.put('ui-grid/ui-grid-menu-button',
17849 "<div class=\"ui-grid-menu-button\" ng-click=\"toggleMenu()\"><div class=\"ui-grid-icon-container\"><i class=\"ui-grid-icon-menu\"> </i></div><div ui-grid-menu menu-items=\"menuItems\"></div></div>"
17853 $templateCache.put('ui-grid/ui-grid-no-header',
17854 "<div class=\"ui-grid-top-panel\"></div>"
17858 $templateCache.put('ui-grid/ui-grid-row',
17859 "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" ui-grid-cell></div>"
17863 $templateCache.put('ui-grid/ui-grid',
17864 "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
17865 " /* Styles for the grid */\n" +
17868 " .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
17869 " height: {{ grid.options.rowHeight }}px;\n" +
17872 " .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
17873 " border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
17876 " {{ grid.verticalScrollbarStyles }}\n" +
17877 " {{ grid.horizontalScrollbarStyles }}\n" +
17879 " .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
17880 " padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
17883 " {{ grid.customStyles }}</style><div ui-grid-menu-button ng-if=\"grid.options.enableGridMenu\"></div><div ui-grid-render-container container-id=\"'body'\" col-container-name=\"'body'\" row-container-name=\"'body'\" bind-scroll-horizontal=\"true\" bind-scroll-vertical=\"true\" enable-horizontal-scrollbar=\"grid.options.enableHorizontalScrollbar\" enable-vertical-scrollbar=\"grid.options.enableVerticalScrollbar\"></div><div ui-grid-column-menu ng-if=\"grid.options.enableColumnMenus\"></div><div ng-transclude></div></div>"
17887 $templateCache.put('ui-grid/uiGridCell',
17888 "<div class=\"ui-grid-cell-contents\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
17892 $templateCache.put('ui-grid/uiGridColumnFilter',
17893 "<li class=\"ui-grid-menu-item ui-grid-clearfix ui-grid-column-filter\" ng-show=\"itemShown()\" ng-click=\"$event.stopPropagation();\"><div class=\"input-container\"><input class=\"column-filter-input\" type=\"text\" ng-model=\"item.model\" placeholder=\"{{ i18n.search.placeholder }}\"><div class=\"column-filter-cancel-icon-container\"><i class=\"ui-grid-filter-cancel ui-grid-icon-cancel column-filter-cancel-icon\"> </i></div></div><div style=\"button-container\" ng-click=\"item.action($event)\"><div class=\"ui-grid-button\"><i class=\"ui-grid-icon-search\"> </i></div></div></li>"
17897 $templateCache.put('ui-grid/uiGridColumnMenu',
17898 "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
17899 " <div class=\"inner\" ng-show=\"menuShown\">\n" +
17901 " <div ng-show=\"grid.options.enableSorting\">\n" +
17902 " <li ng-click=\"sortColumn($event, asc)\" ng-class=\"{ 'selected' : col.sort.direction == asc }\"><i class=\"ui-grid-icon-sort-alt-up\"></i> Sort Ascending</li>\n" +
17903 " <li ng-click=\"sortColumn($event, desc)\" ng-class=\"{ 'selected' : col.sort.direction == desc }\"><i class=\"ui-grid-icon-sort-alt-down\"></i> Sort Descending</li>\n" +
17904 " <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
17908 " </div> --></div></div>"
17912 $templateCache.put('ui-grid/uiGridFooterCell',
17913 "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationValue() }}</div></div>"
17917 $templateCache.put('ui-grid/uiGridHeaderCell',
17918 "<div ng-class=\"{ 'sortable': sortable }\"><div class=\"ui-grid-vertical-bar\"> </div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><span>{{ col.displayName CUSTOM_FILTERS }}</span> <span ui-grid-visible=\"col.sort.direction\" ng-class=\"{ 'ui-grid-icon-up-dir': col.sort.direction == asc, 'ui-grid-icon-down-dir': col.sort.direction == desc, 'ui-grid-icon-blank': !col.sort.direction }\"> </span></div><div class=\"ui-grid-column-menu-button\" ng-if=\"grid.options.enableColumnMenus && !col.isRowHeader && col.colDef.enableColumnMenu !== false\" class=\"ui-grid-column-menu-button\" ng-click=\"toggleMenu($event)\"><i class=\"ui-grid-icon-angle-down\"> </i></div><div ng-if=\"filterable\" class=\"ui-grid-filter-container\" ng-repeat=\"colFilter in col.filters\"><input type=\"text\" class=\"ui-grid-filter-input\" ng-model=\"colFilter.term\" ng-click=\"$event.stopPropagation()\" ng-attr-placeholder=\"{{colFilter.placeholder || ''}}\"><div class=\"ui-grid-filter-button\" ng-click=\"colFilter.term = null\"><i class=\"ui-grid-icon-cancel\" ng-show=\"!!colFilter.term\"> </i><!-- use !! because angular interprets 'f' as false --></div></div></div>"
17922 $templateCache.put('ui-grid/uiGridMenu',
17923 "<div class=\"ui-grid-menu\" ng-if=\"shown\"><div class=\"ui-grid-menu-mid\" ng-show=\"shownMid\"><div class=\"ui-grid-menu-inner\"><ul class=\"ui-grid-menu-items\"><li ng-repeat=\"item in menuItems\" ui-grid-menu-item action=\"item.action\" title=\"item.title\" active=\"item.active\" icon=\"item.icon\" shown=\"item.shown\" context=\"item.context\" template-url=\"item.templateUrl\"></li></ul></div></div></div>"
17927 $templateCache.put('ui-grid/uiGridMenuItem',
17928 "<li class=\"ui-grid-menu-item\" ng-click=\"itemAction($event, title)\" ng-show=\"itemShown()\" ng-class=\"{ 'ui-grid-menu-item-active' : active() }\"><i ng-class=\"icon\"></i> {{ title }}</li>"
17932 $templateCache.put('ui-grid/uiGridRenderContainer',
17933 "<div class=\"ui-grid-render-container\"><div ui-grid-header></div><div ui-grid-viewport></div><div ui-grid-footer ng-if=\"grid.options.showFooter\"></div><!-- native scrolling --><div ui-grid-native-scrollbar ng-if=\"enableVerticalScrollbar\" type=\"vertical\"></div><div ui-grid-native-scrollbar ng-if=\"enableHorizontalScrollbar\" type=\"horizontal\"></div></div>"
17937 $templateCache.put('ui-grid/uiGridViewport',
17938 "<div class=\"ui-grid-viewport\"><div class=\"ui-grid-canvas\"><div ng-repeat=\"(rowRenderIndex, row) in rowContainer.renderedRows track by $index\" class=\"ui-grid-row\" ng-style=\"containerCtrl.rowStyle(rowRenderIndex)\"><div ui-grid-row=\"row\" row-render-index=\"rowRenderIndex\"></div></div></div></div>"
17942 $templateCache.put('ui-grid/cellEditor',
17943 "<div><form name=\"inputForm\"><input type=\"{{inputType}}\" ng-class=\"'colt' + col.uid\" ui-grid-editor ng-model=\"MODEL_COL_FIELD\"></form></div>"
17947 $templateCache.put('ui-grid/dropdownEditor',
17948 "<div><form name=\"inputForm\"><select ng-class=\"'colt' + col.uid\" ui-grid-edit-dropdown ng-model=\"MODEL_COL_FIELD\" ng-options=\"field[editDropdownIdLabel] as field[editDropdownValueLabel] CUSTOM_FILTERS for field in editDropdownOptionsArray\"></select></form></div>"
17952 $templateCache.put('ui-grid/expandableRow',
17953 "<div ui-grid-expandable-row ng-if=\"expandableRow.shouldRenderExpand()\" class=\"expandableRow\" style=\"float:left; margin-top: 1px; margin-bottom: 1px\" ng-style=\"{width: (grid.renderContainers.body.getCanvasWidth() - grid.verticalScrollbarWidth) + 'px'\n" +
17954 " , height: grid.options.expandableRowHeight + 'px'}\"></div>"
17958 $templateCache.put('ui-grid/expandableRowHeader',
17959 "<div class=\"ui-grid-row-header-cell ui-grid-expandable-buttons-cell\"><div class=\"ui-grid-cell-contents\"><i ng-class=\"{ 'ui-grid-icon-plus-squared' : !row.isExpanded, 'ui-grid-icon-minus-squared' : row.isExpanded }\" ng-click=\"grid.api.expandable.toggleRowExpansion(row.entity)\"></i></div></div>"
17963 $templateCache.put('ui-grid/expandableScrollFiller',
17964 "<div ng-if=\"expandableRow.shouldRenderFiller()\" style=\"float:left; margin-top: 2px; margin-bottom: 2px\" ng-style=\"{ width: (grid.getViewportWidth()) + 'px',\n" +
17965 " height: grid.options.expandableRowHeight + 'px', 'margin-left': grid.options.rowHeader.rowHeaderWidth + 'px' }\"><i class=\"ui-grid-icon-spin5 ui-grid-animate-spin\" ng-style=\"{ 'margin-top': ( grid.options.expandableRowHeight/2 - 5) + 'px',\n" +
17966 " 'margin-left' : ((grid.getViewportWidth() - grid.options.rowHeader.rowHeaderWidth)/2 - 5) + 'px' }\"></i></div>"
17970 $templateCache.put('ui-grid/csvLink',
17971 "<span class=\"ui-grid-exporter-csv-link-span\"><a href=\"data:text/csv;charset=UTF-8,CSV_CONTENT\">LINK_LABEL</a></span>"
17975 $templateCache.put('ui-grid/importerMenuItem',
17976 "<li class=\"ui-grid-menu-item\"><form><input class=\"ui-grid-importer-file-chooser\" type=\"file\" id=\"files\" name=\"files[]\"></form></li>"
17980 $templateCache.put('ui-grid/importerMenuItemContainer',
17981 "<div ui-grid-importer-menu-item></div>"
17985 $templateCache.put('ui-grid/ui-grid-paging',
17986 "<div class=\"ui-grid-pager-panel\" ui-grid-pager><div class=\"ui-grid-pager-container\"><div class=\"ui-grid-pager-control\"><button type=\"button\" ng-click=\"pageToFirst()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle\"><div class=\"first-bar\"></div></div></button> <button type=\"button\" ng-click=\"pageBackward()\" ng-disabled=\"cantPageBackward()\"><div class=\"first-triangle prev-triangle\"></div></button> <input type=\"number\" ng-model=\"grid.options.pagingCurrentPage\" min=\"1\" max=\"{{currentMaxPages}}\" required> <span class=\"ui-grid-pager-max-pages-number\" ng-show=\"currentMaxPages > 0\">/ {{currentMaxPages}}</span> <button type=\"button\" ng-click=\"pageForward()\" ng-disabled=\"cantPageForward()\"><div class=\"last-triangle next-triangle\"></div></button> <button type=\"button\" ng-click=\"pageToLast()\" ng-disabled=\"cantPageToLast()\"><div class=\"last-triangle\"><div class=\"last-bar\"></div></div></button></div><div class=\"ui-grid-pager-row-count-picker\"><select ng-model=\"grid.options.pagingPageSize\" ng-options=\"o as o for o in grid.options.pagingPageSizes\"></select><span class=\"ui-grid-pager-row-count-label\"> {{sizesLabel}}</span></div></div><div class=\"ui-grid-pager-count-container\"><div class=\"ui-grid-pager-count\"><span ng-show=\"grid.options.totalItems > 0\">{{showingLow}} - {{showingHigh}} of {{grid.options.totalItems}} {{totalItemsLabel}}</span></div></div></div>"
17990 $templateCache.put('ui-grid/columnResizer',
17991 "<div ui-grid-column-resizer ng-if=\"grid.options.enableColumnResizing\" class=\"ui-grid-column-resizer\" col=\"col\" position=\"right\" render-index=\"renderIndex\"></div>"
17995 $templateCache.put('ui-grid/selectionHeaderCell',
17996 "<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>"
18000 $templateCache.put('ui-grid/selectionRowHeader',
18001 "<div class=\"ui-grid-row-header-cell ui-grid-disable-selection\"><div class=\"ui-grid-cell-contents\"><ui-grid-selection-row-header-buttons></ui-grid-selection-row-header-buttons></div></div>"
18005 $templateCache.put('ui-grid/selectionRowHeaderButtons',
18006 "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-row-selected': row.isSelected}\" ng-click=\"selectButtonClick(row, $event)\"> </div>"
18010 $templateCache.put('ui-grid/selectionSelectAllButtons',
18011 "<div class=\"ui-grid-selection-row-header-buttons ui-grid-icon-ok\" ng-class=\"{'ui-grid-all-selected': grid.selection.selectAll}\" ng-click=\"headerButtonClick($event)\"> </div>"