1 /*! ui-grid - v3.0.0-rc.3 - 2014-09-25
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 CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
12 COL_FIELD: /COL_FIELD/g,
13 DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
14 TEMPLATE_REGEXP: /<.+>/,
15 FUNC_REGEXP: /(\([^)]*\))?$/,
18 BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
19 COL_CLASS_PREFIX: 'ui-grid-col',
21 GRID_SCROLL: 'uiGridScroll',
22 COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
23 ITEM_DRAGGING: 'uiGridItemDragStart' // For any item being dragged
25 // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
71 GREATER_THAN_OR_EQUAL: 64,
73 LESS_THAN_OR_EQUAL: 256,
85 // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them?
86 CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫']
90 angular.module('ui.grid').directive('uiGridCell', ['$compile', '$log', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $log, $parse, gridUtil, uiGridConstants) {
97 pre: function($scope, $elm, $attrs, uiGridCtrl) {
98 function compileTemplate() {
99 var compiledElementFn = $scope.col.compiledElementFn;
101 compiledElementFn($scope, function(clonedElement, scope) {
102 $elm.append(clonedElement);
106 // If the grid controller is present, use it to get the compiled cell template function
110 // No controller, compile the element manually (for unit tests)
112 var html = $scope.col.cellTemplate
113 .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
114 var cellElement = $compile(html)($scope);
115 $elm.append(cellElement);
118 post: function($scope, $elm, $attrs, uiGridCtrl) {
119 $elm.addClass($scope.col.getColClass(false));
120 if ($scope.col.cellClass) {
121 //var contents = angular.element($elm[0].getElementsByClassName('ui-grid-cell-contents'));
123 if (angular.isFunction($scope.col.cellClass)) {
124 contents.addClass($scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex));
127 contents.addClass($scope.col.cellClass);
141 angular.module('ui.grid').directive('uiGridColumnMenu', ['$log', '$timeout', '$window', '$document', '$injector', 'gridUtil', 'uiGridConstants', 'i18nService', function ($log, $timeout, $window, $document, $injector, gridUtil, uiGridConstants, i18nService) {
143 var uiGridColumnMenu = {
147 templateUrl: 'ui-grid/uiGridColumnMenu',
149 link: function ($scope, $elm, $attrs, uiGridCtrl) {
150 gridUtil.enableAnimations($elm);
152 $scope.grid = uiGridCtrl.grid;
156 // Store a reference to this link/controller in the main uiGrid controller
157 // to allow showMenu later
158 uiGridCtrl.columnMenuScope = $scope;
160 // Save whether we're shown or not so the columns can check
161 self.shown = $scope.menuShown = false;
163 // Put asc and desc sort directions in scope
164 $scope.asc = uiGridConstants.ASC;
165 $scope.desc = uiGridConstants.DESC;
167 // $scope.i18n = i18nService;
169 // Get the grid menu element. We'll use it to calculate positioning
170 $scope.menu = $elm[0].querySelectorAll('.ui-grid-menu');
172 // Get the inner menu part. It's what slides up/down
173 $scope.inner = $elm[0].querySelectorAll('.ui-grid-menu-inner');
177 * @name enableSorting
178 * @propertyOf ui.grid.class:GridOptions.columnDef
179 * @description (optional) True by default. When enabled, this setting adds sort
180 * widgets to the column header, allowing sorting of the data in the individual column.
182 $scope.sortable = function() {
183 if (uiGridCtrl.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
193 * @name enableFiltering
194 * @propertyOf ui.grid.class:GridOptions.columnDef
195 * @description (optional) True by default. When enabled, this setting adds filter
196 * widgets to the column header, allowing filtering of the data in the individual column.
198 $scope.filterable = function() {
199 if (uiGridCtrl.grid.options.enableFiltering && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableFiltering) {
207 var defaultMenuItems = [
208 // NOTE: disabling this in favor of a little filter text box
209 // Column filter input
211 // templateUrl: 'ui-grid/uiGridColumnFilter',
212 // action: function($event) {
213 // $event.stopPropagation();
214 // $scope.filterColumn($event);
216 // cancel: function ($event) {
217 // $event.stopPropagation();
219 // $scope.col.filter = {};
221 // shown: function () {
222 // return filterable();
226 title: i18nService.getSafeText('sort.ascending'),
227 icon: 'ui-grid-icon-sort-alt-up',
228 action: function($event) {
229 $event.stopPropagation();
230 $scope.sortColumn($event, uiGridConstants.ASC);
233 return $scope.sortable();
236 return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' && typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === uiGridConstants.ASC);
240 title: i18nService.getSafeText('sort.descending'),
241 icon: 'ui-grid-icon-sort-alt-down',
242 action: function($event) {
243 $event.stopPropagation();
244 $scope.sortColumn($event, uiGridConstants.DESC);
247 return $scope.sortable();
250 return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' && typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === uiGridConstants.DESC);
254 title: i18nService.getSafeText('sort.remove'),
255 icon: 'ui-grid-icon-cancel',
256 action: function ($event) {
257 $event.stopPropagation();
258 $scope.unsortColumn();
261 return ($scope.sortable() && typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' && typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null);
265 title: i18nService.getSafeText('column.hide'),
266 icon: 'ui-grid-icon-cancel',
267 action: function ($event) {
268 $event.stopPropagation();
274 // Set the menu items for use with the column menu. Let's the user specify extra menu items per column if they want.
275 $scope.menuItems = defaultMenuItems;
276 $scope.$watch('col.menuItems', function (n, o) {
277 if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
278 n.forEach(function (item) {
279 if (typeof(item.context) === 'undefined' || !item.context) {
282 item.context.col = $scope.col;
285 $scope.menuItems = defaultMenuItems.concat(n);
288 $scope.menuItems = defaultMenuItems;
294 $animate = $injector.get('$animate');
297 $log.info('$animate service not found (ngAnimate not add as a dependency?), menu animations will not occur');
301 $scope.showMenu = function(column, $columnElement) {
302 // Swap to this column
303 // note - store a reference to this column in 'self' so the columns can check whether they're the shown column or not
304 self.col = $scope.col = column;
306 // Remove an existing document click handler
307 $document.off('click', documentClick);
309 /* Reposition the menu below this column's element */
310 var left = $columnElement[0].offsetLeft;
311 var top = $columnElement[0].offsetTop;
313 // Get the grid scrollLeft
315 if (column.grid.options.offsetLeft) {
316 offset = column.grid.options.offsetLeft;
319 var height = gridUtil.elementHeight($columnElement, true);
320 var width = gridUtil.elementWidth($columnElement, true);
322 // Flag for whether we're hidden for showing via $animate
325 // Re-position the menu AFTER it's been shown, so we can calculate the width correctly.
326 function reposition() {
327 $timeout(function() {
328 if (hidden && $animate) {
329 $animate.removeClass($scope.inner, 'ng-hide');
330 self.shown = $scope.menuShown = true;
331 $scope.$broadcast('show-menu');
333 else if (angular.element($scope.inner).hasClass('ng-hide')) {
334 angular.element($scope.inner).removeClass('ng-hide');
337 // var containerScrollLeft = $columnelement
338 var containerId = column.renderContainer ? column.renderContainer : 'body';
339 var renderContainer = column.grid.renderContainers[containerId];
340 // var containerScrolLeft = renderContainer.prevScrollLeft;
342 // It's possible that the render container of the column we're attaching to is offset from the grid (i.e. pinned containers), we
343 // need to get the different in the offsetLeft between the render container and the grid
344 var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
345 var renderContainerOffset = renderContainerElm.offsetLeft - $scope.grid.element[0].offsetLeft;
347 var containerScrolLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
349 var myWidth = gridUtil.elementWidth($scope.menu, true);
351 // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
352 // Get the column menu right padding
353 var paddingRight = parseInt(angular.element($scope.menu).css('padding-right'), 10);
355 // $log.debug('position', left + ' + ' + width + ' - ' + myWidth + ' + ' + paddingRight);
357 $elm.css('left', (left + renderContainerOffset - containerScrolLeft + width - myWidth + paddingRight) + 'px');
358 $elm.css('top', (top + height) + 'px');
360 // Hide the menu on a click on the document
361 $document.on('click', documentClick);
365 if ($scope.menuShown && $animate) {
366 // Animate closing the menu on the current column, then animate it opening on the other column
367 $animate.addClass($scope.inner, 'ng-hide', reposition);
371 self.shown = $scope.menuShown = true;
372 $scope.$broadcast('show-menu');
378 $scope.hideMenu = function() {
381 self.shown = $scope.menuShown = false;
382 $scope.$broadcast('hide-menu');
385 // Prevent clicks on the menu from bubbling up to the document and making it hide prematurely
386 // $elm.on('click', function (event) {
387 // event.stopPropagation();
390 function documentClick() {
391 $scope.$apply($scope.hideMenu);
392 $document.off('click', documentClick);
395 function resizeHandler() {
396 $scope.$apply($scope.hideMenu);
398 angular.element($window).bind('resize', resizeHandler);
400 $scope.$on('$destroy', $scope.$on(uiGridConstants.events.GRID_SCROLL, function(evt, args) {
402 // if (!$scope.$$phase) { $scope.$apply(); }
405 $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, function(evt, args) {
407 // if (!$scope.$$phase) { $scope.$apply(); }
410 $scope.$on('$destroy', function() {
411 angular.element($window).off('resize', resizeHandler);
412 $document.off('click', documentClick);
416 $scope.sortColumn = function (event, dir) {
417 event.stopPropagation();
419 uiGridCtrl.grid.sortColumn($scope.col, dir, true)
421 uiGridCtrl.grid.refresh();
426 $scope.unsortColumn = function () {
429 uiGridCtrl.grid.refresh();
433 $scope.hideColumn = function () {
434 $scope.col.colDef.visible = false;
436 uiGridCtrl.grid.refresh();
440 controller: ['$scope', function ($scope) {
443 $scope.$watch('menuItems', function (n, o) {
449 return uiGridColumnMenu;
457 angular.module('ui.grid').directive('uiGridFooterCell', ['$log', '$timeout', 'gridUtil', '$compile', function ($log, $timeout, gridUtil, $compile) {
458 var uiGridFooterCell = {
467 compile: function compile(tElement, tAttrs, transclude) {
469 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
470 function compileTemplate(template) {
471 gridUtil.getTemplate(template).then(function (contents) {
472 var linkFunction = $compile(contents);
473 var html = linkFunction($scope);
478 //compile the footer template
479 if ($scope.col.footerCellTemplate) {
480 //compile the custom template
481 compileTemplate($scope.col.footerCellTemplate);
484 //use default template
485 compileTemplate('ui-grid/uiGridFooterCell');
488 post: function ($scope, $elm, $attrs, uiGridCtrl) {
489 //$elm.addClass($scope.col.getColClass(false));
490 $scope.grid = uiGridCtrl.grid;
492 $elm.addClass($scope.col.getColClass(false));
498 return uiGridFooterCell;
506 angular.module('ui.grid').directive('uiGridFooter', ['$log', '$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($log, $templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
507 var defaultTemplate = 'ui-grid/ui-grid-footer';
513 require: ['^uiGrid', '^uiGridRenderContainer'],
515 compile: function ($elm, $attrs) {
517 pre: function ($scope, $elm, $attrs, controllers) {
518 var uiGridCtrl = controllers[0];
519 var containerCtrl = controllers[1];
521 $scope.grid = uiGridCtrl.grid;
522 $scope.colContainer = containerCtrl.colContainer;
524 containerCtrl.footer = $elm;
526 var footerTemplate = ($scope.grid.options.footerTemplate) ? $scope.grid.options.footerTemplate : defaultTemplate;
527 gridUtil.getTemplate(footerTemplate)
528 .then(function (contents) {
529 var template = angular.element(contents);
531 var newElm = $compile(template)($scope);
535 // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
536 var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
538 if (footerViewport) {
539 containerCtrl.footerViewport = footerViewport;
545 post: function ($scope, $elm, $attrs, controllers) {
546 var uiGridCtrl = controllers[0];
547 var containerCtrl = controllers[1];
549 $log.debug('ui-grid-footer link');
551 var grid = uiGridCtrl.grid;
553 // Don't animate footer cells
554 gridUtil.disableAnimations($elm);
556 containerCtrl.footer = $elm;
558 var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
559 if (footerViewport) {
560 containerCtrl.footerViewport = footerViewport;
572 angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
573 var defaultTemplate = 'ui-grid/ui-grid-group-panel';
580 compile: function($elm, $attrs) {
582 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
583 var groupPanelTemplate = $scope.grid.options.groupPanelTemplate || defaultTemplate;
585 gridUtil.getTemplate(groupPanelTemplate)
586 .then(function (contents) {
587 var template = angular.element(contents);
589 var newElm = $compile(template)($scope);
594 post: function ($scope, $elm, $attrs, uiGridCtrl) {
595 $elm.bind('$destroy', function() {
608 angular.module('ui.grid').directive('uiGridHeaderCell', ['$log', '$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants',
609 function ($log, $compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
610 // Do stuff after mouse has been down this many ms on the header cell
611 var mousedownTimeout = 500;
613 var uiGridHeaderCell = {
622 compile: function() {
624 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
625 var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
626 $elm.append(cellHeader);
629 post: function ($scope, $elm, $attrs, uiGridCtrl) {
630 $scope.grid = uiGridCtrl.grid;
634 * @name filterChanged
635 * @eventOf ui.grid.core.api:PublicApi
636 * @description is raised after the filter is changed. The nature
637 * of the watch expression doesn't allow notification of what changed,
638 * so the receiver of this event will need to re-extract the filter
639 * conditions from the columns.
642 if (!$scope.grid.api.core.raise.filterChanged){
643 $scope.grid.api.registerEvent( 'core', 'filterChanged' );
647 $elm.addClass($scope.col.getColClass(false));
648 // shane - No need for watch now that we trackby col name
649 // $scope.$watch('col.index', function (newValue, oldValue) {
650 // if (newValue === oldValue) { return; }
651 // var className = $elm.attr('class');
652 // className = className.replace(uiGridConstants.COL_CLASS_PREFIX + oldValue, uiGridConstants.COL_CLASS_PREFIX + newValue);
653 // $elm.attr('class', className);
656 // Hide the menu by default
657 $scope.menuShown = false;
659 // Put asc and desc sort directions in scope
660 $scope.asc = uiGridConstants.ASC;
661 $scope.desc = uiGridConstants.DESC;
663 // Store a reference to menu element
664 var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
666 var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
668 // Figure out whether this column is sortable or not
669 if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
670 $scope.sortable = true;
673 $scope.sortable = false;
676 if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
677 $scope.filterable = true;
680 $scope.filterable = false;
683 function handleClick(evt) {
684 // If the shift key is being held down, add this column to the sort
690 // Sort this column then rebuild the grid's rows
691 uiGridCtrl.grid.sortColumn($scope.col, add)
693 if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
694 uiGridCtrl.grid.refresh();
698 // Long-click (for mobile)
699 var cancelMousedownTimeout;
700 var mousedownStartTime = 0;
701 $contentsElm.on('mousedown', function(event) {
702 if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
703 event = event.originalEvent;
706 // Don't show the menu if it's not the left button
707 if (event.button && event.button !== 0) {
711 mousedownStartTime = (new Date()).getTime();
713 cancelMousedownTimeout = $timeout(function() { }, mousedownTimeout);
715 cancelMousedownTimeout.then(function () {
716 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
720 $contentsElm.on('mouseup', function () {
721 $timeout.cancel(cancelMousedownTimeout);
724 $scope.toggleMenu = function($event) {
725 $event.stopPropagation();
727 // If the menu is already showing...
728 if (uiGridCtrl.columnMenuScope.menuShown) {
729 // ... and we're the column the menu is on...
730 if (uiGridCtrl.columnMenuScope.col === $scope.col) {
732 uiGridCtrl.columnMenuScope.hideMenu();
734 // ... and we're NOT the column the menu is on
736 // ... move the menu to our column
737 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
740 // If the menu is NOT showing
742 // ... show it on our column
743 uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
747 // If this column is sortable, add a click event handler
748 if ($scope.sortable) {
749 $contentsElm.on('click', function(evt) {
750 evt.stopPropagation();
752 $timeout.cancel(cancelMousedownTimeout);
754 var mousedownEndTime = (new Date()).getTime();
755 var mousedownTime = mousedownEndTime - mousedownStartTime;
757 if (mousedownTime > mousedownTimeout) {
758 // long click, handled above with mousedown
766 $scope.$on('$destroy', function () {
767 // Cancel any pending long-click timeout
768 $timeout.cancel(cancelMousedownTimeout);
772 if ($scope.filterable) {
773 var filterDeregisters = [];
774 angular.forEach($scope.col.filters, function(filter, i) {
775 filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
776 uiGridCtrl.grid.api.core.raise.filterChanged();
777 uiGridCtrl.grid.refresh()
779 if (uiGridCtrl.prevScrollArgs && uiGridCtrl.prevScrollArgs.y && uiGridCtrl.prevScrollArgs.y.percentage) {
780 uiGridCtrl.fireScrollingEvent({ y: { percentage: uiGridCtrl.prevScrollArgs.y.percentage } });
782 // uiGridCtrl.fireEvent('force-vertical-scroll');
786 $scope.$on('$destroy', function() {
787 angular.forEach(filterDeregisters, function(filterDeregister) {
797 return uiGridHeaderCell;
805 angular.module('ui.grid').directive('uiGridHeader', ['$log', '$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function($log, $templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
806 var defaultTemplate = 'ui-grid/ui-grid-header';
807 var emptyTemplate = 'ui-grid/ui-grid-no-header';
811 // templateUrl: 'ui-grid/ui-grid-header',
814 require: ['^uiGrid', '^uiGridRenderContainer'],
816 compile: function($elm, $attrs) {
818 pre: function ($scope, $elm, $attrs, controllers) {
819 var uiGridCtrl = controllers[0];
820 var containerCtrl = controllers[1];
822 $scope.grid = uiGridCtrl.grid;
823 $scope.colContainer = containerCtrl.colContainer;
825 containerCtrl.header = $elm;
826 containerCtrl.colContainer.header = $elm;
831 * @propertyOf ui.grid.class:GridOptions
832 * @description Null by default. When set to true, this setting will replace the
833 * standard header template with '<div></div>', resulting in no header being shown.
837 if ($scope.grid.options.hideHeader){
838 headerTemplate = emptyTemplate;
840 headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;
843 gridUtil.getTemplate(headerTemplate)
844 .then(function (contents) {
845 var template = angular.element(contents);
847 var newElm = $compile(template)($scope);
851 // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
852 var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
854 if (headerViewport) {
855 containerCtrl.headerViewport = headerViewport;
861 post: function ($scope, $elm, $attrs, controllers) {
862 var uiGridCtrl = controllers[0];
863 var containerCtrl = controllers[1];
865 $log.debug('ui-grid-header link');
867 var grid = uiGridCtrl.grid;
869 // Don't animate header cells
870 gridUtil.disableAnimations($elm);
872 function updateColumnWidths() {
873 var asterisksArray = [],
879 // Get the width of the viewport
880 var availableWidth = containerCtrl.colContainer.getViewportWidth();
882 if (typeof(uiGridCtrl.grid.verticalScrollbarWidth) !== 'undefined' && uiGridCtrl.grid.verticalScrollbarWidth !== undefined && uiGridCtrl.grid.verticalScrollbarWidth > 0) {
883 availableWidth = availableWidth + uiGridCtrl.grid.verticalScrollbarWidth;
886 // The total number of columns
887 // var equalWidthColumnCount = columnCount = uiGridCtrl.grid.options.columnDefs.length;
888 // var equalWidth = availableWidth / equalWidthColumnCount;
890 // The last column we processed
893 var manualWidthSum = 0;
900 // uiGridCtrl.grid.columns.forEach(function(column, i) {
902 var columnCache = containerCtrl.colContainer.visibleColumnCache;
904 columnCache.forEach(function(column, i) {
905 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + i + ' { width: ' + equalWidth + 'px; left: ' + left + 'px; }';
906 //var colWidth = (typeof(c.width) !== 'undefined' && c.width !== undefined) ? c.width : equalWidth;
908 // Skip hidden columns
909 if (!column.visible) { return; }
914 if (!angular.isNumber(column.width)) {
915 isPercent = isNaN(column.width) ? gridUtil.endsWith(column.width, "%") : false;
918 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.
919 asteriskNum = parseInt(asteriskNum + column.width.length, 10);
921 asterisksArray.push(column);
923 else if (isPercent) { // If the width is a percentage, save it until the very last.
924 percentArray.push(column);
926 else if (angular.isNumber(column.width)) {
927 manualWidthSum = parseInt(manualWidthSum + column.width, 10);
929 canvasWidth = parseInt(canvasWidth, 10) + parseInt(column.width, 10);
931 column.drawnWidth = column.width;
933 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + column.width + 'px; }';
937 // Get the remaining width (available width subtracted by the manual widths sum)
938 var remainingWidth = availableWidth - manualWidthSum;
940 var i, column, colWidth;
942 if (percentArray.length > 0) {
943 // Pre-process to make sure they're all within any min/max values
944 for (i = 0; i < percentArray.length; i++) {
945 column = percentArray[i];
947 var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
949 colWidth = parseInt(percent * remainingWidth, 10);
951 if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
952 colWidth = column.colDef.minWidth;
954 remainingWidth = remainingWidth - colWidth;
956 canvasWidth += colWidth;
957 column.drawnWidth = colWidth;
959 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
961 // Remove this element from the percent array so it's not processed below
962 percentArray.splice(i, 1);
964 else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
965 colWidth = column.colDef.maxWidth;
967 remainingWidth = remainingWidth - colWidth;
969 canvasWidth += colWidth;
970 column.drawnWidth = colWidth;
972 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
974 // Remove this element from the percent array so it's not processed below
975 percentArray.splice(i, 1);
979 percentArray.forEach(function(column) {
980 var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
981 var colWidth = parseInt(percent * remainingWidth, 10);
983 canvasWidth += colWidth;
985 column.drawnWidth = colWidth;
987 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
991 if (asterisksArray.length > 0) {
992 var asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
994 // Pre-process to make sure they're all within any min/max values
995 for (i = 0; i < asterisksArray.length; i++) {
996 column = asterisksArray[i];
998 colWidth = parseInt(asteriskVal * column.width.length, 10);
1000 if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
1001 colWidth = column.colDef.minWidth;
1003 remainingWidth = remainingWidth - colWidth;
1006 canvasWidth += colWidth;
1007 column.drawnWidth = colWidth;
1009 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
1011 lastColumn = column;
1013 // Remove this element from the percent array so it's not processed below
1014 asterisksArray.splice(i, 1);
1016 else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
1017 colWidth = column.colDef.maxWidth;
1019 remainingWidth = remainingWidth - colWidth;
1022 canvasWidth += colWidth;
1023 column.drawnWidth = colWidth;
1025 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
1027 // Remove this element from the percent array so it's not processed below
1028 asterisksArray.splice(i, 1);
1032 // Redo the asterisk value, as we may have removed columns due to width constraints
1033 asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
1035 asterisksArray.forEach(function(column) {
1036 var colWidth = parseInt(asteriskVal * column.width.length, 10);
1038 canvasWidth += colWidth;
1040 column.drawnWidth = colWidth;
1042 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
1046 // 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
1047 var leftoverWidth = availableWidth - parseInt(canvasWidth, 10);
1049 if (leftoverWidth > 0 && canvasWidth > 0 && canvasWidth < availableWidth) {
1050 var variableColumn = false;
1051 // uiGridCtrl.grid.columns.forEach(function(col) {
1052 columnCache.forEach(function(col) {
1053 if (col.width && !angular.isNumber(col.width)) {
1054 variableColumn = true;
1058 if (variableColumn) {
1059 var remFn = function (column) {
1060 if (leftoverWidth > 0) {
1061 column.drawnWidth = column.drawnWidth + 1;
1062 canvasWidth = canvasWidth + 1;
1066 while (leftoverWidth > 0) {
1067 columnCache.forEach(remFn);
1072 if (canvasWidth < availableWidth) {
1073 canvasWidth = availableWidth;
1077 // uiGridCtrl.grid.columns.forEach(function (column) {
1078 columnCache.forEach(function (column) {
1079 ret = ret + column.getColClassDefinition();
1082 // Add the vertical scrollbar width back in to the canvas width, it's taken out in getCanvasWidth
1083 if (grid.verticalScrollbarWidth) {
1084 canvasWidth = canvasWidth + grid.verticalScrollbarWidth;
1086 // canvasWidth = canvasWidth + 1;
1088 containerCtrl.colContainer.canvasWidth = parseInt(canvasWidth, 10);
1090 // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
1094 containerCtrl.header = $elm;
1096 var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
1097 if (headerViewport) {
1098 containerCtrl.headerViewport = headerViewport;
1101 //todo: remove this if by injecting gridCtrl into unit tests
1103 uiGridCtrl.grid.registerStyleComputation({
1105 func: updateColumnWidths
1119 * @name ui.grid.directive:uiGridColumnMenu
1124 * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
1127 <doc:example module="app">
1130 var app = angular.module('app', ['ui.grid']);
1132 app.controller('MainCtrl', ['$scope', function ($scope) {
1137 <div ng-controller="MainCtrl">
1138 <div ui-grid-menu shown="true" ></div>
1145 angular.module('ui.grid')
1147 .directive('uiGridMenu', ['$log', '$compile', '$timeout', '$window', '$document', 'gridUtil', function ($log, $compile, $timeout, $window, $document, gridUtil) {
1155 require: '?^uiGrid',
1156 templateUrl: 'ui-grid/uiGridMenu',
1158 link: function ($scope, $elm, $attrs, uiGridCtrl) {
1159 gridUtil.enableAnimations($elm);
1161 if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
1162 $scope.autoHide = true;
1165 if ($scope.autoHide) {
1166 angular.element($window).on('resize', $scope.hideMenu);
1169 $scope.$on('hide-menu', function () {
1170 $scope.shown = false;
1173 $scope.$on('show-menu', function () {
1174 $scope.shown = true;
1177 $scope.$on('$destroy', function() {
1178 angular.element($window).off('resize', $scope.hideMenu);
1181 controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
1184 self.hideMenu = $scope.hideMenu = function() {
1185 $scope.shown = false;
1188 function documentClick() {
1189 $scope.$apply(function () {
1191 angular.element(document).off('click', documentClick);
1195 self.showMenu = $scope.showMenu = function() {
1196 $scope.shown = true;
1198 // Turn off an existing dpcument click handler
1199 angular.element(document).off('click', documentClick);
1201 // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
1202 $timeout(function() {
1203 angular.element(document).on('click', documentClick);
1207 $scope.$on('$destroy', function () {
1208 angular.element(document).off('click', documentClick);
1216 .directive('uiGridMenuItem', ['$log', 'gridUtil', '$compile', 'i18nService', function ($log, gridUtil, $compile, i18nService) {
1217 var uiGridMenuItem = {
1228 require: ['?^uiGrid', '^uiGridMenu'],
1229 templateUrl: 'ui-grid/uiGridMenuItem',
1231 compile: function($elm, $attrs) {
1233 pre: function ($scope, $elm, $attrs, controllers) {
1234 var uiGridCtrl = controllers[0],
1235 uiGridMenuCtrl = controllers[1];
1237 if ($scope.templateUrl) {
1238 gridUtil.getTemplate($scope.templateUrl)
1239 .then(function (contents) {
1240 var template = angular.element(contents);
1242 var newElm = $compile(template)($scope);
1243 $elm.replaceWith(newElm);
1247 post: function ($scope, $elm, $attrs, controllers) {
1248 var uiGridCtrl = controllers[0],
1249 uiGridMenuCtrl = controllers[1];
1251 // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
1252 // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
1253 // throw new TypeError("$scope.shown is defined but not a function");
1255 if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
1256 $scope.shown = function() { return true; };
1259 $scope.itemShown = function () {
1261 if ($scope.context) {
1262 context.context = $scope.context;
1265 if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
1266 context.grid = uiGridCtrl.grid;
1269 return $scope.shown.call(context);
1272 $scope.itemAction = function($event,title) {
1273 $log.debug('itemAction');
1274 $event.stopPropagation();
1276 if (typeof($scope.action) === 'function') {
1279 if ($scope.context) {
1280 context.context = $scope.context;
1283 // Add the grid to the function call context if the uiGrid controller is present
1284 if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
1285 context.grid = uiGridCtrl.grid;
1288 $scope.action.call(context, $event, title);
1290 uiGridMenuCtrl.hideMenu();
1294 $scope.i18n = i18nService.get();
1300 return uiGridMenuItem;
1307 angular.module('ui.grid').directive('uiGridNativeScrollbar', ['$log', '$timeout', '$document', 'uiGridConstants', 'gridUtil',
1308 function ($log, $timeout, $document, uiGridConstants, gridUtil) {
1309 var scrollBarWidth = gridUtil.getScrollbarWidth();
1310 scrollBarWidth = scrollBarWidth > 0 ? scrollBarWidth : 17;
1312 // If the browser is IE, add 1px to the scrollbar container, otherwise scroll events won't work right (in IE11 at least)
1313 var browser = gridUtil.detectBrowser();
1314 if (browser === 'ie') {
1315 scrollBarWidth = scrollBarWidth + 1;
1322 require: ['^uiGrid', '^uiGridRenderContainer'],
1323 link: function ($scope, $elm, $attrs, controllers) {
1324 var uiGridCtrl = controllers[0];
1325 var containerCtrl = controllers[1];
1326 var rowContainer = containerCtrl.rowContainer;
1327 var colContainer = containerCtrl.colContainer;
1328 var grid = uiGridCtrl.grid;
1330 var contents = angular.element('<div class="contents"> </div>');
1332 $elm.addClass('ui-grid-native-scrollbar');
1334 var previousScrollPosition;
1336 var elmMaxScroll = 0;
1338 if ($scope.type === 'vertical') {
1339 // Update the width based on native scrollbar width
1340 $elm.css('width', scrollBarWidth + 'px');
1342 $elm.addClass('vertical');
1344 grid.verticalScrollbarWidth = scrollBarWidth;
1345 colContainer.verticalScrollbarWidth = scrollBarWidth;
1347 // Save the initial scroll position for use in scroll events
1348 previousScrollPosition = $elm[0].scrollTop;
1350 else if ($scope.type === 'horizontal') {
1351 // Update the height based on native scrollbar height
1352 $elm.css('height', scrollBarWidth + 'px');
1354 $elm.addClass('horizontal');
1356 // Save this scrollbar's dimension in the grid properties
1357 grid.horizontalScrollbarHeight = scrollBarWidth;
1358 rowContainer.horizontalScrollbarHeight = scrollBarWidth;
1360 // Save the initial scroll position for use in scroll events
1361 previousScrollPosition = gridUtil.normalizeScrollLeft($elm);
1364 // Save the contents elm inside the scrollbar elm so it sizes correctly
1365 $elm.append(contents);
1367 // Get the relevant element dimension now that the contents are in it
1368 if ($scope.type === 'vertical') {
1369 elmMaxScroll = gridUtil.elementHeight($elm);
1371 else if ($scope.type === 'horizontal') {
1372 elmMaxScroll = gridUtil.elementWidth($elm);
1375 function updateNativeVerticalScrollbar() {
1376 // Get the height that the scrollbar should have
1377 var height = rowContainer.getViewportHeight();
1379 // Update the vertical scrollbar's content height so it's the same as the canvas
1380 var contentHeight = rowContainer.getCanvasHeight();
1382 // TODO(c0bra): set scrollbar `top` by height of header row
1383 // var headerHeight = gridUtil.outerElementHeight(containerCtrl.header);
1384 var headerHeight = colContainer.headerHeight ? colContainer.headerHeight : grid.headerHeight;
1386 // $log.debug('headerHeight in scrollbar', headerHeight);
1388 // var ret = '.grid' + uiGridCtrl.grid.id + ' .ui-grid-native-scrollbar.vertical .contents { height: ' + h + 'px; }';
1389 var ret = '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.vertical .contents { height: ' + contentHeight + 'px; }';
1390 ret += '\n .grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.vertical { height: ' + height + 'px; top: ' + headerHeight + 'px}';
1392 elmMaxScroll = contentHeight;
1397 // Get the grid's bottom border height (TODO(c0bra): need to account for footer here!)
1398 var gridElm = gridUtil.closestElm($elm, '.ui-grid');
1399 var gridBottomBorder = gridUtil.getBorderSize(gridElm, 'bottom');
1401 function updateNativeHorizontalScrollbar() {
1402 var w = colContainer.getCanvasWidth();
1404 // Scrollbar needs to be negatively positioned beyond the bottom of the relatively-positioned render container
1405 var bottom = (scrollBarWidth * -1) + gridBottomBorder;
1406 if (grid.options.showFooter) {
1409 var ret = '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.horizontal { bottom: ' + bottom + 'px; }';
1410 ret += '.grid' + grid.id + ' .ui-grid-render-container-' + containerCtrl.containerId + ' .ui-grid-native-scrollbar.horizontal .contents { width: ' + w + 'px; }';
1417 // NOTE: priority 6 so they run after the column widths update, which in turn update the canvas width
1418 if ($scope.type === 'vertical') {
1419 grid.registerStyleComputation({
1421 func: updateNativeVerticalScrollbar
1424 else if ($scope.type === 'horizontal') {
1425 grid.registerStyleComputation({
1427 func: updateNativeHorizontalScrollbar
1432 $scope.scrollSource = null;
1434 function scrollEvent(evt) {
1435 if ($scope.type === 'vertical') {
1436 grid.flagScrollingVertically();
1437 var newScrollTop = $elm[0].scrollTop;
1439 var yDiff = previousScrollPosition - newScrollTop;
1441 var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
1443 // Subtract the h. scrollbar height from the vertical length if it's present
1444 if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
1445 vertScrollLength = vertScrollLength - uiGridCtrl.grid.horizontalScrollbarHeight;
1448 var vertScrollPercentage = newScrollTop / vertScrollLength;
1450 if (vertScrollPercentage > 1) {
1451 vertScrollPercentage = 1;
1453 if (vertScrollPercentage < 0) {
1454 vertScrollPercentage = 0;
1460 percentage: vertScrollPercentage
1464 // 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)
1465 if (!$scope.scrollSource) {
1466 uiGridCtrl.fireScrollingEvent(yArgs);
1469 // Reset the scroll source for the next scroll event
1470 $scope.scrollSource = null;
1473 previousScrollPosition = newScrollTop;
1475 else if ($scope.type === 'horizontal') {
1476 grid.flagScrollingHorizontally();
1477 // var newScrollLeft = $elm[0].scrollLeft;
1478 var newScrollLeft = gridUtil.normalizeScrollLeft($elm);
1480 var xDiff = previousScrollPosition - newScrollLeft;
1482 var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
1483 var horizScrollPercentage = newScrollLeft / horizScrollLength;
1488 percentage: horizScrollPercentage
1492 // 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)
1493 if (!$scope.scrollSource) {
1494 uiGridCtrl.fireScrollingEvent(xArgs);
1497 // Reset the scroll source for the next scroll event
1498 $scope.scrollSource = null;
1501 previousScrollPosition = newScrollLeft;
1505 $elm.on('scroll', scrollEvent);
1507 $elm.on('$destroy', function () {
1511 function gridScroll(evt, args) {
1512 // Don't listen to our own scroll event!
1513 if (args.target && (args.target === $elm || angular.element(args.target).hasClass('ui-grid-native-scrollbar'))) {
1517 // Set the source of the scroll event in our scope so it's available in our 'scroll' event handler
1518 $scope.scrollSource = args.target;
1520 if ($scope.type === 'vertical') {
1521 if (args.y && typeof(args.y.percentage) !== 'undefined' && args.y.percentage !== undefined) {
1522 grid.flagScrollingVertically();
1523 var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
1525 var newScrollTop = Math.max(0, args.y.percentage * vertScrollLength);
1527 $elm[0].scrollTop = newScrollTop;
1532 else if ($scope.type === 'horizontal') {
1533 if (args.x && typeof(args.x.percentage) !== 'undefined' && args.x.percentage !== undefined) {
1534 grid.flagScrollingHorizontally();
1535 var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
1537 var newScrollLeft = Math.max(0, args.x.percentage * horizScrollLength);
1539 // $elm[0].scrollLeft = newScrollLeft;
1540 $elm[0].scrollLeft = gridUtil.denormalizeScrollLeft($elm, newScrollLeft);
1545 var gridScrollDereg = $scope.$on(uiGridConstants.events.GRID_SCROLL, gridScroll);
1546 $scope.$on('$destroy', gridScrollDereg);
1557 var module = angular.module('ui.grid');
1559 module.directive('uiGridRenderContainer', ['$log', '$timeout', '$document', 'uiGridConstants', 'gridUtil',
1560 function($log, $timeout, $document, uiGridConstants, GridUtil) {
1564 templateUrl: 'ui-grid/uiGridRenderContainer',
1565 require: ['^uiGrid', 'uiGridRenderContainer'],
1568 rowContainerName: '=',
1569 colContainerName: '=',
1570 bindScrollHorizontal: '=',
1571 bindScrollVertical: '=',
1572 enableScrollbars: '='
1574 controller: 'uiGridRenderContainer as RenderContainer',
1575 compile: function () {
1577 pre: function prelink($scope, $elm, $attrs, controllers) {
1578 $log.debug('render container ' + $scope.containerId + ' pre-link');
1580 var uiGridCtrl = controllers[0];
1581 var containerCtrl = controllers[1];
1583 var grid = $scope.grid = uiGridCtrl.grid;
1585 // Verify that the render container for this element exists
1586 if (!$scope.rowContainerName) {
1587 throw "No row render container name specified";
1589 if (!$scope.colContainerName) {
1590 throw "No column render container name specified";
1593 if (!grid.renderContainers[$scope.rowContainerName]) {
1594 throw "Row render container '" + $scope.rowContainerName + "' is not registered.";
1596 if (!grid.renderContainers[$scope.colContainerName]) {
1597 throw "Column render container '" + $scope.colContainerName + "' is not registered.";
1600 var rowContainer = $scope.rowContainer = grid.renderContainers[$scope.rowContainerName];
1601 var colContainer = $scope.colContainer = grid.renderContainers[$scope.colContainerName];
1603 containerCtrl.containerId = $scope.containerId;
1604 containerCtrl.rowContainer = rowContainer;
1605 containerCtrl.colContainer = colContainer;
1607 post: function postlink($scope, $elm, $attrs, controllers) {
1608 $log.debug('render container ' + $scope.containerId + ' post-link');
1610 var uiGridCtrl = controllers[0];
1611 var containerCtrl = controllers[1];
1613 var grid = uiGridCtrl.grid;
1614 var rowContainer = containerCtrl.rowContainer;
1615 var colContainer = containerCtrl.colContainer;
1617 // Put the container name on this element as a class
1618 $elm.addClass('ui-grid-render-container-' + $scope.containerId);
1620 // Bind to left/right-scroll events
1622 if ($scope.bindScrollHorizontal || $scope.bindScrollVertical) {
1623 scrollUnbinder = $scope.$on(uiGridConstants.events.GRID_SCROLL, scrollHandler);
1626 function scrollHandler (evt, args) {
1628 if (args.y && $scope.bindScrollVertical) {
1629 containerCtrl.prevScrollArgs = args;
1631 var scrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
1633 // Add the height of the native horizontal scrollbar, if it's there. Otherwise it will mask over the final row
1634 if (grid.horizontalScrollbarHeight && grid.horizontalScrollbarHeight > 0) {
1635 scrollLength = scrollLength + grid.horizontalScrollbarHeight;
1638 var oldScrollTop = containerCtrl.viewport[0].scrollTop;
1640 var scrollYPercentage;
1641 if (typeof(args.y.percentage) !== 'undefined' && args.y.percentage !== undefined) {
1642 scrollYPercentage = args.y.percentage;
1644 else if (typeof(args.y.pixels) !== 'undefined' && args.y.pixels !== undefined) {
1645 scrollYPercentage = args.y.percentage = (oldScrollTop + args.y.pixels) / scrollLength;
1646 // $log.debug('y.percentage', args.y.percentage);
1649 throw new Error("No percentage or pixel value provided for scroll event Y axis");
1652 var newScrollTop = Math.max(0, scrollYPercentage * scrollLength);
1654 containerCtrl.viewport[0].scrollTop = newScrollTop;
1656 // TOOD(c0bra): what's this for?
1657 // grid.options.offsetTop = newScrollTop;
1659 containerCtrl.prevScrollArgs.y.pixels = newScrollTop - oldScrollTop;
1662 // Horizontal scroll
1663 if (args.x && $scope.bindScrollHorizontal) {
1664 containerCtrl.prevScrollArgs = args;
1666 var scrollWidth = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
1668 // var oldScrollLeft = containerCtrl.viewport[0].scrollLeft;
1669 var oldScrollLeft = GridUtil.normalizeScrollLeft(containerCtrl.viewport);
1671 var scrollXPercentage;
1672 if (typeof(args.x.percentage) !== 'undefined' && args.x.percentage !== undefined) {
1673 scrollXPercentage = args.x.percentage;
1675 else if (typeof(args.x.pixels) !== 'undefined' && args.x.pixels !== undefined) {
1676 scrollXPercentage = args.x.percentage = (oldScrollLeft + args.x.pixels) / scrollWidth;
1679 throw new Error("No percentage or pixel value provided for scroll event X axis");
1682 var newScrollLeft = Math.max(0, scrollXPercentage * scrollWidth);
1684 // uiGridCtrl.adjustScrollHorizontal(newScrollLeft, scrollXPercentage);
1686 // containerCtrl.viewport[0].scrollLeft = newScrollLeft;
1687 containerCtrl.viewport[0].scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.viewport, newScrollLeft);
1689 containerCtrl.prevScrollLeft = newScrollLeft;
1691 if (containerCtrl.headerViewport) {
1692 // containerCtrl.headerViewport.scrollLeft = newScrollLeft;
1693 containerCtrl.headerViewport.scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.headerViewport, newScrollLeft);
1696 if (containerCtrl.footerViewport) {
1697 // containerCtrl.footerViewport.scrollLeft = newScrollLeft;
1698 containerCtrl.footerViewport.scrollLeft = GridUtil.denormalizeScrollLeft(containerCtrl.footerViewport, newScrollLeft);
1701 // uiGridCtrl.grid.options.offsetLeft = newScrollLeft;
1703 containerCtrl.prevScrollArgs.x.pixels = newScrollLeft - oldScrollLeft;
1707 // Scroll the render container viewport when the mousewheel is used
1708 $elm.bind('wheel mousewheel DomMouseScroll MozMousePixelScroll', function(evt) {
1710 evt.preventDefault();
1712 var newEvent = GridUtil.normalizeWheelEvent(evt);
1714 var args = { target: $elm };
1715 if (newEvent.deltaY !== 0) {
1716 var scrollYAmount = newEvent.deltaY * -120;
1718 // Get the scroll percentage
1719 var scrollYPercentage = (containerCtrl.viewport[0].scrollTop + scrollYAmount) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
1721 // Keep scrollPercentage within the range 0-1.
1722 if (scrollYPercentage < 0) { scrollYPercentage = 0; }
1723 else if (scrollYPercentage > 1) { scrollYPercentage = 1; }
1725 args.y = { percentage: scrollYPercentage, pixels: scrollYAmount };
1727 if (newEvent.deltaX !== 0) {
1728 var scrollXAmount = newEvent.deltaX * -120;
1730 // Get the scroll percentage
1731 var scrollLeft = GridUtil.normalizeScrollLeft(containerCtrl.viewport);
1732 var scrollXPercentage = (scrollLeft + scrollXAmount) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
1734 // Keep scrollPercentage within the range 0-1.
1735 if (scrollXPercentage < 0) { scrollXPercentage = 0; }
1736 else if (scrollXPercentage > 1) { scrollXPercentage = 1; }
1738 args.x = { percentage: scrollXPercentage, pixels: scrollXAmount };
1741 uiGridCtrl.fireScrollingEvent(args);
1748 scrollLeftStart = 0,
1753 function touchmove(event) {
1754 if (event.originalEvent) {
1755 event = event.originalEvent;
1758 event.preventDefault();
1760 var deltaX, deltaY, newX, newY;
1761 newX = event.targetTouches[0].screenX;
1762 newY = event.targetTouches[0].screenY;
1763 deltaX = -(newX - startX);
1764 deltaY = -(newY - startY);
1766 directionY = (deltaY < 1) ? -1 : 1;
1767 directionX = (deltaX < 1) ? -1 : 1;
1772 var args = { target: event.target };
1775 var scrollYPercentage = (scrollTopStart + deltaY) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
1777 if (scrollYPercentage > 1) { scrollYPercentage = 1; }
1778 else if (scrollYPercentage < 0) { scrollYPercentage = 0; }
1780 args.y = { percentage: scrollYPercentage, pixels: deltaY };
1783 var scrollXPercentage = (scrollLeftStart + deltaX) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
1785 if (scrollXPercentage > 1) { scrollXPercentage = 1; }
1786 else if (scrollXPercentage < 0) { scrollXPercentage = 0; }
1788 args.x = { percentage: scrollXPercentage, pixels: deltaX };
1791 uiGridCtrl.fireScrollingEvent(args);
1794 function touchend(event) {
1795 if (event.originalEvent) {
1796 event = event.originalEvent;
1799 event.preventDefault();
1801 $document.unbind('touchmove', touchmove);
1802 $document.unbind('touchend', touchend);
1803 $document.unbind('touchcancel', touchend);
1805 // Get the distance we moved on the Y axis
1806 var scrollTopEnd = containerCtrl.viewport[0].scrollTop;
1807 var scrollLeftEnd = containerCtrl.viewport[0].scrollTop;
1808 var deltaY = Math.abs(scrollTopEnd - scrollTopStart);
1809 var deltaX = Math.abs(scrollLeftEnd - scrollLeftStart);
1811 // Get the duration it took to move this far
1812 var moveDuration = (new Date()) - moveStart;
1814 // Scale the amount moved by the time it took to move it (i.e. quicker, longer moves == more scrolling after the move is over)
1815 var moveYScale = deltaY / moveDuration;
1816 var moveXScale = deltaX / moveDuration;
1818 var decelerateInterval = 63; // 1/16th second
1819 var decelerateCount = 8; // == 1/2 second
1820 var scrollYLength = 120 * directionY * moveYScale;
1821 var scrollXLength = 120 * directionX * moveXScale;
1823 function decelerate() {
1824 $timeout(function() {
1825 var args = { target: event.target };
1827 if (scrollYLength !== 0) {
1828 var scrollYPercentage = (containerCtrl.viewport[0].scrollTop + scrollYLength) / (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
1830 args.y = { percentage: scrollYPercentage, pixels: scrollYLength };
1833 if (scrollXLength !== 0) {
1834 var scrollXPercentage = (containerCtrl.viewport[0].scrollLeft + scrollXLength) / (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
1835 args.x = { percentage: scrollXPercentage, pixels: scrollXLength };
1838 uiGridCtrl.fireScrollingEvent(args);
1840 decelerateCount = decelerateCount -1;
1841 scrollYLength = scrollYLength / 2;
1842 scrollXLength = scrollXLength / 2;
1844 if (decelerateCount > 0) {
1848 uiGridCtrl.scrollbars.forEach(function (sbar) {
1849 sbar.removeClass('ui-grid-scrollbar-visible');
1850 sbar.removeClass('ui-grid-scrolling');
1853 }, decelerateInterval);
1859 if (GridUtil.isTouchEnabled()) {
1860 $elm.bind('touchstart', function (event) {
1861 if (event.originalEvent) {
1862 event = event.originalEvent;
1865 event.preventDefault();
1867 uiGridCtrl.scrollbars.forEach(function (sbar) {
1868 sbar.addClass('ui-grid-scrollbar-visible');
1869 sbar.addClass('ui-grid-scrolling');
1872 moveStart = new Date();
1873 startY = event.targetTouches[0].screenY;
1874 startX = event.targetTouches[0].screenX;
1875 scrollTopStart = containerCtrl.viewport[0].scrollTop;
1876 scrollLeftStart = containerCtrl.viewport[0].scrollLeft;
1878 $document.on('touchmove', touchmove);
1879 $document.on('touchend touchcancel', touchend);
1883 $elm.bind('$destroy', function() {
1885 $elm.unbind('keydown');
1887 ['touchstart', 'touchmove', 'touchend','keydown', 'wheel', 'mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'].forEach(function (eventName) {
1888 $elm.unbind(eventName);
1892 // TODO(c0bra): Handle resizing the inner canvas based on the number of elements
1896 var canvasWidth = colContainer.getCanvasWidth();
1897 var viewportWidth = colContainer.getViewportWidth();
1899 var canvasHeight = rowContainer.getCanvasHeight();
1900 var viewportHeight = rowContainer.getViewportHeight();
1902 var headerViewportWidth = colContainer.getHeaderViewportWidth();
1903 var footerViewportWidth = colContainer.getHeaderViewportWidth();
1905 // Set canvas dimensions
1906 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-canvas { width: ' + canvasWidth + 'px; height: ' + canvasHeight + 'px; }';
1907 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-canvas { width: ' + canvasWidth + 'px; }';
1909 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-viewport { width: ' + viewportWidth + 'px; height: ' + viewportHeight + 'px; }';
1910 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-header-viewport { width: ' + headerViewportWidth + 'px; }';
1912 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-canvas { width: ' + canvasWidth + 'px; }';
1913 ret += '\n .grid' + uiGridCtrl.grid.id + ' .ui-grid-render-container-' + $scope.containerId + ' .ui-grid-footer-viewport { width: ' + footerViewportWidth + 'px; }';
1919 uiGridCtrl.grid.registerStyleComputation({
1930 module.controller('uiGridRenderContainer', ['$scope', '$log', function ($scope, $log) {
1933 self.rowStyle = function (index) {
1934 var renderContainer = $scope.grid.renderContainers[$scope.containerId];
1938 if (!renderContainer.disableRowOffset) {
1939 if (index === 0 && self.currentTopRow !== 0) {
1940 // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
1941 var hiddenRowWidth = ($scope.rowContainer.currentTopRow) *
1942 $scope.rowContainer.visibleRowCache[$scope.rowContainer.currentTopRow].height;
1944 // return { 'margin-top': hiddenRowWidth + 'px' };
1945 styles['margin-top'] = hiddenRowWidth + 'px';
1949 if (!renderContainer.disableColumnOffset && $scope.colContainer.currentFirstColumn !== 0) {
1950 if ($scope.grid.isRTL()) {
1951 styles['margin-right'] = $scope.colContainer.columnOffset + 'px';
1954 styles['margin-left'] = $scope.colContainer.columnOffset + 'px';
1961 self.columnStyle = function (index) {
1962 var renderContainer = $scope.grid.renderContainers[$scope.containerId];
1966 if (!renderContainer.disableColumnOffset) {
1967 if (index === 0 && $scope.colContainer.currentFirstColumn !== 0) {
1968 var offset = $scope.colContainer.columnOffset;
1970 if ($scope.grid.isRTL()) {
1971 return { 'margin-right': offset + 'px' };
1974 return { 'margin-left': offset + 'px' };
1987 angular.module('ui.grid').directive('uiGridRow', ['$log', function($log) {
1991 // templateUrl: 'ui-grid/ui-grid-row',
1992 require: ['^uiGrid', '^uiGridRenderContainer'],
1995 //rowRenderIndex is added to scope to give the true visual index of the row to any directives that need it
1998 compile: function() {
2000 pre: function($scope, $elm, $attrs, controllers) {
2001 var uiGridCtrl = controllers[0];
2002 var containerCtrl = controllers[1];
2004 var grid = uiGridCtrl.grid;
2006 $scope.grid = uiGridCtrl.grid;
2007 $scope.colContainer = containerCtrl.colContainer;
2009 grid.getRowTemplateFn.then(function (templateFn) {
2010 templateFn($scope, function(clonedElement, scope) {
2011 $elm.replaceWith(clonedElement);
2015 post: function($scope, $elm, $attrs, controllers) {
2016 var uiGridCtrl = controllers[0];
2017 var containerCtrl = controllers[1];
2019 //add optional reference to externalScopes function to scope
2020 //so it can be retrieved in lower elements
2021 $scope.getExternalScopes = uiGridCtrl.getExternalScopes;
2034 * @name ui.grid.directive:uiGridStyle
2039 * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
2042 <doc:example module="app">
2045 var app = angular.module('app', ['ui.grid']);
2047 app.controller('MainCtrl', ['$scope', function ($scope) {
2048 $scope.myStyle = '.blah { border: 1px solid }';
2052 <div ng-controller="MainCtrl">
2053 <style ui-grid-style>{{ myStyle }}</style>
2054 <span class="blah">I am in a box.</span>
2058 it('should apply the right class to the element', function () {
2059 element(by.css('.blah')).getCssValue('border')
2061 expect(c).toContain('1px solid');
2069 angular.module('ui.grid').directive('uiGridStyle', ['$log', '$interpolate', function($log, $interpolate) {
2073 // require: '?^uiGrid',
2074 link: function($scope, $elm, $attrs, uiGridCtrl) {
2075 $log.debug('ui-grid-style link');
2076 // if (uiGridCtrl === undefined) {
2077 // $log.warn('[ui-grid-style link] uiGridCtrl is undefined!');
2080 var interpolateFn = $interpolate($elm.text(), true);
2082 if (interpolateFn) {
2083 $scope.$watch(interpolateFn, function(value) {
2088 // uiGridCtrl.recalcRowStyles = function() {
2089 // var offset = (scope.options.offsetTop || 0) - (scope.options.excessRows * scope.options.rowHeight);
2090 // var rowHeight = scope.options.rowHeight;
2093 // var rowStyleCount = uiGridCtrl.minRowsToRender() + (scope.options.excessRows * 2);
2094 // for (var i = 1; i <= rowStyleCount; i++) {
2095 // ret = ret + ' .grid' + scope.gridId + ' .ui-grid-row:nth-child(' + i + ') { top: ' + offset + 'px; }';
2096 // offset = offset + rowHeight;
2099 // scope.rowStyles = ret;
2102 // uiGridCtrl.styleComputions.push(uiGridCtrl.recalcRowStyles);
2112 angular.module('ui.grid').directive('uiGridViewport', ['$log', 'gridUtil',
2113 function($log, gridUtil) {
2117 templateUrl: 'ui-grid/uiGridViewport',
2118 require: ['^uiGrid', '^uiGridRenderContainer'],
2119 link: function($scope, $elm, $attrs, controllers) {
2120 $log.debug('viewport post-link');
2122 var uiGridCtrl = controllers[0];
2123 var containerCtrl = controllers[1];
2125 $scope.containerCtrl = containerCtrl;
2127 var rowContainer = containerCtrl.rowContainer;
2128 var colContainer = containerCtrl.colContainer;
2130 var grid = uiGridCtrl.grid;
2132 $scope.grid = uiGridCtrl.grid;
2134 // Put the containers in scope so we can get rows and columns from them
2135 $scope.rowContainer = containerCtrl.rowContainer;
2136 $scope.colContainer = containerCtrl.colContainer;
2138 // Register this viewport with its container
2139 containerCtrl.viewport = $elm;
2141 $elm.on('scroll', function (evt) {
2142 var newScrollTop = $elm[0].scrollTop;
2143 // var newScrollLeft = $elm[0].scrollLeft;
2144 var newScrollLeft = gridUtil.normalizeScrollLeft($elm);
2148 if (newScrollLeft !== colContainer.prevScrollLeft) {
2149 var xDiff = newScrollLeft - colContainer.prevScrollLeft;
2151 var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth());
2152 var horizScrollPercentage = newScrollLeft / horizScrollLength;
2154 colContainer.adjustScrollHorizontal(newScrollLeft, horizScrollPercentage);
2157 if (newScrollTop !== rowContainer.prevScrollTop) {
2158 var yDiff = newScrollTop - rowContainer.prevScrollTop;
2160 // uiGridCtrl.fireScrollingEvent({ y: { pixels: diff } });
2161 var vertScrollLength = (rowContainer.getCanvasHeight() - rowContainer.getViewportHeight());
2162 // var vertScrollPercentage = (uiGridCtrl.prevScrollTop + yDiff) / vertScrollLength;
2163 var vertScrollPercentage = newScrollTop / vertScrollLength;
2165 if (vertScrollPercentage > 1) { vertScrollPercentage = 1; }
2166 if (vertScrollPercentage < 0) { vertScrollPercentage = 0; }
2168 rowContainer.adjustScrollVertical(newScrollTop, vertScrollPercentage);
2179 angular.module('ui.grid')
2180 .directive('uiGridVisible', function uiGridVisibleAction() {
2181 return function ($scope, $elm, $attr) {
2182 $scope.$watch($attr.uiGridVisible, function (visible) {
2183 // $elm.css('visibility', visible ? 'visible' : 'hidden');
2184 $elm[visible ? 'removeClass' : 'addClass']('ui-grid-invisible');
2193 angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', '$log', 'gridUtil', '$q', 'uiGridConstants',
2194 '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile',
2195 function ($scope, $elm, $attrs, $log, gridUtil, $q, uiGridConstants,
2196 $templateCache, gridClassFactory, $timeout, $parse, $compile) {
2197 $log.debug('ui-grid controller');
2201 // Extend options with ui-grid attribute reference
2202 self.grid = gridClassFactory.createGrid($scope.uiGrid);
2203 $elm.addClass('grid' + self.grid.id);
2204 self.grid.rtl = $elm.css('direction') === 'rtl';
2207 //add optional reference to externalScopes function to controller
2208 //so it can be retrieved in lower elements that have isolate scope
2209 self.getExternalScopes = $scope.getExternalScopes;
2211 // angular.extend(self.grid.options, );
2213 //all properties of grid are available on scope
2214 $scope.grid = self.grid;
2216 if ($attrs.uiGridColumns) {
2217 $attrs.$observe('uiGridColumns', function(value) {
2218 self.grid.options.columnDefs = value;
2219 self.grid.buildColumns()
2221 self.grid.preCompileCellTemplates();
2223 self.grid.refreshCanvas(true);
2229 var dataWatchCollectionDereg;
2230 if (angular.isString($scope.uiGrid.data)) {
2231 dataWatchCollectionDereg = $scope.$parent.$watchCollection($scope.uiGrid.data, dataWatchFunction);
2234 dataWatchCollectionDereg = $scope.$parent.$watchCollection(function() { return $scope.uiGrid.data; }, dataWatchFunction);
2237 var columnDefWatchCollectionDereg = $scope.$parent.$watchCollection(function() { return $scope.uiGrid.columnDefs; }, columnDefsWatchFunction);
2239 function columnDefsWatchFunction(n, o) {
2241 self.grid.options.columnDefs = n;
2242 self.grid.buildColumns()
2245 self.grid.preCompileCellTemplates();
2247 self.grid.refreshCanvas(true);
2252 function dataWatchFunction(n) {
2253 // $log.debug('dataWatch fired');
2257 if (self.grid.columns.length === 0) {
2258 $log.debug('loading cols in dataWatchFunction');
2259 if (!$attrs.uiGridColumns && self.grid.options.columnDefs.length === 0) {
2260 self.grid.buildColumnDefsFromData(n);
2262 promises.push(self.grid.buildColumns()
2264 self.grid.preCompileCellTemplates();}
2267 $q.all(promises).then(function() {
2268 self.grid.modifyRows(n)
2270 // if (self.viewport) {
2271 self.grid.redrawInPlace();
2274 $scope.$evalAsync(function() {
2275 self.grid.refreshCanvas(true);
2283 $scope.$on('$destroy', function() {
2284 dataWatchCollectionDereg();
2285 columnDefWatchCollectionDereg();
2288 $scope.$watch(function () { return self.grid.styleComputations; }, function() {
2289 self.grid.refreshCanvas(true);
2295 //todo: throttle this event?
2296 self.fireScrollingEvent = function(args) {
2297 $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
2300 self.fireEvent = function(eventName, args) {
2301 // Add the grid to the event arguments if it's not there
2302 if (typeof(args) === 'undefined' || args === undefined) {
2306 if (typeof(args.grid) === 'undefined' || args.grid === undefined) {
2307 args.grid = self.grid;
2310 $scope.$broadcast(eventName, args);
2313 self.innerCompile = function innerCompile(elm) {
2314 $compile(elm)($scope);
2321 * @name ui.grid.directive:uiGrid
2324 * @param {Object} uiGrid Options for the grid to use
2325 * @param {Object=} external-scopes Add external-scopes='someScopeObjectYouNeed' attribute so you can access
2326 * your scopes from within any custom templatedirective. You access by $scope.getExternalScopes() function
2328 * @description Create a very basic grid.
2331 <example module="app">
2332 <file name="app.js">
2333 var app = angular.module('app', ['ui.grid']);
2335 app.controller('MainCtrl', ['$scope', function ($scope) {
2337 { name: 'Bob', title: 'CEO' },
2338 { name: 'Frank', title: 'Lowly Developer' }
2342 <file name="index.html">
2343 <div ng-controller="MainCtrl">
2344 <div ui-grid="{ data: data }"></div>
2349 angular.module('ui.grid').directive('uiGrid',
2364 templateUrl: 'ui-grid/ui-grid',
2367 getExternalScopes: '&?externalScopes' //optional functionwrapper around any needed external scope instances
2371 controller: 'uiGridController',
2372 compile: function () {
2374 post: function ($scope, $elm, $attrs, uiGridCtrl) {
2375 $log.debug('ui-grid postlink');
2377 var grid = uiGridCtrl.grid;
2379 // Initialize scrollbars (TODO: move to controller??)
2380 uiGridCtrl.scrollbars = [];
2382 //todo: assume it is ok to communicate that rendering is complete??
2383 grid.renderingComplete();
2385 grid.element = $elm;
2387 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
2389 // Default canvasWidth to the grid width, in case we don't get any column definitions to calculate it from
2390 grid.canvasWidth = uiGridCtrl.grid.gridWidth;
2392 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
2394 // 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
2395 if (grid.gridHeight < grid.options.rowHeight) {
2396 // Figure out the new height
2397 var newHeight = grid.options.minRowsToShow * grid.options.rowHeight;
2399 $elm.css('height', newHeight + 'px');
2401 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
2404 // Run initial canvas refresh
2405 grid.refreshCanvas();
2407 //add pinned containers for row headers support
2408 //moved from pinning feature
2409 var left = angular.element('<div ng-if="grid.hasLeftContainer()" style="width: 0" ui-grid-pinned-container="\'left\'"></div>');
2411 uiGridCtrl.innerCompile(left);
2413 var right = angular.element('<div ng-if="grid.hasRightContainer()" style="width: 0" ui-grid-pinned-container="\'right\'"></div>');
2415 uiGridCtrl.innerCompile(right);
2418 //if we add a left container after render, we need to watch and react
2419 $scope.$watch(function () { return grid.hasLeftContainer();}, function (newValue, oldValue) {
2420 if (newValue === oldValue) {
2424 //todo: remove this code. it was commented out after moving from pinning because body is already float:left
2425 // var bodyContainer = angular.element($elm[0].querySelectorAll('[container-id="body"]'));
2427 // bodyContainer.attr('style', 'float: left; position: inherit');
2430 // bodyContainer.attr('style', 'float: left; position: relative');
2433 grid.refreshCanvas(true);
2436 //if we add a right container after render, we need to watch and react
2437 $scope.$watch(function () { return grid.hasRightContainer();}, function (newValue, oldValue) {
2438 if (newValue === oldValue) {
2441 grid.refreshCanvas(true);
2445 // Resize the grid on window resize events
2446 function gridResize($event) {
2447 grid.gridWidth = $scope.gridWidth = gridUtil.elementWidth($elm);
2448 grid.gridHeight = $scope.gridHeight = gridUtil.elementHeight($elm);
2450 grid.queueRefresh();
2453 angular.element($window).on('resize', gridResize);
2455 // Unbind from window resize events when the grid is destroyed
2456 $elm.on('$destroy', function () {
2457 angular.element($window).off('resize', gridResize);
2471 angular.module('ui.grid').directive('uiGridPinnedContainer', ['$log', function ($log) {
2475 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>',
2477 side: '=uiGridPinnedContainer'
2480 compile: function compile() {
2482 post: function ($scope, $elm, $attrs, uiGridCtrl) {
2483 $log.debug('ui-grid-pinned-container ' + $scope.side + ' link');
2485 var grid = uiGridCtrl.grid;
2489 $elm.addClass('ui-grid-pinned-container-' + $scope.side);
2491 function updateContainerDimensions() {
2492 // $log.debug('update ' + $scope.side + ' dimensions');
2496 // Column containers
2497 if ($scope.side === 'left' || $scope.side === 'right') {
2498 var cols = grid.renderContainers[$scope.side].visibleColumnCache;
2500 for (var i = 0; i < cols.length; i++) {
2502 width += col.drawnWidth;
2507 // $log.debug('myWidth', myWidth);
2509 // TODO(c0bra): Subtract sum of col widths from grid viewport width and update it
2510 $elm.attr('style', null);
2512 var myHeight = grid.renderContainers.body.getViewportHeight(); // + grid.horizontalScrollbarHeight;
2514 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; } ';
2520 grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
2521 // Subtract our own width
2522 adjustment.width -= myWidth;
2527 // Register style computation to adjust for columns in `side`'s render container
2528 grid.registerStyleComputation({
2530 func: updateContainerDimensions
2540 angular.module('ui.grid')
2541 .factory('Grid', ['$log', '$q', '$compile', '$parse', 'gridUtil', 'uiGridConstants', 'GridOptions', 'GridColumn', 'GridRow', 'GridApi', 'rowSorter', 'rowSearcher', 'GridRenderContainer', '$timeout',
2542 function($log, $q, $compile, $parse, gridUtil, uiGridConstants, GridOptions, GridColumn, GridRow, GridApi, rowSorter, rowSearcher, GridRenderContainer, $timeout) {
2546 * @name ui.grid.core.api:PublicApi
2547 * @description Public Api for the core grid features
2554 * @name ui.grid.class:Grid
2555 * @description Grid is the main viewModel. Any properties or methods needed to maintain state are defined in
2556 * * this prototype. One instance of Grid is created per Grid directive instance.
2557 * @param {object} options Object map of options to pass into the grid. An 'id' property is expected.
2559 var Grid = function Grid(options) {
2561 // Get the id out of the options, then remove it
2562 if (options !== undefined && typeof(options.id) !== 'undefined' && options.id) {
2563 if (!/^[_a-zA-Z0-9-]+$/.test(options.id)) {
2564 throw new Error("Grid id '" + options.id + '" is invalid. It must follow CSS selector syntax rules.');
2568 throw new Error('No ID provided. An ID must be given when creating a grid.');
2571 self.id = options.id;
2574 // Get default options
2575 self.options = new GridOptions();
2577 // Extend the default options with what we were passed in
2578 angular.extend(self.options, options);
2580 self.headerHeight = self.options.headerRowHeight;
2581 self.footerHeight = self.options.showFooter === true ? self.options.footerRowHeight : 0;
2584 self.gridHeight = 0;
2586 self.columnBuilders = [];
2587 self.rowBuilders = [];
2588 self.rowsProcessors = [];
2589 self.columnsProcessors = [];
2590 self.styleComputations = [];
2591 self.viewportAdjusters = [];
2592 self.rowHeaderColumns = [];
2594 // self.visibleRowCache = [];
2596 // Set of 'render' containers for self grid, which can render sets of rows
2597 self.renderContainers = {};
2600 self.renderContainers.body = new GridRenderContainer('body', self);
2602 self.cellValueGetterCache = {};
2604 // Cached function to use with custom row templates
2605 self.getRowTemplateFn = null;
2608 //representation of the rows on the grid.
2609 //these are wrapped references to the actual data rows (options.data)
2612 //represents the columns on the grid
2617 * @name isScrollingVertically
2618 * @propertyOf ui.grid.class:Grid
2619 * @description set to true when Grid is scrolling vertically. Set to false via debounced method
2621 self.isScrollingVertically = false;
2625 * @name isScrollingHorizontally
2626 * @propertyOf ui.grid.class:Grid
2627 * @description set to true when Grid is scrolling horizontally. Set to false via debounced method
2629 self.isScrollingHorizontally = false;
2631 var debouncedVertical = gridUtil.debounce(function () {
2632 self.isScrollingVertically = false;
2635 var debouncedHorizontal = gridUtil.debounce(function () {
2636 self.isScrollingHorizontally = false;
2642 * @name flagScrollingVertically
2643 * @methodOf ui.grid.class:Grid
2644 * @description sets isScrollingVertically to true and sets it to false in a debounced function
2646 self.flagScrollingVertically = function() {
2647 self.isScrollingVertically = true;
2648 debouncedVertical();
2653 * @name flagScrollingHorizontally
2654 * @methodOf ui.grid.class:Grid
2655 * @description sets isScrollingHorizontally to true and sets it to false in a debounced function
2657 self.flagScrollingHorizontally = function() {
2658 self.isScrollingHorizontally = true;
2659 debouncedHorizontal();
2664 self.api = new GridApi(self);
2669 * @methodOf ui.grid.core.api:PublicApi
2670 * @description Refresh the rendered grid on screen.
2673 self.api.registerMethod( 'core', 'refresh', this.refresh );
2678 * @methodOf ui.grid.core.api:PublicApi
2679 * @description Refresh the rendered grid on screen? Note: not functional at present
2680 * @returns {promise} promise that is resolved when render completes?
2683 self.api.registerMethod( 'core', 'refreshRows', this.refreshRows );
2689 * @methodOf ui.grid.core.api:PublicApi
2690 * @description The sort criteria on one or more columns has
2691 * changed. Provides as parameters the grid and the output of
2692 * getColumnSorting, which is an array of gridColumns
2693 * that have sorting on them, sorted in priority order.
2695 * @param {Grid} grid the grid
2696 * @param {array} sortColumns an array of columns with
2697 * sorts on them, in priority order
2701 * gridApi.core.on.sortChanged( grid, sortColumns );
2704 self.api.registerEvent( 'core', 'sortChanged' );
2710 * @methodOf ui.grid.class:Grid
2711 * @description Returns true if grid is RightToLeft
2713 Grid.prototype.isRTL = function () {
2720 * @name registerColumnBuilder
2721 * @methodOf ui.grid.class:Grid
2722 * @description When the build creates columns from column definitions, the columnbuilders will be called to add
2723 * additional properties to the column.
2724 * @param {function(colDef, col, gridOptions)} columnsProcessor function to be called
2726 Grid.prototype.registerColumnBuilder = function registerColumnBuilder(columnBuilder) {
2727 this.columnBuilders.push(columnBuilder);
2732 * @name buildColumnDefsFromData
2733 * @methodOf ui.grid.class:Grid
2734 * @description Populates columnDefs from the provided data
2735 * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
2737 Grid.prototype.buildColumnDefsFromData = function (dataRows){
2738 this.options.columnDefs = gridUtil.getColumnsFromData(dataRows, this.options.excludeProperties);
2743 * @name registerRowBuilder
2744 * @methodOf ui.grid.class:Grid
2745 * @description When the build creates rows from gridOptions.data, the rowBuilders will be called to add
2746 * additional properties to the row.
2747 * @param {function(colDef, col, gridOptions)} rowBuilder function to be called
2749 Grid.prototype.registerRowBuilder = function registerRowBuilder(rowBuilder) {
2750 this.rowBuilders.push(rowBuilder);
2756 * @methodOf ui.grid.class:Grid
2757 * @description returns a grid column for the column name
2758 * @param {string} name column name
2760 Grid.prototype.getColumn = function getColumn(name) {
2761 var columns = this.columns.filter(function (column) {
2762 return column.colDef.name === name;
2764 return columns.length > 0 ? columns[0] : null;
2770 * @methodOf ui.grid.class:Grid
2771 * @description returns a grid colDef for the column name
2772 * @param {string} name column.field
2774 Grid.prototype.getColDef = function getColDef(name) {
2775 var colDefs = this.options.columnDefs.filter(function (colDef) {
2776 return colDef.name === name;
2778 return colDefs.length > 0 ? colDefs[0] : null;
2784 * @methodOf ui.grid.class:Grid
2785 * @description uses the first row of data to assign colDef.type for any types not defined.
2790 * @propertyOf ui.grid.class:GridOptions.columnDef
2791 * @description the type of the column, used in sorting. If not provided then the
2792 * grid will guess the type. Add this only if the grid guessing is not to your
2793 * satisfaction. Refer to {@link ui.grid.service:GridUtil.guessType gridUtil.guessType} for
2794 * a list of values the grid knows about.
2797 Grid.prototype.assignTypes = function(){
2799 self.options.columnDefs.forEach(function (colDef, index) {
2801 //Assign colDef type if not specified
2803 var col = new GridColumn(colDef, index, self);
2804 var firstRow = self.rows.length > 0 ? self.rows[0] : null;
2806 colDef.type = gridUtil.guessType(self.getCellValue(firstRow, col));
2809 $log.log('Unable to assign type from data, so defaulting to string');
2810 colDef.type = 'string';
2818 * @name addRowHeaderColumn
2819 * @methodOf ui.grid.class:Grid
2820 * @description adds a row header column to the grid
2821 * @param {object} column def
2823 Grid.prototype.addRowHeaderColumn = function addRowHeaderColumn(colDef) {
2825 //self.createLeftContainer();
2826 var rowHeaderCol = new GridColumn(colDef, self.rowHeaderColumns.length + 1, self);
2827 rowHeaderCol.isRowHeader = true;
2829 self.createRightContainer();
2830 rowHeaderCol.renderContainer = 'right';
2833 self.createLeftContainer();
2834 rowHeaderCol.renderContainer = 'left';
2837 self.columnBuilders[0](colDef,rowHeaderCol,self.gridOptions)
2839 rowHeaderCol.enableFiltering = false;
2840 rowHeaderCol.enableSorting = false;
2841 self.rowHeaderColumns.push(rowHeaderCol);
2847 * @name buildColumns
2848 * @methodOf ui.grid.class:Grid
2849 * @description creates GridColumn objects from the columnDefinition. Calls each registered
2850 * columnBuilder to further process the column
2851 * @returns {Promise} a promise to load any needed column resources
2853 Grid.prototype.buildColumns = function buildColumns() {
2854 $log.debug('buildColumns');
2856 var builderPromises = [];
2857 var offset = self.rowHeaderColumns.length;
2859 //add row header columns to the grid columns array
2860 angular.forEach(self.rowHeaderColumns, function (rowHeaderColumn) {
2862 self.columns.push(rowHeaderColumn);
2865 // Synchronize self.columns with self.options.columnDefs so that columns can also be removed.
2866 if (self.columns.length > self.options.columnDefs.length) {
2867 self.columns.forEach(function (column, index) {
2868 if (!self.getColDef(column.name)) {
2869 self.columns.splice(index, 1);
2874 self.options.columnDefs.forEach(function (colDef, index) {
2875 self.preprocessColDef(colDef);
2876 var col = self.getColumn(colDef.name);
2879 col = new GridColumn(colDef, index + offset, self);
2880 self.columns.push(col);
2883 col.updateColumnDef(colDef, col.index);
2886 self.columnBuilders.forEach(function (builder) {
2887 builderPromises.push(builder.call(self, colDef, col, self.options));
2891 return $q.all(builderPromises);
2896 * @name preCompileCellTemplates
2897 * @methodOf ui.grid.class:Grid
2898 * @description precompiles all cell templates
2900 Grid.prototype.preCompileCellTemplates = function() {
2901 this.columns.forEach(function (col) {
2902 var html = col.cellTemplate.replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
2904 var compiledElementFn = $compile(html);
2905 col.compiledElementFn = compiledElementFn;
2911 * @name createLeftContainer
2912 * @methodOf ui.grid.class:Grid
2913 * @description creates the left render container if it doesn't already exist
2915 Grid.prototype.createLeftContainer = function() {
2916 if (!this.hasLeftContainer()) {
2917 this.renderContainers.left = new GridRenderContainer('left', this, { disableColumnOffset: true });
2923 * @name createRightContainer
2924 * @methodOf ui.grid.class:Grid
2925 * @description creates the right render container if it doesn't already exist
2927 Grid.prototype.createRightContainer = function() {
2928 if (!this.hasRightContainer()) {
2929 this.renderContainers.right = new GridRenderContainer('right', this, { disableColumnOffset: true });
2935 * @name hasLeftContainer
2936 * @methodOf ui.grid.class:Grid
2937 * @description returns true if leftContainer exists
2939 Grid.prototype.hasLeftContainer = function() {
2940 return this.renderContainers.left !== undefined;
2945 * @name hasLeftContainer
2946 * @methodOf ui.grid.class:Grid
2947 * @description returns true if rightContainer exists
2949 Grid.prototype.hasRightContainer = function() {
2950 return this.renderContainers.right !== undefined;
2955 * undocumented function
2956 * @name preprocessColDef
2957 * @methodOf ui.grid.class:Grid
2958 * @description defaults the name property from field to maintain backwards compatibility with 2.x
2959 * validates that name or field is present
2961 Grid.prototype.preprocessColDef = function preprocessColDef(colDef) {
2962 if (!colDef.field && !colDef.name) {
2963 throw new Error('colDef.name or colDef.field property is required');
2966 //maintain backwards compatibility with 2.x
2967 //field was required in 2.x. now name is required
2968 if (colDef.name === undefined && colDef.field !== undefined) {
2969 colDef.name = colDef.field;
2974 // 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
2975 Grid.prototype.newInN = function newInN(o, n, oAccessor, nAccessor) {
2979 for (var i=0; i<n.length; i++) {
2980 var nV = nAccessor ? n[i][nAccessor] : n[i];
2983 for (var j=0; j<o.length; j++) {
2984 var oV = oAccessor ? o[j][oAccessor] : o[j];
2985 if (self.options.rowEquality(nV, oV)) {
3001 * @methodOf ui.grid.class:Grid
3002 * @description returns the GridRow that contains the rowEntity
3003 * @param {object} rowEntity the gridOptions.data array element instance
3005 Grid.prototype.getRow = function getRow(rowEntity) {
3006 var rows = this.rows.filter(function (row) {
3007 return row.entity === rowEntity;
3009 return rows.length > 0 ? rows[0] : null;
3016 * @methodOf ui.grid.class:Grid
3017 * @description creates or removes GridRow objects from the newRawData array. Calls each registered
3018 * rowBuilder to further process the row
3020 * Rows are identified using the gridOptions.rowEquality function
3022 Grid.prototype.modifyRows = function modifyRows(newRawData) {
3027 if (self.rows.length === 0 && newRawData.length > 0) {
3028 if (self.options.enableRowHashing) {
3029 if (!self.rowHashMap) {
3030 self.createRowHashMap();
3033 for (i=0; i<newRawData.length; i++) {
3034 newRow = newRawData[i];
3036 self.rowHashMap.put(newRow, {
3043 self.addRows(newRawData);
3044 //now that we have data, it is save to assign types to colDefs
3047 else if (newRawData.length > 0) {
3048 var unfoundNewRows, unfoundOldRows, unfoundNewRowsToFind;
3050 // If row hashing is turned on
3051 if (self.options.enableRowHashing) {
3052 // Array of new rows that haven't been found in the old rowset
3053 unfoundNewRows = [];
3054 // 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).
3055 unfoundNewRowsToFind = [];
3056 // Map of rows that have been found in the new rowset
3057 var foundOldRows = {};
3058 // Array of old rows that have NOT been found in the new rowset
3059 unfoundOldRows = [];
3061 // Create the row HashMap if it doesn't exist already
3062 if (!self.rowHashMap) {
3063 self.createRowHashMap();
3065 var rowhash = self.rowHashMap;
3067 // Make sure every new row has a hash
3068 for (i = 0; i < newRawData.length; i++) {
3069 newRow = newRawData[i];
3071 // Flag this row as needing to be manually found if it didn't come in with a $$hashKey
3072 var mustFind = false;
3073 if (!self.options.getRowIdentity(newRow)) {
3077 // See if the new row is already in the rowhash
3078 var found = rowhash.get(newRow);
3081 // See if it's already being used by as GridRow
3083 // If so, mark this new row as being found
3084 foundOldRows[self.options.rowIdentity(newRow)] = true;
3088 // Put the row in the hashmap with the index it corresponds to
3089 rowhash.put(newRow, {
3094 // This row has to be searched for manually in the old row set
3096 unfoundNewRowsToFind.push(newRow);
3099 unfoundNewRows.push(newRow);
3104 // Build the list of unfound old rows
3105 for (i = 0; i < self.rows.length; i++) {
3106 var row = self.rows[i];
3107 var hash = self.options.rowIdentity(row.entity);
3108 if (!foundOldRows[hash]) {
3109 unfoundOldRows.push(row);
3114 // Look for new rows
3115 var newRows = unfoundNewRows || [];
3117 // The unfound new rows is either `unfoundNewRowsToFind`, if row hashing is turned on, or straight `newRawData` if it isn't
3118 var unfoundNew = (unfoundNewRowsToFind || newRawData);
3120 // Search for real new rows in `unfoundNew` and concat them onto `newRows`
3121 newRows = newRows.concat(self.newInN(self.rows, unfoundNew, 'entity'));
3123 self.addRows(newRows);
3125 var deletedRows = self.getDeletedRows((unfoundOldRows || self.rows), newRawData);
3127 for (i = 0; i < deletedRows.length; i++) {
3128 if (self.options.enableRowHashing) {
3129 self.rowHashMap.remove(deletedRows[i].entity);
3132 self.rows.splice( self.rows.indexOf(deletedRows[i]), 1 );
3137 // Reset the row HashMap
3138 self.createRowHashMap();
3140 // Reset the rows length!
3141 self.rows.length = 0;
3144 var p1 = $q.when(self.processRowsProcessors(self.rows))
3145 .then(function (renderableRows) {
3146 return self.setVisibleRows(renderableRows);
3149 var p2 = $q.when(self.processColumnsProcessors(self.columns))
3150 .then(function (renderableColumns) {
3151 return self.setVisibleColumns(renderableColumns);
3154 return $q.all([p1, p2]);
3157 Grid.prototype.getDeletedRows = function(oldRows, newRows) {
3160 var olds = oldRows.filter(function (oldRow) {
3161 return !newRows.some(function (newItem) {
3162 return self.options.rowEquality(newItem, oldRow.entity);
3165 // var olds = self.newInN(newRows, oldRows, null, 'entity');
3166 // dump('olds', olds);
3171 * Private Undocumented Method
3173 * @methodOf ui.grid.class:Grid
3174 * @description adds the newRawData array of rows to the grid and calls all registered
3175 * rowBuilders. this keyword will reference the grid
3177 Grid.prototype.addRows = function addRows(newRawData) {
3180 var existingRowCount = self.rows.length;
3181 for (var i=0; i < newRawData.length; i++) {
3182 var newRow = self.processRowBuilders(new GridRow(newRawData[i], i + existingRowCount, self));
3184 if (self.options.enableRowHashing) {
3185 var found = self.rowHashMap.get(newRow.entity);
3191 self.rows.push(newRow);
3197 * @name processRowBuilders
3198 * @methodOf ui.grid.class:Grid
3199 * @description processes all RowBuilders for the gridRow
3200 * @param {GridRow} gridRow reference to gridRow
3201 * @returns {GridRow} the gridRow with all additional behavior added
3203 Grid.prototype.processRowBuilders = function processRowBuilders(gridRow) {
3206 self.rowBuilders.forEach(function (builder) {
3207 builder.call(self, gridRow, self.gridOptions);
3215 * @name registerStyleComputation
3216 * @methodOf ui.grid.class:Grid
3217 * @description registered a styleComputation function
3219 * If the function returns a value it will be appended into the grid's `<style>` block
3220 * @param {function($scope)} styleComputation function
3222 Grid.prototype.registerStyleComputation = function registerStyleComputation(styleComputationInfo) {
3223 this.styleComputations.push(styleComputationInfo);
3227 // NOTE (c0bra): We already have rowBuilders. I think these do exactly the same thing...
3228 // Grid.prototype.registerRowFilter = function(filter) {
3229 // // TODO(c0bra): validate filter?
3231 // this.rowFilters.push(filter);
3234 // Grid.prototype.removeRowFilter = function(filter) {
3235 // var idx = this.rowFilters.indexOf(filter);
3237 // if (typeof(idx) !== 'undefined' && idx !== undefined) {
3238 // this.rowFilters.slice(idx, 1);
3242 // Grid.prototype.processRowFilters = function(rows) {
3244 // self.rowFilters.forEach(function (filter) {
3245 // filter.call(self, rows);
3252 * @name registerRowsProcessor
3253 * @methodOf ui.grid.class:Grid
3254 * @param {function(renderableRows)} rows processor function
3255 * @returns {Array[GridRow]} Updated renderable rows
3258 Register a "rows processor" function. When the rows are updated,
3259 the grid calls each registered "rows processor", which has a chance
3260 to alter the set of rows (sorting, etc) as long as the count is not
3263 Grid.prototype.registerRowsProcessor = function registerRowsProcessor(processor) {
3264 if (!angular.isFunction(processor)) {
3265 throw 'Attempt to register non-function rows processor: ' + processor;
3268 this.rowsProcessors.push(processor);
3273 * @name removeRowsProcessor
3274 * @methodOf ui.grid.class:Grid
3275 * @param {function(renderableRows)} rows processor function
3276 * @description Remove a registered rows processor
3278 Grid.prototype.removeRowsProcessor = function removeRowsProcessor(processor) {
3279 var idx = this.rowsProcessors.indexOf(processor);
3281 if (typeof(idx) !== 'undefined' && idx !== undefined) {
3282 this.rowsProcessors.splice(idx, 1);
3287 * Private Undocumented Method
3288 * @name processRowsProcessors
3289 * @methodOf ui.grid.class:Grid
3290 * @param {Array[GridRow]} The array of "renderable" rows
3291 * @param {Array[GridColumn]} The array of columns
3292 * @description Run all the registered rows processors on the array of renderable rows
3294 Grid.prototype.processRowsProcessors = function processRowsProcessors(renderableRows) {
3297 // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
3298 var myRenderableRows = renderableRows.slice(0);
3300 // self.rowsProcessors.forEach(function (processor) {
3301 // myRenderableRows = processor.call(self, myRenderableRows, self.columns);
3303 // if (!renderableRows) {
3304 // throw "Processor at index " + i + " did not return a set of renderable rows";
3307 // if (!angular.isArray(renderableRows)) {
3308 // throw "Processor at index " + i + " did not return an array";
3314 // Return myRenderableRows with no processing if we have no rows processors
3315 if (self.rowsProcessors.length === 0) {
3316 return $q.when(myRenderableRows);
3319 // Counter for iterating through rows processors
3322 // Promise for when we're done with all the processors
3323 var finished = $q.defer();
3325 // This function will call the processor in self.rowsProcessors at index 'i', and then
3326 // when done will call the next processor in the list, using the output from the processor
3327 // at i as the argument for 'renderedRowsToProcess' on the next iteration.
3329 // If we're at the end of the list of processors, we resolve our 'finished' callback with
3331 function startProcessor(i, renderedRowsToProcess) {
3332 // Get the processor at 'i'
3333 var processor = self.rowsProcessors[i];
3335 // Call the processor, passing in the rows to process and the current columns
3336 // (note: it's wrapped in $q.when() in case the processor does not return a promise)
3337 return $q.when( processor.call(self, renderedRowsToProcess, self.columns) )
3338 .then(function handleProcessedRows(processedRows) {
3340 if (!processedRows) {
3341 throw "Processor at index " + i + " did not return a set of renderable rows";
3344 if (!angular.isArray(processedRows)) {
3345 throw "Processor at index " + i + " did not return an array";
3348 // Processor is done, increment the counter
3351 // If we're not done with the processors, call the next one
3352 if (i <= self.rowsProcessors.length - 1) {
3353 return startProcessor(i, processedRows);
3355 // We're done! Resolve the 'finished' promise
3357 finished.resolve(processedRows);
3362 // Start on the first processor
3363 startProcessor(0, myRenderableRows);
3365 return finished.promise;
3368 Grid.prototype.setVisibleRows = function setVisibleRows(rows) {
3369 // $log.debug('setVisibleRows');
3373 //var newVisibleRowCache = [];
3375 // Reset all the render container row caches
3376 for (var i in self.renderContainers) {
3377 var container = self.renderContainers[i];
3379 container.visibleRowCache.length = 0;
3382 // rows.forEach(function (row) {
3383 for (var ri = 0; ri < rows.length; ri++) {
3386 // If the row is visible
3388 // newVisibleRowCache.push(row);
3390 // If the row has a container specified
3391 if (typeof(row.renderContainer) !== 'undefined' && row.renderContainer) {
3392 self.renderContainers[row.renderContainer].visibleRowCache.push(row);
3394 // If not, put it into the body container
3396 self.renderContainers.body.visibleRowCache.push(row);
3404 * @name registerColumnsProcessor
3405 * @methodOf ui.grid.class:Grid
3406 * @param {function(renderableColumns)} rows processor function
3407 * @returns {Array[GridColumn]} Updated renderable columns
3410 Register a "columns processor" function. When the columns are updated,
3411 the grid calls each registered "columns processor", which has a chance
3412 to alter the set of columns, as long as the count is not modified.
3414 Grid.prototype.registerColumnsProcessor = function registerColumnsProcessor(processor) {
3415 if (!angular.isFunction(processor)) {
3416 throw 'Attempt to register non-function rows processor: ' + processor;
3419 this.columnsProcessors.push(processor);
3422 Grid.prototype.removeColumnsProcessor = function removeColumnsProcessor(processor) {
3423 var idx = this.columnsProcessors.indexOf(processor);
3425 if (typeof(idx) !== 'undefined' && idx !== undefined) {
3426 this.columnsProcessors.splice(idx, 1);
3430 Grid.prototype.processColumnsProcessors = function processColumnsProcessors(renderableColumns) {
3433 // Create a shallow copy of the rows so that we can safely sort them without altering the original grid.rows sort order
3434 var myRenderableColumns = renderableColumns.slice(0);
3436 // Return myRenderableRows with no processing if we have no rows processors
3437 if (self.columnsProcessors.length === 0) {
3438 return $q.when(myRenderableColumns);
3441 // Counter for iterating through rows processors
3444 // Promise for when we're done with all the processors
3445 var finished = $q.defer();
3447 // This function will call the processor in self.rowsProcessors at index 'i', and then
3448 // when done will call the next processor in the list, using the output from the processor
3449 // at i as the argument for 'renderedRowsToProcess' on the next iteration.
3451 // If we're at the end of the list of processors, we resolve our 'finished' callback with
3453 function startProcessor(i, renderedColumnsToProcess) {
3454 // Get the processor at 'i'
3455 var processor = self.columnsProcessors[i];
3457 // Call the processor, passing in the rows to process and the current columns
3458 // (note: it's wrapped in $q.when() in case the processor does not return a promise)
3459 return $q.when( processor.call(self, renderedColumnsToProcess, self.rows) )
3460 .then(function handleProcessedRows(processedColumns) {
3462 if (!processedColumns) {
3463 throw "Processor at index " + i + " did not return a set of renderable rows";
3466 if (!angular.isArray(processedColumns)) {
3467 throw "Processor at index " + i + " did not return an array";
3470 // Processor is done, increment the counter
3473 // If we're not done with the processors, call the next one
3474 if (i <= self.columnsProcessors.length - 1) {
3475 return startProcessor(i, myRenderableColumns);
3477 // We're done! Resolve the 'finished' promise
3479 finished.resolve(myRenderableColumns);
3484 // Start on the first processor
3485 startProcessor(0, myRenderableColumns);
3487 return finished.promise;
3490 Grid.prototype.setVisibleColumns = function setVisibleColumns(columns) {
3491 // $log.debug('setVisibleColumns');
3495 // Reset all the render container row caches
3496 for (var i in self.renderContainers) {
3497 var container = self.renderContainers[i];
3499 container.visibleColumnCache.length = 0;
3502 for (var ci = 0; ci < columns.length; ci++) {
3503 var column = columns[ci];
3505 // If the column is visible
3506 if (column.visible) {
3507 // If the column has a container specified
3508 if (typeof(column.renderContainer) !== 'undefined' && column.renderContainer) {
3509 self.renderContainers[column.renderContainer].visibleColumnCache.push(column);
3511 // If not, put it into the body container
3513 self.renderContainers.body.visibleColumnCache.push(column);
3521 * @name handleWindowResize
3522 * @methodOf ui.grid.class:Grid
3523 * @description Triggered when the browser window resizes; automatically resizes the grid
3525 Grid.prototype.handleWindowResize = function handleWindowResize($event) {
3528 self.gridWidth = gridUtil.elementWidth(self.element);
3529 self.gridHeight = gridUtil.elementHeight(self.element);
3531 self.queueRefresh();
3536 * @name queueRefresh
3537 * @methodOf ui.grid.class:Grid
3538 * @description todo: @c0bra can you document this method?
3540 Grid.prototype.queueRefresh = function queueRefresh() {
3542 if (self.refreshCanceller) {
3543 $timeout.cancel(self.refreshCanceller);
3546 self.refreshCanceller = $timeout(function () {
3547 self.refreshCanvas(true);
3550 self.refreshCanceller.then(function () {
3551 self.refreshCanceller = null;
3554 return self.refreshCanceller;
3560 * @methodOf ui.grid.class:Grid
3561 * @description calls each styleComputation function
3563 // TODO: this used to take $scope, but couldn't see that it was used
3564 Grid.prototype.buildStyles = function buildStyles() {
3565 // $log.debug('buildStyles');
3569 self.customStyles = '';
3571 self.styleComputations
3572 .sort(function(a, b) {
3573 if (a.priority === null) { return 1; }
3574 if (b.priority === null) { return -1; }
3575 if (a.priority === null && b.priority === null) { return 0; }
3576 return a.priority - b.priority;
3578 .forEach(function (compInfo) {
3579 // this used to provide $scope as a second parameter, but I couldn't find any
3580 // style builders that used it, so removed it as part of moving to grid from controller
3581 var ret = compInfo.func.call(self);
3583 if (angular.isString(ret)) {
3584 self.customStyles += '\n' + ret;
3590 Grid.prototype.minColumnsToRender = function minColumnsToRender() {
3592 var viewport = this.getViewportWidth();
3596 self.columns.forEach(function(col, i) {
3597 if (totalWidth < viewport) {
3598 totalWidth += col.drawnWidth;
3603 for (var j = i; j >= i - min; j--) {
3604 currWidth += self.columns[j].drawnWidth;
3606 if (currWidth < viewport) {
3615 Grid.prototype.getBodyHeight = function getBodyHeight() {
3616 // Start with the viewportHeight
3617 var bodyHeight = this.getViewportHeight();
3619 // Add the horizontal scrollbar height if there is one
3620 if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
3621 bodyHeight = bodyHeight + this.horizontalScrollbarHeight;
3627 // NOTE: viewport drawable height is the height of the grid minus the header row height (including any border)
3628 // TODO(c0bra): account for footer height
3629 Grid.prototype.getViewportHeight = function getViewportHeight() {
3632 var viewPortHeight = this.gridHeight - this.headerHeight - this.footerHeight;
3634 // Account for native horizontal scrollbar, if present
3635 if (typeof(this.horizontalScrollbarHeight) !== 'undefined' && this.horizontalScrollbarHeight !== undefined && this.horizontalScrollbarHeight > 0) {
3636 viewPortHeight = viewPortHeight - this.horizontalScrollbarHeight;
3639 var adjustment = self.getViewportAdjustment();
3641 viewPortHeight = viewPortHeight + adjustment.height;
3643 // $log.debug('viewPortHeight', viewPortHeight);
3645 return viewPortHeight;
3648 Grid.prototype.getViewportWidth = function getViewportWidth() {
3651 var viewPortWidth = this.gridWidth;
3653 if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
3654 viewPortWidth = viewPortWidth - this.verticalScrollbarWidth;
3657 var adjustment = self.getViewportAdjustment();
3659 viewPortWidth = viewPortWidth + adjustment.width;
3661 // $log.debug('getviewPortWidth', viewPortWidth);
3663 return viewPortWidth;
3666 Grid.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
3667 var viewPortWidth = this.getViewportWidth();
3669 if (typeof(this.verticalScrollbarWidth) !== 'undefined' && this.verticalScrollbarWidth !== undefined && this.verticalScrollbarWidth > 0) {
3670 viewPortWidth = viewPortWidth + this.verticalScrollbarWidth;
3673 return viewPortWidth;
3676 Grid.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
3677 this.viewportAdjusters.push(func);
3680 Grid.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
3681 var idx = this.viewportAdjusters.indexOf(func);
3683 if (typeof(idx) !== 'undefined' && idx !== undefined) {
3684 this.viewportAdjusters.splice(idx, 1);
3688 Grid.prototype.getViewportAdjustment = function getViewportAdjustment() {
3691 var adjustment = { height: 0, width: 0 };
3693 self.viewportAdjusters.forEach(function (func) {
3694 adjustment = func.call(this, adjustment);
3700 Grid.prototype.getVisibleRowCount = function getVisibleRowCount() {
3703 // this.rows.forEach(function (row) {
3704 // if (row.visible) {
3709 // return this.visibleRowCache.length;
3710 return this.renderContainers.body.visibleRowCache.length;
3713 Grid.prototype.getVisibleRows = function getVisibleRows() {
3714 return this.renderContainers.body.visibleRowCache;
3717 Grid.prototype.getVisibleColumnCount = function getVisibleColumnCount() {
3720 // this.rows.forEach(function (row) {
3721 // if (row.visible) {
3726 // return this.visibleRowCache.length;
3727 return this.renderContainers.body.visibleColumnCache.length;
3731 Grid.prototype.searchRows = function searchRows(renderableRows) {
3732 return rowSearcher.search(this, renderableRows, this.columns);
3735 Grid.prototype.sortByColumn = function sortByColumn(renderableRows) {
3736 return rowSorter.sort(this, renderableRows, this.columns);
3739 Grid.prototype.getCellValue = function getCellValue(row, col){
3742 if (!self.cellValueGetterCache[col.colDef.name]) {
3743 self.cellValueGetterCache[col.colDef.name] = $parse(row.getEntityQualifiedColField(col));
3746 return self.cellValueGetterCache[col.colDef.name](row);
3750 Grid.prototype.getNextColumnSortPriority = function getNextColumnSortPriority() {
3754 self.columns.forEach(function (col) {
3755 if (col.sort && col.sort.priority && col.sort.priority > p) {
3756 p = col.sort.priority;
3765 * @name resetColumnSorting
3766 * @methodOf ui.grid.class:Grid
3767 * @description Return the columns that the grid is currently being sorted by
3768 * @param {GridColumn} [excludedColumn] Optional GridColumn to exclude from having its sorting reset
3770 Grid.prototype.resetColumnSorting = function resetColumnSorting(excludeCol) {
3773 self.columns.forEach(function (col) {
3774 if (col !== excludeCol) {
3782 * @name getColumnSorting
3783 * @methodOf ui.grid.class:Grid
3784 * @description Return the columns that the grid is currently being sorted by
3785 * @returns {Array[GridColumn]} An array of GridColumn objects
3787 Grid.prototype.getColumnSorting = function getColumnSorting() {
3790 var sortedCols = [], myCols;
3792 // Iterate through all the columns, sorted by priority
3793 // Make local copy of column list, because sorting is in-place and we do not want to
3794 // change the original sequence of columns
3795 myCols = self.columns.slice(0);
3796 myCols.sort(rowSorter.prioritySort).forEach(function (col) {
3797 if (col.sort && typeof(col.sort.direction) !== 'undefined' && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
3798 sortedCols.push(col);
3808 * @methodOf ui.grid.class:Grid
3809 * @description Set the sorting on a given column, optionally resetting any existing sorting on the Grid.
3810 * Emits the sortChanged event whenever the sort criteria are changed.
3811 * @param {GridColumn} column Column to set the sorting on
3812 * @param {uiGridConstants.ASC|uiGridConstants.DESC} [direction] Direction to sort by, either descending or ascending.
3813 * If not provided, the column will iterate through the sort directions: ascending, descending, unsorted.
3814 * @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
3815 * by this column only
3816 * @returns {Promise} A resolved promise that supplies the column.
3819 Grid.prototype.sortColumn = function sortColumn(column, directionOrAdd, add) {
3823 if (typeof(column) === 'undefined' || !column) {
3824 throw new Error('No column parameter provided');
3827 // Second argument can either be a direction or whether to add this column to the existing sort.
3828 // If it's a boolean, it's an add, otherwise, it's a direction
3829 if (typeof(directionOrAdd) === 'boolean') {
3830 add = directionOrAdd;
3833 direction = directionOrAdd;
3837 self.resetColumnSorting(column);
3838 column.sort.priority = 0;
3841 column.sort.priority = self.getNextColumnSortPriority();
3845 // Figure out the sort direction
3846 if (column.sort.direction && column.sort.direction === uiGridConstants.ASC) {
3847 column.sort.direction = uiGridConstants.DESC;
3849 else if (column.sort.direction && column.sort.direction === uiGridConstants.DESC) {
3850 column.sort.direction = null;
3853 column.sort.direction = uiGridConstants.ASC;
3857 column.sort.direction = direction;
3860 self.api.core.raise.sortChanged( self, self.getColumnSorting() );
3862 return $q.when(column);
3866 * communicate to outside world that we are done with initial rendering
3868 Grid.prototype.renderingComplete = function(){
3869 if (angular.isFunction(this.options.onRegisterApi)) {
3870 this.options.onRegisterApi(this.api);
3872 this.api.core.raise.renderingComplete( this.api );
3875 Grid.prototype.createRowHashMap = function createRowHashMap() {
3878 var hashMap = new RowHashMap();
3879 hashMap.grid = self;
3881 self.rowHashMap = hashMap;
3888 * @methodOf ui.grid.class:Grid
3889 * @description Refresh the rendered grid on screen.
3892 Grid.prototype.refresh = function refresh() {
3893 $log.debug('grid refresh');
3897 var p1 = self.processRowsProcessors(self.rows).then(function (renderableRows) {
3898 self.setVisibleRows(renderableRows);
3901 var p2 = self.processColumnsProcessors(self.columns).then(function (renderableColumns) {
3902 self.setVisibleColumns(renderableColumns);
3905 return $q.all([p1, p2]).then(function () {
3906 self.redrawInPlace();
3908 self.refreshCanvas(true);
3915 * @methodOf ui.grid.class:Grid
3916 * @description Refresh the rendered rows on screen? Note: not functional at present
3917 * @returns {promise} promise that is resolved when render completes?
3920 Grid.prototype.refreshRows = function refreshRows() {
3923 return self.processRowsProcessors(self.rows)
3924 .then(function (renderableRows) {
3925 self.setVisibleRows(renderableRows);
3927 // TODO: this method doesn't exist, so clearly refreshRows doesn't work.
3930 self.refreshCanvas();
3936 * @name redrawCanvas
3937 * @methodOf ui.grid.class:Grid
3939 * @params {object} buildStyles optional parameter. Use TBD
3940 * @returns {promise} promise that is resolved when the canvas
3941 * has been refreshed
3944 Grid.prototype.refreshCanvas = function(buildStyles) {
3953 // Get all the header heights
3954 var containerHeadersToRecalc = [];
3955 for (var containerId in self.renderContainers) {
3956 if (self.renderContainers.hasOwnProperty(containerId)) {
3957 var container = self.renderContainers[containerId];
3959 if (container.header) {
3960 containerHeadersToRecalc.push(container);
3965 if (containerHeadersToRecalc.length > 0) {
3966 // Putting in a timeout as it's not calculating after the grid element is rendered and filled out
3967 $timeout(function() {
3968 // var oldHeaderHeight = self.grid.headerHeight;
3969 // self.grid.headerHeight = gridUtil.outerElementHeight(self.header);
3971 var rebuildStyles = false;
3973 // Get all the header heights
3974 for (var i = 0; i < containerHeadersToRecalc.length; i++) {
3975 var container = containerHeadersToRecalc[i];
3977 if (container.header) {
3978 var oldHeaderHeight = container.headerHeight;
3979 var headerHeight = gridUtil.outerElementHeight(container.header);
3980 container.headerHeight = headerHeight;
3982 if (oldHeaderHeight !== headerHeight) {
3983 rebuildStyles = true;
3988 // Rebuild styles if the header height has changed
3989 // The header height is used in body/viewport calculations and those are then used in other styles so we need it to be available
3990 if (buildStyles && rebuildStyles) {
3998 // Timeout still needs to be here to trigger digest after styles have been rebuilt
3999 $timeout(function() {
4010 * @name redrawCanvas
4011 * @methodOf ui.grid.class:Grid
4012 * @description Redraw the rows and columns based on our current scroll position
4015 Grid.prototype.redrawInPlace = function redrawInPlace() {
4016 // $log.debug('redrawInPlace');
4020 for (var i in self.renderContainers) {
4021 var container = self.renderContainers[i];
4023 // $log.debug('redrawing container', i);
4025 container.adjustRows(container.prevScrollTop, null);
4026 container.adjustColumns(container.prevScrollLeft, null);
4031 // Blatantly stolen from Angular as it isn't exposed (yet? 2.0?)
4032 function RowHashMap() {}
4034 RowHashMap.prototype = {
4036 * Store key value pair
4037 * @param key key to store can be any type
4038 * @param value value to store can be any type
4040 put: function(key, value) {
4041 this[this.grid.options.rowIdentity(key)] = value;
4046 * @returns {Object} the value for the key
4048 get: function(key) {
4049 return this[this.grid.options.rowIdentity(key)];
4053 * Remove the key/value pair
4056 remove: function(key) {
4057 var value = this[key = this.grid.options.rowIdentity(key)];
4073 angular.module('ui.grid')
4074 .factory('GridApi', ['$log', '$q', '$rootScope', 'gridUtil', 'uiGridConstants',
4075 function ($log, $q, $rootScope, gridUtil, uiGridConstants) {
4078 * @name ui.grid.class:GridApi
4079 * @description GridApi provides the ability to register public methods events inside the grid and allow
4080 * for other components to use the api via featureName.methodName and featureName.on.eventName(function(args){}
4081 * @param {object} grid grid that owns api
4083 var GridApi = function GridApi(grid) {
4085 this.listeners = [];
4089 * @name renderingComplete
4090 * @methodOf ui.grid.core.api:PublicApi
4091 * @description Rendering is complete, called at the same
4092 * time as `onRegisterApi`, but provides a way to obtain
4093 * that same event within features without stopping end
4094 * users from getting at the onRegisterApi method.
4096 * Included in gridApi so that it's always there - otherwise
4097 * there is still a timing problem with when a feature can
4100 * @param {GridApi} gridApi the grid api, as normally
4101 * returned in the onRegisterApi method
4105 * gridApi.core.on.renderingComplete( grid );
4108 this.registerEvent( 'core', 'renderingComplete' );
4113 * @name ui.grid.class:suppressEvents
4114 * @methodOf ui.grid.class:GridApi
4115 * @description Used to execute a function while disabling the specified event listeners.
4116 * Disables the listenerFunctions, executes the callbackFn, and then enables
4117 * the listenerFunctions again
4118 * @param {object} listenerFuncs listenerFunc or array of listenerFuncs to suppress. These must be the same
4119 * functions that were used in the .on.eventName method
4120 * @param {object} callBackFn function to execute
4123 * var navigate = function (newRowCol, oldRowCol){
4124 * //do something on navigate
4127 * gridApi.cellNav.on.navigate(scope,navigate);
4130 * //call the scrollTo event and suppress our navigate listener
4131 * //scrollTo will still raise the event for other listeners
4132 * gridApi.suppressEvents(navigate, function(){
4133 * gridApi.cellNav.scrollTo(aRow, aCol);
4138 GridApi.prototype.suppressEvents = function (listenerFuncs, callBackFn) {
4140 var listeners = angular.isArray(listenerFuncs) ? listenerFuncs : [listenerFuncs];
4142 //find all registered listeners
4143 var foundListeners = [];
4144 listeners.forEach(function (l) {
4145 foundListeners = self.listeners.filter(function (lstnr) {
4146 return l === lstnr.handler;
4150 //deregister all the listeners
4151 foundListeners.forEach(function(l){
4157 //reregister all the listeners
4158 foundListeners.forEach(function(l){
4159 l.dereg = registerEventWithAngular(l.scope, l.eventId, l.handler, self.grid);
4166 * @name registerEvent
4167 * @methodOf ui.grid.class:GridApi
4168 * @description Registers a new event for the given feature
4169 * @param {string} featureName name of the feature that raises the event
4170 * @param {string} eventName name of the event
4172 GridApi.prototype.registerEvent = function (featureName, eventName) {
4174 if (!self[featureName]) {
4175 self[featureName] = {};
4178 var feature = self[featureName];
4184 var eventId = self.grid.id + featureName + eventName;
4186 $log.log('Creating raise event method ' + featureName + '.raise.' + eventName);
4187 feature.raise[eventName] = function () {
4188 $rootScope.$broadcast.apply($rootScope, [eventId].concat(Array.prototype.slice.call(arguments)));
4191 $log.log('Creating on event method ' + featureName + '.on.' + eventName);
4192 feature.on[eventName] = function (scope, handler) {
4193 var dereg = registerEventWithAngular(scope, eventId, handler, self.grid);
4195 //track our listener so we can turn off and on
4196 var listener = {handler: handler, dereg: dereg, eventId: eventId, scope: scope};
4197 self.listeners.push(listener);
4199 //destroy tracking when scope is destroyed
4200 //wanted to remove the listener from the array but angular does
4201 //strange things in scope.$destroy so I could not access the listener array
4202 scope.$on('$destroy', function() {
4203 listener.dereg = null;
4204 listener.handler = null;
4205 listener.eventId = null;
4206 listener.scope = null;
4211 function registerEventWithAngular(scope, eventId, handler, grid) {
4212 return scope.$on(eventId, function (event) {
4213 var args = Array.prototype.slice.call(arguments);
4214 args.splice(0, 1); //remove evt argument
4215 handler.apply(grid.api, args);
4221 * @name registerEventsFromObject
4222 * @methodOf ui.grid.class:GridApi
4223 * @description Registers features and events from a simple objectMap.
4224 * eventObjectMap must be in this format (multiple features allowed)
4228 * eventNameOne:function(args){},
4229 * eventNameTwo:function(args){}
4233 * @param {object} eventObjectMap map of feature/event names
4235 GridApi.prototype.registerEventsFromObject = function (eventObjectMap) {
4238 angular.forEach(eventObjectMap, function (featProp, featPropName) {
4239 var feature = {name: featPropName, events: []};
4240 angular.forEach(featProp, function (prop, propName) {
4241 feature.events.push(propName);
4243 features.push(feature);
4246 features.forEach(function (feature) {
4247 feature.events.forEach(function (event) {
4248 self.registerEvent(feature.name, event);
4256 * @name registerMethod
4257 * @methodOf ui.grid.class:GridApi
4258 * @description Registers a new event for the given feature
4259 * @param {string} featureName name of the feature
4260 * @param {string} methodName name of the method
4261 * @param {object} callBackFn function to execute
4262 * @param {object} thisArg binds callBackFn 'this' to thisArg. Defaults to gridApi.grid
4264 GridApi.prototype.registerMethod = function (featureName, methodName, callBackFn, thisArg) {
4265 if (!this[featureName]) {
4266 this[featureName] = {};
4269 var feature = this[featureName];
4271 feature[methodName] = gridUtil.createBoundedWrapper(thisArg || this.grid, callBackFn);
4276 * @name registerMethodsFromObject
4277 * @methodOf ui.grid.class:GridApi
4278 * @description Registers features and methods from a simple objectMap.
4279 * eventObjectMap must be in this format (multiple features allowed)
4283 * methodNameOne:function(args){},
4284 * methodNameTwo:function(args){}
4286 * @param {object} eventObjectMap map of feature/event names
4287 * @param {object} thisArg binds this to thisArg for all functions. Defaults to gridApi.grid
4289 GridApi.prototype.registerMethodsFromObject = function (methodMap, thisArg) {
4292 angular.forEach(methodMap, function (featProp, featPropName) {
4293 var feature = {name: featPropName, methods: []};
4294 angular.forEach(featProp, function (prop, propName) {
4295 feature.methods.push({name: propName, fn: prop});
4297 features.push(feature);
4300 features.forEach(function (feature) {
4301 feature.methods.forEach(function (method) {
4302 self.registerMethod(feature.name, method.name, method.fn, thisArg);
4316 angular.module('ui.grid')
4317 .factory('GridColumn', ['gridUtil', 'uiGridConstants', function(gridUtil, uiGridConstants) {
4321 * @name ui.grid.class:GridColumn
4322 * @description Represents the viewModel for each column. Any state or methods needed for a Grid Column
4323 * are defined on this prototype
4324 * @param {ColDef} colDef Column definition.
4325 * @param {number} index the current position of the column in the array
4326 * @param {Grid} grid reference to the grid
4330 * ******************************************************************************************
4331 * PaulL1: Ugly hack here in documentation. These properties are clearly properties of GridColumn,
4332 * and need to be noted as such for those extending and building ui-grid itself.
4333 * However, from an end-developer perspective, they interact with all these through columnDefs,
4334 * and they really need to be documented there. I feel like they're relatively static, and
4335 * I can't find an elegant way for ngDoc to reference to both....so I've duplicated each
4336 * comment block. Ugh.
4343 * @propertyOf ui.grid.class:GridColumn
4344 * @description (mandatory) each column should have a name, although for backward
4345 * compatibility with 2.x name can be omitted if field is present
4352 * @propertyOf ui.grid.class:GridOptions.columnDef
4353 * @description (mandatory) each column should have a name, although for backward
4354 * compatibility with 2.x name can be omitted if field is present
4361 * @propertyOf ui.grid.class:GridColumn
4362 * @description Column name that will be shown in the header. If displayName is not
4363 * provided then one is generated using the name.
4370 * @propertyOf ui.grid.class:GridOptions.columnDef
4371 * @description Column name that will be shown in the header. If displayName is not
4372 * provided then one is generated using the name.
4379 * @propertyOf ui.grid.class:GridColumn
4380 * @description field must be provided if you wish to bind to a
4381 * property in the data source. Should be an angular expression that evaluates against grid.options.data
4382 * array element. Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
4383 * See the angular docs on binding expressions.
4390 * @propertyOf ui.grid.class:GridOptions.columnDef
4391 * @description field must be provided if you wish to bind to a
4392 * property in the data source. Should be an angular expression that evaluates against grid.options.data
4393 * array element. Can be a complex expression: <code>employee.address.city</code>, or can be a function: <code>employee.getFullAddress()</code>.
4394 * See the angular docs on binding expressions.
4401 * @propertyOf ui.grid.class:GridColumn
4402 * @description Filter on this column.
4404 * <pre>{ term: 'text', condition: uiGridConstants.filter.STARTS_WITH, placeholder: 'type to filter...' }</pre>
4411 * @propertyOf ui.grid.class:GridOptions.columnDef
4412 * @description Specify a single filter field on this column.
4414 * <pre>$scope.gridOptions.columnDefs = [
4418 * condition: uiGridConstants.filter.STARTS_WITH,
4419 * placeholder: 'starts with...'
4427 function GridColumn(colDef, index, grid) {
4431 colDef.index = index;
4433 self.updateColumnDef(colDef);
4436 GridColumn.prototype.setPropertyOrDefault = function (colDef, propName, defaultValue) {
4439 // Use the column definition filter if we were passed it
4440 if (typeof(colDef[propName]) !== 'undefined' && colDef[propName]) {
4441 self[propName] = colDef[propName];
4443 // Otherwise use our own if it's set
4444 else if (typeof(self[propName]) !== 'undefined') {
4445 self[propName] = self[propName];
4447 // Default to empty object for the filter
4449 self[propName] = defaultValue ? defaultValue : {};
4458 * @propertyOf ui.grid.class:GridOptions.columnDef
4459 * @description sets the column width. Can be either
4460 * a number or a percentage, or an * for auto.
4462 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', width: 100},
4463 * { field: 'field2', width: '20%'},
4464 * { field: 'field3', width: '*' }]; </pre>
4471 * @propertyOf ui.grid.class:GridOptions.columnDef
4472 * @description sets the minimum column width. Should be a number.
4474 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', minWidth: 100}]; </pre>
4481 * @propertyOf ui.grid.class:GridOptions.columnDef
4482 * @description sets the maximum column width. Should be a number.
4484 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', maxWidth: 100}]; </pre>
4491 * @propertyOf ui.grid.class:GridOptions.columnDef
4492 * @description sets whether or not the column is visible
4493 * </br>Default is true
4495 * <pre> $scope.gridOptions.columnDefs = [
4496 * { field: 'field1', visible: true},
4497 * { field: 'field2', visible: false }
4505 * @propertyOf ui.grid.class:GridOptions.columnDef
4506 * @description Can be used to set the sort direction for the column, values are
4507 * uiGridConstants.ASC or uiGridConstants.DESC
4509 * <pre> $scope.gridOptions.columnDefs = [ { field: 'field1', sort: { direction: uiGridConstants.ASC }}] </pre>
4515 * @name sortingAlgorithm
4516 * @propertyOf ui.grid.class:GridColumn
4517 * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
4518 * like any normal sorting function.
4524 * @name sortingAlgorithm
4525 * @propertyOf ui.grid.class:GridOptions.columnDef
4526 * @description Algorithm to use for sorting this column. Takes 'a' and 'b' parameters
4527 * like any normal sorting function.
4534 * @propertyOf ui.grid.class:GridOptions.columnDef
4535 * @description Specify multiple filter fields.
4537 * <pre>$scope.gridOptions.columnDefs = [
4539 * field: 'field1', filters: [
4541 * condition: uiGridConstants.filter.STARTS_WITH,
4542 * placeholder: 'starts with...'
4545 * condition: uiGridConstants.filter.ENDS_WITH,
4546 * placeholder: 'ends with...'
4558 * @propertyOf ui.grid.class:GridColumn
4559 * @description Filters for this column. Includes 'term' property bound to filter input elements.
4563 * term: 'foo', // ngModel for <input>
4564 * condition: uiGridConstants.filter.STARTS_WITH,
4565 * placeholder: 'starts with...'
4569 * condition: uiGridConstants.filter.ENDS_WITH,
4570 * placeholder: 'ends with...'
4580 * @propertyOf ui.grid.class:GridOptions.columnDef
4581 * @description used to add menu items to a column. Refer to the tutorial on this
4584 * <pre> $scope.gridOptions.columnDefs = [
4585 * { field: 'field1', menuItems: [
4587 * title: 'Outer Scope Alert',
4588 * icon: 'ui-grid-icon-info-circled',
4589 * action: function($event) {
4590 * this.context.blargh(); // $scope.blargh() would work too, this is just an example
4596 * action: function() {
4597 * alert('Grid ID: ' + this.grid.id);
4603 GridColumn.prototype.updateColumnDef = function(colDef, index) {
4606 self.colDef = colDef;
4608 //position of column
4609 self.index = (typeof(index) === 'undefined') ? colDef.index : index;
4611 if (colDef.name === undefined) {
4612 throw new Error('colDef.name is required for column at index ' + self.index);
4615 var parseErrorMsg = "Cannot parse column width '" + colDef.width + "' for column named '" + colDef.name + "'";
4617 // If width is not defined, set it to a single star
4618 if (gridUtil.isNullOrUndefined(colDef.width)) {
4622 // If the width is not a number
4623 if (!angular.isNumber(colDef.width)) {
4624 // See if it ends with a percent
4625 if (gridUtil.endsWith(colDef.width, '%')) {
4626 // If so we should be able to parse the non-percent-sign part to a number
4627 var percentStr = colDef.width.replace(/%/g, '');
4628 var percent = parseInt(percentStr, 10);
4629 if (isNaN(percent)) {
4630 throw new Error(parseErrorMsg);
4632 self.width = colDef.width;
4634 // And see if it's a number string
4635 else if (colDef.width.match(/^(\d+)$/)) {
4636 self.width = parseInt(colDef.width.match(/^(\d+)$/)[1], 10);
4638 // Otherwise it should be a string of asterisks
4639 else if (!colDef.width.match(/^\*+$/)) {
4640 throw new Error(parseErrorMsg);
4643 // Is a number, use it as the width
4645 self.width = colDef.width;
4649 // Remove this column from the grid sorting
4650 GridColumn.prototype.unsort = function () {
4654 self.minWidth = !colDef.minWidth ? 50 : colDef.minWidth;
4655 self.maxWidth = !colDef.maxWidth ? 9000 : colDef.maxWidth;
4657 //use field if it is defined; name if it is not
4658 self.field = (colDef.field === undefined) ? colDef.name : colDef.field;
4660 // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
4661 self.displayName = (colDef.displayName === undefined) ? gridUtil.readableColumnName(colDef.name) : colDef.displayName;
4663 //self.originalIndex = index;
4665 self.aggregationType = angular.isDefined(colDef.aggregationType) ? colDef.aggregationType : null;
4666 self.footerCellTemplate = angular.isDefined(colDef.footerCellTemplate) ? colDef.footerCellTemplate : null;
4671 * @propertyOf ui.grid.class:GridOptions.columnDef
4672 * @description cellClass can be a string specifying the class to append to a cell
4673 * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name
4676 self.cellClass = colDef.cellClass;
4682 * @propertyOf ui.grid.class:GridOptions.columnDef
4683 * @description cellFilter is a filter to apply to the content of each cell
4686 * gridOptions.columnDefs[0].cellFilter = 'date'
4689 self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
4693 * @name headerCellFilter
4694 * @propertyOf ui.grid.class:GridOptions.columnDef
4695 * @description headerCellFilter is a filter to apply to the content of the column header
4698 * gridOptions.columnDefs[0].headerCellFilter = 'translate'
4701 self.headerCellFilter = colDef.headerCellFilter ? colDef.headerCellFilter : "";
4703 self.visible = gridUtil.isNullOrUndefined(colDef.visible) || colDef.visible;
4705 self.headerClass = colDef.headerClass;
4706 //self.cursor = self.sortable ? 'pointer' : 'default';
4708 self.visible = true;
4710 // Turn on sorting by default
4711 self.enableSorting = typeof(colDef.enableSorting) !== 'undefined' ? colDef.enableSorting : true;
4712 self.sortingAlgorithm = colDef.sortingAlgorithm;
4714 // Turn on filtering by default (it's disabled by default at the Grid level)
4715 self.enableFiltering = typeof(colDef.enableFiltering) !== 'undefined' ? colDef.enableFiltering : true;
4717 // self.menuItems = colDef.menuItems;
4718 self.setPropertyOrDefault(colDef, 'menuItems', []);
4720 // Use the column definition sort if we were passed it
4721 self.setPropertyOrDefault(colDef, 'sort');
4723 // Set up default filters array for when one is not provided.
4724 // In other words, this (in column def):
4726 // filter: { term: 'something', flags: {}, condition: [CONDITION] }
4728 // is just shorthand for this:
4730 // filters: [{ term: 'something', flags: {}, condition: [CONDITION] }]
4732 var defaultFilters = [];
4733 if (colDef.filter) {
4734 defaultFilters.push(colDef.filter);
4736 else if (self.enableFiltering && self.grid.options.enableFiltering) {
4737 // Add an empty filter definition object, which will
4738 // translate to a guessed condition and no pre-populated
4739 // value for the filter <input>.
4740 defaultFilters.push({});
4745 * @name ui.grid.class:GridOptions.columnDef.filter
4746 * @propertyOf ui.grid.class:GridOptions.columnDef
4747 * @description An object defining filtering for a column.
4753 * @propertyOf ui.grid.class:GridOptions.columnDef.filter
4754 * @description Defines how rows are chosen as matching the filter term. This can be set to
4755 * one of the constants in uiGridConstants.filter, or you can supply a custom filter function
4756 * that gets passed the following arguments: [searchTerm, cellValue, row, column].
4762 * @propertyOf ui.grid.class:GridOptions.columnDef.filter
4763 * @description If set, the filter field will be pre-populated
4770 * @propertyOf ui.grid.class:GridOptions.columnDef.filter
4771 * @description String that will be set to the <input>.placeholder attribute.
4779 condition: uiGridConstants.filter.CONTAINS,
4780 placeholder: 'my placeholder',
4789 self.setPropertyOrDefault(colDef, 'filter');
4790 self.setPropertyOrDefault(colDef, 'filters', defaultFilters);
4799 * @methodOf ui.grid.class:GridColumn
4800 * @description Returns the class name for the column
4801 * @param {bool} prefixDot if true, will return .className instead of className
4803 GridColumn.prototype.getColClass = function (prefixDot) {
4804 var cls = uiGridConstants.COL_CLASS_PREFIX + this.index;
4806 return prefixDot ? '.' + cls : cls;
4811 * @name getColClassDefinition
4812 * @methodOf ui.grid.class:GridColumn
4813 * @description Returns the class definition for th column
4815 GridColumn.prototype.getColClassDefinition = function () {
4816 return ' .grid' + this.grid.id + ' ' + this.getColClass(true) + ' { width: ' + this.drawnWidth + 'px; }';
4821 * @name getRenderContainer
4822 * @methodOf ui.grid.class:GridColumn
4823 * @description Returns the render container object that this column belongs to.
4825 * Columns will be default be in the `body` render container if they aren't allocated to one specifically.
4827 GridColumn.prototype.getRenderContainer = function getRenderContainer() {
4830 var containerId = self.renderContainer;
4832 if (containerId === null || containerId === '' || containerId === undefined) {
4833 containerId = 'body';
4836 return self.grid.renderContainers[containerId];
4842 * @methodOf ui.grid.class:GridColumn
4843 * @description Makes the column visible by setting colDef.visible = true
4845 GridColumn.prototype.showColumn = function() {
4846 this.colDef.visible = true;
4852 * @methodOf ui.grid.class:GridColumn
4853 * @description Hides the column by setting colDef.visible = false
4855 GridColumn.prototype.hideColumn = function() {
4856 this.colDef.visible = false;
4861 * @name getAggregationValue
4862 * @methodOf ui.grid.class:GridColumn
4863 * @description gets the aggregation value based on the aggregation type for this column
4865 GridColumn.prototype.getAggregationValue = function () {
4868 var visibleRows = self.grid.getVisibleRows();
4869 var cellValues = [];
4870 angular.forEach(visibleRows, function (row) {
4871 var cellValue = self.grid.getCellValue(row, self);
4872 if (angular.isNumber(cellValue)) {
4873 cellValues.push(cellValue);
4876 if (angular.isFunction(self.aggregationType)) {
4877 return self.aggregationType(visibleRows, self);
4879 else if (self.aggregationType === uiGridConstants.aggregationTypes.count) {
4880 //TODO: change to i18n
4881 return 'total rows: ' + self.grid.getVisibleRowCount();
4883 else if (self.aggregationType === uiGridConstants.aggregationTypes.sum) {
4884 angular.forEach(cellValues, function (value) {
4887 //TODO: change to i18n
4888 return 'total: ' + result;
4890 else if (self.aggregationType === uiGridConstants.aggregationTypes.avg) {
4891 angular.forEach(cellValues, function (value) {
4894 result = result / cellValues.length;
4895 //TODO: change to i18n
4896 return 'avg: ' + result;
4898 else if (self.aggregationType === uiGridConstants.aggregationTypes.min) {
4899 return 'min: ' + Math.min.apply(null, cellValues);
4901 else if (self.aggregationType === uiGridConstants.aggregationTypes.max) {
4902 return 'max: ' + Math.max.apply(null, cellValues);
4916 angular.module('ui.grid')
4917 .factory('GridOptions', ['gridUtil', function(gridUtil) {
4921 * @name ui.grid.class:GridOptions
4922 * @description Default GridOptions class. GridOptions are defined by the application developer and overlaid
4923 * over this object. Setting gridOptions within your controller is the most common method for an application
4924 * developer to configure the behaviour of their ui-grid
4926 * @example To define your gridOptions within your controller:
4927 * <pre>$scope.gridOptions = {
4928 * data: $scope.myData,
4930 * { name: 'field1', displayName: 'pretty display name' },
4931 * { name: 'field2', visible: false }
4935 * You can then use this within your html template, when you define your grid:
4936 * <pre><div ui-grid="gridOptions"></div></pre>
4938 * To provide default options for all of the grids within your application, use an angular
4939 * decorator to modify the GridOptions factory.
4940 * <pre>app.config(function($provide){
4941 * $provide.decorator('GridOptions',function($delegate){
4942 * return function(){
4943 * var defaultOptions = new $delegate();
4944 * defaultOptions.excludeProperties = ['id' ,'$$hashKey'];
4945 * return defaultOptions;
4950 function GridOptions() {
4952 this.onRegisterApi = angular.noop();
4957 * @propertyOf ui.grid.class:GridOptions
4958 * @description (mandatory) Array of data to be rendered into the grid, providing the data source or data binding for
4959 * the grid. The most common case is an array of objects, where each object has a number of attributes.
4960 * Each attribute automatically becomes a column in your grid. This array could, for example, be sourced from
4961 * an angularJS $resource query request. The array can also contain complex objects.
4969 * @propertyOf ui.grid.class:GridOptions
4970 * @description Array of columnDef objects. Only required property is name.
4971 * The individual options available in columnDefs are documented in the
4972 * {@link ui.grid.class:GridOptions.columnDef columnDef} section
4973 * </br>_field property can be used in place of name for backwards compatibility with 2.x_
4976 * <pre>var columnDefs = [{name:'field1'}, {name:'field2'}];</pre>
4979 this.columnDefs = [];
4983 * @name ui.grid.class:GridOptions.columnDef
4984 * @description Definition / configuration of an individual column, which would typically be
4985 * one of many column definitions within the gridOptions.columnDefs array
4987 * <pre>{name:'field1', field: 'field1', filter: { term: 'xxx' }}</pre>
4994 * @name excludeProperties
4995 * @propertyOf ui.grid.class:GridOptions
4996 * @description Array of property names in data to ignore when auto-generating column names. Provides the
4997 * inverse of columnDefs - columnDefs is a list of columns to include, excludeProperties is a list of columns
5000 * If columnDefs is defined, this will be ignored.
5002 * Defaults to ['$$hashKey']
5005 this.excludeProperties = ['$$hashKey'];
5009 * @name enableRowHashing
5010 * @propertyOf ui.grid.class:GridOptions
5011 * @description True by default. When enabled, this setting allows uiGrid to add
5012 * `$$hashKey`-type properties (similar to Angular) to elements in the `data` array. This allows
5013 * the grid to maintain state while vastly speeding up the process of altering `data` by adding/moving/removing rows.
5015 * Note that this DOES add properties to your data that you may not want, but they are stripped out when using `angular.toJson()`. IF
5016 * 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
5017 * and are altering the data set often.
5019 this.enableRowHashing = true;
5024 * @methodOf ui.grid.class:GridOptions
5025 * @description This function is used to get and, if necessary, set the value uniquely identifying this row.
5027 * By default it returns the `$$hashKey` property if it exists. If it doesn't it uses gridUtil.nextUid() to generate one
5029 this.rowIdentity = function rowIdentity(row) {
5030 return gridUtil.hashKey(row);
5035 * @name getRowIdentity
5036 * @methodOf ui.grid.class:GridOptions
5037 * @description This function returns the identity value uniquely identifying this row.
5039 * By default it returns the `$$hashKey` property but can be overridden to use any property or set of properties you want.
5041 this.getRowIdentity = function getRowIdentity(row) {
5042 return row.$$hashKey;
5045 this.headerRowHeight = 30;
5046 this.rowHeight = 30;
5047 this.maxVisibleRowCount = 200;
5051 * @name minRowsToShow
5052 * @propertyOf ui.grid.class:GridOptions
5053 * @description Minimum number of rows to show when the grid doesn't have a defined height. Defaults to "10".
5055 this.minRowsToShow = 10;
5057 this.showFooter = false;
5058 this.footerRowHeight = 30;
5060 this.columnWidth = 50;
5061 this.maxVisibleColumnCount = 200;
5063 // Turn virtualization on when number of data elements goes over this number
5064 this.virtualizationThreshold = 20;
5066 this.columnVirtualizationThreshold = 10;
5068 // Extra rows to to render outside of the viewport
5069 this.excessRows = 4;
5070 this.scrollThreshold = 4;
5072 // Extra columns to to render outside of the viewport
5073 this.excessColumns = 4;
5074 this.horizontalScrollThreshold = 2;
5078 * @name enableSorting
5079 * @propertyOf ui.grid.class:GridOptions
5080 * @description True by default. When enabled, this setting adds sort
5081 * widgets to the column headers, allowing sorting of the data for the entire grid.
5082 * Sorting can then be disabled on individual columns using the columnDefs.
5084 this.enableSorting = true;
5088 * @name enableFiltering
5089 * @propertyOf ui.grid.class:GridOptions
5090 * @description False by default. When enabled, this setting adds filter
5091 * boxes to each column header, allowing filtering within the column for the entire grid.
5092 * Filtering can then be disabled on individual columns using the columnDefs.
5094 this.enableFiltering = false;
5098 * @name enableColumnMenu
5099 * @propertyOf ui.grid.class:GridOptions
5100 * @description True by default. When enabled, this setting displays a column
5101 * menu within each column.
5103 this.enableColumnMenu = true;
5107 * @name enableScrollbars
5108 * @propertyOf ui.grid.class:GridOptions
5109 * @description True by default. When enabled, this settings enable vertical
5110 * and horizontal scrollbar for grid.
5112 this.enableScrollbars = true;
5114 // Columns can't be smaller than 10 pixels
5115 this.minimumColumnSize = 10;
5120 * @methodOf ui.grid.class:GridOptions
5121 * @description By default, rows are compared using object equality. This option can be overridden
5122 * to compare on any data item property or function
5123 * @param {object} entityA First Data Item to compare
5124 * @param {object} entityB Second Data Item to compare
5126 this.rowEquality = function(entityA, entityB) {
5127 return entityA === entityB;
5132 * @name headerTemplate
5133 * @propertyOf ui.grid.class:GridOptions
5134 * @description Null by default. When provided, this setting uses a custom header
5135 * template, rather than the default template. Can be set to either the name of a template file:
5136 * <pre> $scope.gridOptions.headerTemplate = 'header_template.html';</pre>
5138 * <pre> $scope.gridOptions.headerTemplate = '<div class="ui-grid-top-panel" style="text-align: center">I am a Custom Grid Header</div>'</pre>
5139 * or the id of a precompiled template (TBD how to use this).
5140 * </br>Refer to the custom header tutorial for more information.
5141 * If you want no header at all, you can set to an empty div:
5142 * <pre> $scope.gridOptions.headerTemplate = '<div></div>';</pre>
5144 * If you want to only have a static header, then you can set to static content. If
5145 * you want to tailor the existing column headers, then you should look at the
5146 * current 'ui-grid-header.html' template in github as your starting point.
5149 this.headerTemplate = null;
5153 * @name footerTemplate
5154 * @propertyOf ui.grid.class:GridOptions
5155 * @description (optional) Null by default. When provided, this setting uses a custom footer
5156 * template. Can be set to either the name of a template file 'footer_template.html', inline html
5157 * <pre>'<div class="ui-grid-bottom-panel" style="text-align: center">I am a Custom Grid Footer</div>'</pre>, or the id
5158 * of a precompiled template (TBD how to use this). Refer to the custom footer tutorial for more information.
5160 this.footerTemplate = null;
5165 * @propertyOf ui.grid.class:GridOptions
5166 * @description 'ui-grid/ui-grid-row' by default. When provided, this setting uses a
5167 * custom row template. Can be set to either the name of a template file:
5168 * <pre> $scope.gridOptions.rowTemplate = 'row_template.html';</pre>
5170 * <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>
5171 * or the id of a precompiled template (TBD how to use this) can be provided.
5172 * </br>Refer to the custom row template tutorial for more information.
5174 this.rowTemplate = 'ui-grid/ui-grid-row';
5185 angular.module('ui.grid')
5186 .factory('GridRenderContainer', ['$log', 'gridUtil', function($log, gridUtil) {
5187 function GridRenderContainer(name, grid, options) {
5190 // if (gridUtil.type(grid) !== 'Grid') {
5191 // throw new Error('Grid argument is not a Grid object');
5198 // self.rowCache = [];
5199 // self.columnCache = [];
5201 self.visibleRowCache = [];
5202 self.visibleColumnCache = [];
5204 self.renderedRows = [];
5205 self.renderedColumns = [];
5207 self.prevScrollTop = 0;
5208 self.prevScrolltopPercentage = 0;
5209 self.prevRowScrollIndex = 0;
5211 self.prevScrollLeft = 0;
5212 self.prevScrollleftPercentage = 0;
5213 self.prevColumnScrollIndex = 0;
5215 self.columnStyles = "";
5217 self.viewportAdjusters = [];
5219 if (options && angular.isObject(options)) {
5220 angular.extend(self, options);
5223 grid.registerStyleComputation({
5226 return self.columnStyles;
5231 // GridRenderContainer.prototype.addRenderable = function addRenderable(renderable) {
5232 // this.renderables.push(renderable);
5235 GridRenderContainer.prototype.reset = function reset() {
5236 // this.rowCache.length = 0;
5237 // this.columnCache.length = 0;
5239 this.visibleColumnCache.length = 0;
5240 this.visibleRowCache.length = 0;
5242 this.renderedRows.length = 0;
5243 this.renderedColumns.length = 0;
5246 // TODO(c0bra): calculate size?? Should this be in a stackable directive?
5248 GridRenderContainer.prototype.minRowsToRender = function minRowsToRender() {
5251 var rowAddedHeight = 0;
5252 var viewPortHeight = self.getViewportHeight();
5253 for (var i = self.visibleRowCache.length - 1; rowAddedHeight < viewPortHeight && i >= 0; i--) {
5254 rowAddedHeight += self.visibleRowCache[i].height;
5260 GridRenderContainer.prototype.minColumnsToRender = function minColumnsToRender() {
5262 var viewportWidth = this.getViewportWidth();
5266 // self.columns.forEach(function(col, i) {
5267 for (var i = 0; i < self.visibleColumnCache.length; i++) {
5268 var col = self.visibleColumnCache[i];
5270 if (totalWidth < viewportWidth) {
5271 totalWidth += col.drawnWidth;
5276 for (var j = i; j >= i - min; j--) {
5277 currWidth += self.visibleColumnCache[j].drawnWidth;
5279 if (currWidth < viewportWidth) {
5288 GridRenderContainer.prototype.getVisibleRowCount = function getVisibleRowCount() {
5289 return this.visibleRowCache.length;
5292 GridRenderContainer.prototype.registerViewportAdjuster = function registerViewportAdjuster(func) {
5293 this.viewportAdjusters.push(func);
5296 GridRenderContainer.prototype.removeViewportAdjuster = function registerViewportAdjuster(func) {
5297 var idx = this.viewportAdjusters.indexOf(func);
5299 if (typeof(idx) !== 'undefined' && idx !== undefined) {
5300 this.viewportAdjusters.splice(idx, 1);
5304 GridRenderContainer.prototype.getViewportAdjustment = function getViewportAdjustment() {
5307 var adjustment = { height: 0, width: 0 };
5309 self.viewportAdjusters.forEach(function (func) {
5310 adjustment = func.call(this, adjustment);
5316 GridRenderContainer.prototype.getViewportHeight = function getViewportHeight() {
5319 var headerHeight = (self.headerHeight) ? self.headerHeight : self.grid.headerHeight;
5321 var viewPortHeight = self.grid.gridHeight - headerHeight - self.grid.footerHeight;
5323 // Account for native horizontal scrollbar, if present
5324 if (typeof(self.horizontalScrollbarHeight) !== 'undefined' && self.horizontalScrollbarHeight !== undefined && self.horizontalScrollbarHeight > 0) {
5325 viewPortHeight = viewPortHeight - self.horizontalScrollbarHeight;
5328 var adjustment = self.getViewportAdjustment();
5330 viewPortHeight = viewPortHeight + adjustment.height;
5332 return viewPortHeight;
5335 GridRenderContainer.prototype.getViewportWidth = function getViewportWidth() {
5338 var viewPortWidth = self.grid.gridWidth;
5340 if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
5341 viewPortWidth = viewPortWidth - self.grid.verticalScrollbarWidth;
5344 var adjustment = self.getViewportAdjustment();
5346 viewPortWidth = viewPortWidth + adjustment.width;
5348 return viewPortWidth;
5351 GridRenderContainer.prototype.getHeaderViewportWidth = function getHeaderViewportWidth() {
5354 var viewPortWidth = this.getViewportWidth();
5356 if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
5357 viewPortWidth = viewPortWidth + self.grid.verticalScrollbarWidth;
5360 // var adjustment = self.getViewportAdjustment();
5361 // viewPortWidth = viewPortWidth + adjustment.width;
5363 return viewPortWidth;
5366 GridRenderContainer.prototype.getCanvasHeight = function getCanvasHeight() {
5371 self.visibleRowCache.forEach(function(row){
5375 if (typeof(self.grid.horizontalScrollbarHeight) !== 'undefined' && self.grid.horizontalScrollbarHeight !== undefined && self.grid.horizontalScrollbarHeight > 0) {
5376 ret = ret - self.grid.horizontalScrollbarHeight;
5382 GridRenderContainer.prototype.getCanvasWidth = function getCanvasWidth() {
5385 var ret = self.canvasWidth;
5387 if (typeof(self.verticalScrollbarWidth) !== 'undefined' && self.verticalScrollbarWidth !== undefined && self.verticalScrollbarWidth > 0) {
5388 ret = ret - self.verticalScrollbarWidth;
5394 GridRenderContainer.prototype.setRenderedRows = function setRenderedRows(newRows) {
5395 this.renderedRows.length = newRows.length;
5396 for (var i = 0; i < newRows.length; i++) {
5397 this.renderedRows[i] = newRows[i];
5401 GridRenderContainer.prototype.setRenderedColumns = function setRenderedColumns(newColumns) {
5405 this.renderedColumns.length = newColumns.length;
5406 for (var i = 0; i < newColumns.length; i++) {
5407 this.renderedColumns[i] = newColumns[i];
5410 this.updateColumnOffset();
5413 GridRenderContainer.prototype.updateColumnOffset = function updateColumnOffset() {
5414 // Calculate the width of the columns on the left side that are no longer rendered.
5415 // That will be the offset for the columns as we scroll horizontally.
5416 var hiddenColumnsWidth = 0;
5417 for (var i = 0; i < this.currentFirstColumn; i++) {
5418 hiddenColumnsWidth += this.visibleColumnCache[i].drawnWidth;
5421 this.columnOffset = hiddenColumnsWidth;
5424 GridRenderContainer.prototype.adjustScrollVertical = function adjustScrollVertical(scrollTop, scrollPercentage, force) {
5425 if (this.prevScrollTop === scrollTop && !force) {
5429 scrollTop = this.getCanvasHeight() * scrollPercentage;
5431 this.adjustRows(scrollTop, scrollPercentage);
5433 this.prevScrollTop = scrollTop;
5434 this.prevScrolltopPercentage = scrollPercentage;
5436 this.grid.queueRefresh();
5439 GridRenderContainer.prototype.adjustScrollHorizontal = function adjustScrollHorizontal(scrollLeft, scrollPercentage, force) {
5440 if (this.prevScrollLeft === scrollLeft && !force) {
5444 // scrollLeft = uiGridCtrl.canvas[0].scrollWidth * scrollPercentage;
5445 scrollLeft = this.getCanvasWidth() * scrollPercentage;
5447 //$log.debug('scrollPercentage', scrollPercentage);
5449 this.adjustColumns(scrollLeft, scrollPercentage);
5451 this.prevScrollLeft = scrollLeft;
5452 this.prevScrollleftPercentage = scrollPercentage;
5454 this.grid.queueRefresh();
5457 GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage) {
5460 var minRows = self.minRowsToRender();
5462 var rowCache = self.visibleRowCache;
5464 var maxRowIndex = rowCache.length - minRows;
5465 self.maxRowIndex = maxRowIndex;
5467 var curRowIndex = self.prevRowScrollIndex;
5469 // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
5470 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollTop) {
5471 scrollPercentage = scrollTop / self.getCanvasHeight();
5474 var rowIndex = Math.ceil(Math.min(maxRowIndex, maxRowIndex * scrollPercentage));
5476 // Define a max row index that we can't scroll past
5477 if (rowIndex > maxRowIndex) {
5478 rowIndex = maxRowIndex;
5482 if (rowCache.length > self.grid.options.virtualizationThreshold) {
5483 // Have we hit the threshold going down?
5484 if (self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
5487 //Have we hit the threshold going up?
5488 if (self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) {
5492 var rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows);
5493 var rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows);
5495 newRange = [rangeStart, rangeEnd];
5498 var maxLen = self.visibleRowCache.length;
5499 newRange = [0, Math.max(maxLen, minRows + self.grid.options.excessRows)];
5502 self.updateViewableRowRange(newRange);
5504 self.prevRowScrollIndex = rowIndex;
5507 GridRenderContainer.prototype.adjustColumns = function adjustColumns(scrollLeft, scrollPercentage) {
5510 var minCols = self.minColumnsToRender();
5512 var columnCache = self.visibleColumnCache;
5513 var maxColumnIndex = columnCache.length - minCols;
5515 // Calculate the scroll percentage according to the scrollTop location, if no percentage was provided
5516 if ((typeof(scrollPercentage) === 'undefined' || scrollPercentage === null) && scrollLeft) {
5517 scrollPercentage = scrollLeft / self.getCanvasWidth();
5520 var colIndex = Math.ceil(Math.min(maxColumnIndex, maxColumnIndex * scrollPercentage));
5522 // Define a max row index that we can't scroll past
5523 if (colIndex > maxColumnIndex) {
5524 colIndex = maxColumnIndex;
5528 if (columnCache.length > self.grid.options.columnVirtualizationThreshold && self.getCanvasWidth() > self.getViewportWidth()) {
5529 // Have we hit the threshold going down?
5530 if (self.prevScrollLeft < scrollLeft && colIndex < self.prevColumnScrollIndex + self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
5533 //Have we hit the threshold going up?
5534 if (self.prevScrollLeft > scrollLeft && colIndex > self.prevColumnScrollIndex - self.grid.options.horizontalScrollThreshold && colIndex < maxColumnIndex) {
5538 var rangeStart = Math.max(0, colIndex - self.grid.options.excessColumns);
5539 var rangeEnd = Math.min(columnCache.length, colIndex + minCols + self.grid.options.excessColumns);
5541 newRange = [rangeStart, rangeEnd];
5544 var maxLen = self.visibleColumnCache.length;
5546 newRange = [0, Math.max(maxLen, minCols + self.grid.options.excessColumns)];
5549 self.updateViewableColumnRange(newRange);
5551 self.prevColumnScrollIndex = colIndex;
5554 // Method for updating the visible rows
5555 GridRenderContainer.prototype.updateViewableRowRange = function updateViewableRowRange(renderedRange) {
5556 // Slice out the range of rows from the data
5557 // var rowArr = uiGridCtrl.grid.rows.slice(renderedRange[0], renderedRange[1]);
5558 var rowArr = this.visibleRowCache.slice(renderedRange[0], renderedRange[1]);
5560 // Define the top-most rendered row
5561 this.currentTopRow = renderedRange[0];
5563 // TODO(c0bra): make this method!
5564 this.setRenderedRows(rowArr);
5567 // Method for updating the visible columns
5568 GridRenderContainer.prototype.updateViewableColumnRange = function updateViewableColumnRange(renderedRange) {
5569 // Slice out the range of rows from the data
5570 // var columnArr = uiGridCtrl.grid.columns.slice(renderedRange[0], renderedRange[1]);
5571 var columnArr = this.visibleColumnCache.slice(renderedRange[0], renderedRange[1]);
5573 // Define the left-most rendered columns
5574 this.currentFirstColumn = renderedRange[0];
5576 this.setRenderedColumns(columnArr);
5579 GridRenderContainer.prototype.rowStyle = function (index) {
5584 if (index === 0 && self.currentTopRow !== 0) {
5585 // The row offset-top is just the height of the rows above the current top-most row, which are no longer rendered
5586 var hiddenRowWidth = (self.currentTopRow) * self.grid.options.rowHeight;
5588 // return { 'margin-top': hiddenRowWidth + 'px' };
5589 styles['margin-top'] = hiddenRowWidth + 'px';
5592 if (self.currentFirstColumn !== 0) {
5593 if (self.grid.isRTL()) {
5594 styles['margin-right'] = self.columnOffset + 'px';
5597 styles['margin-left'] = self.columnOffset + 'px';
5604 GridRenderContainer.prototype.columnStyle = function (index) {
5607 if (index === 0 && self.currentFirstColumn !== 0) {
5608 var offset = self.columnOffset;
5610 if (self.grid.isRTL()) {
5611 return { 'margin-right': offset + 'px' };
5614 return { 'margin-left': offset + 'px' };
5621 GridRenderContainer.prototype.updateColumnWidths = function () {
5624 var asterisksArray = [],
5630 // Get the width of the viewport
5631 var availableWidth = self.getViewportWidth();
5633 if (typeof(self.grid.verticalScrollbarWidth) !== 'undefined' && self.grid.verticalScrollbarWidth !== undefined && self.grid.verticalScrollbarWidth > 0) {
5634 availableWidth = availableWidth + self.grid.verticalScrollbarWidth;
5637 // The total number of columns
5638 // var equalWidthColumnCount = columnCount = uiGridCtrl.grid.options.columnDefs.length;
5639 // var equalWidth = availableWidth / equalWidthColumnCount;
5641 // The last column we processed
5644 var manualWidthSum = 0;
5646 var canvasWidth = 0;
5651 // uiGridCtrl.grid.columns.forEach(function(column, i) {
5653 var columnCache = self.visibleColumnCache;
5655 columnCache.forEach(function(column, i) {
5656 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + i + ' { width: ' + equalWidth + 'px; left: ' + left + 'px; }';
5657 //var colWidth = (typeof(c.width) !== 'undefined' && c.width !== undefined) ? c.width : equalWidth;
5659 // Skip hidden columns
5660 if (!column.visible) { return; }
5665 if (!angular.isNumber(column.width)) {
5666 isPercent = isNaN(column.width) ? gridUtil.endsWith(column.width, "%") : false;
5669 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.
5670 asteriskNum = parseInt(asteriskNum + column.width.length, 10);
5672 asterisksArray.push(column);
5674 else if (isPercent) { // If the width is a percentage, save it until the very last.
5675 percentArray.push(column);
5677 else if (angular.isNumber(column.width)) {
5678 manualWidthSum = parseInt(manualWidthSum + column.width, 10);
5680 canvasWidth = parseInt(canvasWidth, 10) + parseInt(column.width, 10);
5682 column.drawnWidth = column.width;
5684 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + column.width + 'px; }';
5688 // Get the remaining width (available width subtracted by the manual widths sum)
5689 var remainingWidth = availableWidth - manualWidthSum;
5691 var i, column, colWidth;
5693 if (percentArray.length > 0) {
5694 // Pre-process to make sure they're all within any min/max values
5695 for (i = 0; i < percentArray.length; i++) {
5696 column = percentArray[i];
5698 var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
5700 colWidth = parseInt(percent * remainingWidth, 10);
5702 if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
5703 colWidth = column.colDef.minWidth;
5705 remainingWidth = remainingWidth - colWidth;
5707 canvasWidth += colWidth;
5708 column.drawnWidth = colWidth;
5710 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
5712 // Remove this element from the percent array so it's not processed below
5713 percentArray.splice(i, 1);
5715 else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
5716 colWidth = column.colDef.maxWidth;
5718 remainingWidth = remainingWidth - colWidth;
5720 canvasWidth += colWidth;
5721 column.drawnWidth = colWidth;
5723 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
5725 // Remove this element from the percent array so it's not processed below
5726 percentArray.splice(i, 1);
5730 percentArray.forEach(function(column) {
5731 var percent = parseInt(column.width.replace(/%/g, ''), 10) / 100;
5732 var colWidth = parseInt(percent * remainingWidth, 10);
5734 canvasWidth += colWidth;
5736 column.drawnWidth = colWidth;
5738 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
5742 if (asterisksArray.length > 0) {
5743 var asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
5745 // Pre-process to make sure they're all within any min/max values
5746 for (i = 0; i < asterisksArray.length; i++) {
5747 column = asterisksArray[i];
5749 colWidth = parseInt(asteriskVal * column.width.length, 10);
5751 if (column.colDef.minWidth && colWidth < column.colDef.minWidth) {
5752 colWidth = column.colDef.minWidth;
5754 remainingWidth = remainingWidth - colWidth;
5757 canvasWidth += colWidth;
5758 column.drawnWidth = colWidth;
5760 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
5762 lastColumn = column;
5764 // Remove this element from the percent array so it's not processed below
5765 asterisksArray.splice(i, 1);
5767 else if (column.colDef.maxWidth && colWidth > column.colDef.maxWidth) {
5768 colWidth = column.colDef.maxWidth;
5770 remainingWidth = remainingWidth - colWidth;
5773 canvasWidth += colWidth;
5774 column.drawnWidth = colWidth;
5776 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
5778 // Remove this element from the percent array so it's not processed below
5779 asterisksArray.splice(i, 1);
5783 // Redo the asterisk value, as we may have removed columns due to width constraints
5784 asteriskVal = parseInt(remainingWidth / asteriskNum, 10);
5786 asterisksArray.forEach(function(column) {
5787 var colWidth = parseInt(asteriskVal * column.width.length, 10);
5789 canvasWidth += colWidth;
5791 column.drawnWidth = colWidth;
5793 // ret = ret + ' .grid' + uiGridCtrl.grid.id + ' .col' + column.index + ' { width: ' + colWidth + 'px; }';
5797 // 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
5798 var leftoverWidth = availableWidth - parseInt(canvasWidth, 10);
5800 if (leftoverWidth > 0 && canvasWidth > 0 && canvasWidth < availableWidth) {
5801 var variableColumn = false;
5802 // uiGridCtrl.grid.columns.forEach(function(col) {
5803 columnCache.forEach(function(col) {
5804 if (col.width && !angular.isNumber(col.width)) {
5805 variableColumn = true;
5809 if (variableColumn) {
5810 var remFn = function (column) {
5811 if (leftoverWidth > 0) {
5812 column.drawnWidth = column.drawnWidth + 1;
5813 canvasWidth = canvasWidth + 1;
5817 while (leftoverWidth > 0) {
5818 columnCache.forEach(remFn);
5823 if (canvasWidth < availableWidth) {
5824 canvasWidth = availableWidth;
5828 columnCache.forEach(function (column) {
5829 ret = ret + column.getColClassDefinition();
5832 // Add the vertical scrollbar width back in to the canvas width, it's taken out in getCanvasWidth
5833 if (self.grid.verticalScrollbarWidth) {
5834 canvasWidth = canvasWidth + self.grid.verticalScrollbarWidth;
5836 // canvasWidth = canvasWidth + 1;
5838 self.canvasWidth = parseInt(canvasWidth, 10);
5840 // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
5843 // Set this render container's column styles so they can be used in style computation
5844 this.columnStyles = ret;
5847 return GridRenderContainer;
5853 angular.module('ui.grid')
5854 .factory('GridRow', ['gridUtil', function(gridUtil) {
5858 * @name ui.grid.class:GridRow
5859 * @description GridRow is the viewModel for one logical row on the grid. A grid Row is not necessarily a one-to-one
5860 * relation to gridOptions.data.
5861 * @param {object} entity the array item from GridOptions.data
5862 * @param {number} index the current position of the row in the array
5863 * @param {Grid} reference to the parent grid
5865 function GridRow(entity, index, grid) {
5870 * @propertyOf ui.grid.class:GridRow
5871 * @description A reference back to the grid
5878 * @propertyOf ui.grid.class:GridRow
5879 * @description A reference to an item in gridOptions.data[]
5881 this.entity = entity;
5886 * @propertyOf ui.grid.class:GridRow
5887 * @description the index of the GridRow. It should always be unique and immutable
5895 * @propertyOf ui.grid.class:GridRow
5896 * @description UniqueId of row
5898 this.uid = gridUtil.nextUid();
5903 * @propertyOf ui.grid.class:GridRow
5904 * @description If true, the row will be rendered
5907 this.visible = true;
5912 * @propertyOf ui.grid.class:GridRow
5913 * @description height of each individual row
5915 this.height = grid.options.rowHeight;
5919 * @name setRowInvisible
5920 * @methodOf ui.grid.core.api:PublicApi
5921 * @description Sets an override on the row to make it always invisible,
5922 * which will override any filtering or other visibility calculations.
5923 * If the row is currently visible then sets it to invisible and calls
5924 * both grid refresh and emits the rowsVisibleChanged event
5925 * @param {object} rowEntity gridOptions.data[] array instance
5927 if (!this.grid.api.core.setRowInvisible){
5928 this.grid.api.registerMethod( 'core', 'setRowInvisible', this.setRowInvisible );
5933 * @name clearRowInvisible
5934 * @methodOf ui.grid.core.api:PublicApi
5935 * @description Clears any override on visibility for the row so that it returns to
5936 * using normal filtering and other visibility calculations.
5937 * If the row is currently invisible then sets it to visible and calls
5938 * both grid refresh and emits the rowsVisibleChanged event
5939 * TODO: if a filter is active then we can't just set it to visible?
5940 * @param {object} rowEntity gridOptions.data[] array instance
5942 if (!this.grid.api.core.clearRowInvisible){
5943 this.grid.api.registerMethod( 'core', 'clearRowInvisible', this.clearRowInvisible );
5948 * @name getVisibleRows
5949 * @methodOf ui.grid.core.api:PublicApi
5950 * @description Returns all visible rows
5951 * @param {Grid} grid the grid you want to get visible rows from
5952 * @returns {array} an array of gridRow
5954 if (!this.grid.api.core.getVisibleRows){
5955 this.grid.api.registerMethod( 'core', 'getVisibleRows', this.getVisibleRows );
5960 * @name rowsVisibleChanged
5961 * @eventOf ui.grid.core.api:PublicApi
5962 * @description is raised after the rows that are visible
5963 * change. The filtering is zero-based, so it isn't possible
5964 * to say which rows changed (unlike in the selection feature).
5965 * We can plausibly know which row was changed when setRowInvisible
5966 * is called, but in that situation the user already knows which row
5967 * they changed. When a filter runs we don't know what changed,
5968 * and that is the one that would have been useful.
5971 if (!this.grid.api.core.raise.rowsVisibleChanged){
5972 this.grid.api.registerEvent( 'core', 'rowsVisibleChanged' );
5979 * @name getQualifiedColField
5980 * @methodOf ui.grid.class:GridRow
5981 * @description returns the qualified field name as it exists on scope
5982 * ie: row.entity.fieldA
5983 * @param {GridCol} col column instance
5984 * @returns {string} resulting name that can be evaluated on scope
5986 GridRow.prototype.getQualifiedColField = function(col) {
5987 return 'row.' + this.getEntityQualifiedColField(col);
5992 * @name getEntityQualifiedColField
5993 * @methodOf ui.grid.class:GridRow
5994 * @description returns the qualified field name minus the row path
5996 * @param {GridCol} col column instance
5997 * @returns {string} resulting name that can be evaluated against a row
5999 GridRow.prototype.getEntityQualifiedColField = function(col) {
6000 return gridUtil.preEval('entity.' + col.field);
6006 * @name setRowInvisible
6007 * @methodOf ui.grid.class:GridRow
6008 * @description Sets an override on the row that forces it to always
6009 * be invisible, and if the row is currently visible then marks it
6010 * as invisible and refreshes the grid. Emits the rowsVisibleChanged
6011 * event if it changed the row visibility
6012 * @param {GridRow} row row to force invisible, needs to be a GridRow,
6013 * which can be found from your data entity using grid.findRow
6015 GridRow.prototype.setRowInvisible = function (row) {
6017 row.forceInvisible = true;
6020 row.visible = false;
6022 row.grid.api.core.raise.rowsVisibleChanged();
6029 * @name clearRowInvisible
6030 * @methodOf ui.grid.class:GridRow
6031 * @description Clears any override on the row visibility, returning it
6032 * to normal visibility calculations. If the row is currently invisible
6033 * then sets it to visible and calls refresh and emits the rowsVisibleChanged
6035 * TODO: if filter in action, then is this right?
6036 * @param {GridRow} row row clear force invisible, needs to be a GridRow,
6037 * which can be found from your data entity using grid.findRow
6039 GridRow.prototype.clearRowInvisible = function (row) {
6041 row.forceInvisible = false;
6043 if ( !row.visible ){
6046 row.grid.api.core.raise.rowsVisibleChanged();
6053 * @name getVisibleRows
6054 * @methodOf ui.grid.class:GridRow
6055 * @description Returns all the visible rows
6056 * @param {Grid} grid the grid to return rows from
6057 * @returns {array} rows that are currently visible, returns the
6058 * GridRows rather than gridRow.entity
6059 * TODO: should this come from visible row cache instead?
6061 GridRow.prototype.getVisibleRows = function ( grid ) {
6062 return grid.rows.filter(function (row) {
6075 * @name ui.grid.service:gridClassFactory
6077 * @description factory to return dom specific instances of a grid
6080 angular.module('ui.grid').service('gridClassFactory', ['gridUtil', '$q', '$compile', '$templateCache', 'uiGridConstants', '$log', 'Grid', 'GridColumn', 'GridRow',
6081 function (gridUtil, $q, $compile, $templateCache, uiGridConstants, $log, Grid, GridColumn, GridRow) {
6087 * @methodOf ui.grid.service:gridClassFactory
6088 * @description Creates a new grid instance. Each instance will have a unique id
6089 * @param {object} options An object map of options to pass into the created grid instance.
6090 * @returns {Grid} grid
6092 createGrid : function(options) {
6093 options = (typeof(options) !== 'undefined') ? options: {};
6094 options.id = gridUtil.newId();
6095 var grid = new Grid(options);
6097 // NOTE/TODO: rowTemplate should always be defined...
6098 if (grid.options.rowTemplate) {
6099 var rowTemplateFnPromise = $q.defer();
6100 grid.getRowTemplateFn = rowTemplateFnPromise.promise;
6102 gridUtil.getTemplate(grid.options.rowTemplate)
6104 function (template) {
6105 var rowTemplateFn = $compile(template);
6106 rowTemplateFnPromise.resolve(rowTemplateFn);
6109 // Todo handle response error here?
6110 throw new Error("Couldn't fetch/use row template '" + grid.options.rowTemplate + "'");
6114 grid.registerColumnBuilder(service.defaultColumnBuilder);
6116 // Reset all rows to visible initially
6117 grid.registerRowsProcessor(function allRowsVisible(rows) {
6118 rows.forEach(function (row) {
6119 row.visible = !row.forceInvisible;
6125 grid.registerColumnsProcessor(function allColumnsVisible(columns) {
6126 columns.forEach(function (column) {
6127 column.visible = true;
6133 grid.registerColumnsProcessor(function(renderableColumns) {
6134 renderableColumns.forEach(function (column) {
6135 if (column.colDef.visible === false) {
6136 column.visible = false;
6140 return renderableColumns;
6145 if (grid.options.enableFiltering) {
6146 grid.registerRowsProcessor(grid.searchRows);
6149 // Register the default row processor, it sorts rows by selected columns
6150 if (grid.options.externalSort && angular.isFunction(grid.options.externalSort)) {
6151 grid.registerRowsProcessor(grid.options.externalSort);
6154 grid.registerRowsProcessor(grid.sortByColumn);
6162 * @name defaultColumnBuilder
6163 * @methodOf ui.grid.service:gridClassFactory
6164 * @description Processes designTime column definitions and applies them to col for the
6165 * core grid features
6166 * @param {object} colDef reference to column definition
6167 * @param {GridColumn} col reference to gridCol
6168 * @param {object} gridOptions reference to grid options
6170 defaultColumnBuilder: function (colDef, col, gridOptions) {
6172 var templateGetPromises = [];
6176 * @name headerCellTemplate
6177 * @propertyOf ui.grid.class:GridOptions.columnDef
6178 * @description a custom template for the header for this column. The default
6179 * is ui-grid/uiGridHeaderCell
6182 if (!colDef.headerCellTemplate) {
6183 colDef.headerCellTemplate = 'ui-grid/uiGridHeaderCell';
6188 * @name cellTemplate
6189 * @propertyOf ui.grid.class:GridOptions.columnDef
6190 * @description a custom template for each cell in this column. The default
6191 * is ui-grid/uiGridCell
6194 if (!colDef.cellTemplate) {
6195 colDef.cellTemplate = 'ui-grid/uiGridCell';
6198 templateGetPromises.push(gridUtil.getTemplate(colDef.cellTemplate)
6200 function (template) {
6201 col.cellTemplate = template.replace(uiGridConstants.CUSTOM_FILTERS, col.cellFilter ? "|" + col.cellFilter : "");
6204 throw new Error("Couldn't fetch/use colDef.cellTemplate '" + colDef.cellTemplate + "'");
6209 templateGetPromises.push(gridUtil.getTemplate(colDef.headerCellTemplate)
6211 function (template) {
6212 col.headerCellTemplate = template.replace(uiGridConstants.CUSTOM_FILTERS, col.headerCellFilter ? "|" + col.headerCellFilter : "");
6215 throw new Error("Couldn't fetch/use colDef.headerCellTemplate '" + colDef.headerCellTemplate + "'");
6219 return $q.all(templateGetPromises);
6224 //class definitions (moved to separate factories)
6232 var module = angular.module('ui.grid');
6234 function escapeRegExp(str) {
6235 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
6238 function QuickCache() {
6239 var c = function(get, set) {
6240 // Return the cached value of 'get' if it's stored
6241 if (get && c.cache[get]) {
6242 return c.cache[get];
6244 // Otherwise set it and return it
6245 else if (get && set) {
6247 return c.cache[get];
6256 c.clear = function () {
6265 * @name ui.grid.service:rowSearcher
6267 * @description Service for searching/filtering rows based on column value conditions.
6269 module.service('rowSearcher', ['$log', 'uiGridConstants', function ($log, uiGridConstants) {
6270 var defaultCondition = uiGridConstants.filter.STARTS_WITH;
6272 var rowSearcher = {};
6274 // rowSearcher.searchColumn = function searchColumn(condition, item) {
6277 // var col = self.fieldMap[condition.columnDisplay];
6282 // var sp = col.cellFilter.split(':');
6283 // var filter = col.cellFilter ? $filter(sp[0]) : null;
6284 // var value = item[condition.column] || item[col.field.split('.')[0]];
6285 // if (value === null || value === undefined) {
6288 // if (typeof filter === "function") {
6289 // var filterResults = filter(typeof value === "object" ? evalObject(value, col.field) : value, sp[1]).toString();
6290 // result = condition.regex.test(filterResults);
6293 // result = condition.regex.test(typeof value === "object" ? evalObject(value, col.field).toString() : value.toString());
6304 * @methodOf ui.grid.service:rowSearcher
6305 * @description Get the term from a filter
6306 * Trims leading and trailing whitespace
6307 * @param {object} filter object to use
6308 * @returns {object} Parsed term
6310 rowSearcher.getTerm = function getTerm(filter) {
6311 if (typeof(filter.term) === 'undefined') { return filter.term; }
6313 var term = filter.term;
6315 // Strip leading and trailing whitespace if the term is a string
6316 if (typeof(term) === 'string') {
6326 * @methodOf ui.grid.service:rowSearcher
6327 * @description Remove leading and trailing asterisk (*) from the filter's term
6328 * @param {object} filter object to use
6329 * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
6331 rowSearcher.stripTerm = function stripTerm(filter) {
6332 var term = rowSearcher.getTerm(filter);
6334 if (typeof(term) === 'string') {
6335 return escapeRegExp(term.replace(/(^\*|\*$)/g, ''));
6344 * @name guessCondition
6345 * @methodOf ui.grid.service:rowSearcher
6346 * @description Guess the condition for a filter based on its term
6348 * Defaults to STARTS_WITH. Uses CONTAINS for strings beginning and ending with *s (*bob*).
6349 * Uses STARTS_WITH for strings ending with * (bo*). Uses ENDS_WITH for strings starting with * (*ob).
6350 * @param {object} filter object to use
6351 * @returns {uiGridConstants.filter<int>} Value representing the condition constant value
6353 rowSearcher.guessCondition = function guessCondition(filter) {
6354 if (typeof(filter.term) === 'undefined' || !filter.term) {
6355 return defaultCondition;
6358 var term = rowSearcher.getTerm(filter);
6360 // Term starts with and ends with a *, use 'contains' condition
6361 // if (/^\*[\s\S]+?\*$/.test(term)) {
6362 // return uiGridConstants.filter.CONTAINS;
6364 // // Term starts with a *, use 'ends with' condition
6365 // else if (/^\*/.test(term)) {
6366 // return uiGridConstants.filter.ENDS_WITH;
6368 // // Term ends with a *, use 'starts with' condition
6369 // else if (/\*$/.test(term)) {
6370 // return uiGridConstants.filter.STARTS_WITH;
6372 // // Default to default condition
6374 // return defaultCondition;
6377 // If the term has *s then turn it into a regex
6378 if (/\*/.test(term)) {
6379 var regexpFlags = '';
6380 if (!filter.flags || !filter.flags.caseSensitive) {
6384 var reText = term.replace(/(\\)?\*/g, function ($0, $1) { return $1 ? $0 : '[\\s\\S]*?'; });
6385 return new RegExp('^' + reText + '$', regexpFlags);
6387 // Otherwise default to default condition
6389 return defaultCondition;
6393 rowSearcher.runColumnFilter = function runColumnFilter(grid, row, column, termCache, i, filter) {
6394 // Cache typeof condition
6395 var conditionType = typeof(filter.condition);
6397 // Default to CONTAINS condition
6398 if (conditionType === 'undefined' || !filter.condition) {
6399 filter.condition = uiGridConstants.filter.CONTAINS;
6402 // Term to search for.
6403 var term = rowSearcher.stripTerm(filter);
6405 if (term === null || term === undefined || term === '') {
6409 // Get the column value for this row
6410 var value = grid.getCellValue(row, column);
6412 var regexpFlags = '';
6413 if (!filter.flags || !filter.flags.caseSensitive) {
6417 var cacheId = column.field + i;
6419 // If the filter's condition is a RegExp, then use it
6420 if (filter.condition instanceof RegExp) {
6421 if (!filter.condition.test(value)) {
6425 // If the filter's condition is a function, run it
6426 else if (conditionType === 'function') {
6427 return filter.condition(term, value, row, column);
6429 else if (filter.condition === uiGridConstants.filter.STARTS_WITH) {
6430 var startswithRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp('^' + term, regexpFlags));
6432 if (!startswithRE.test(value)) {
6436 else if (filter.condition === uiGridConstants.filter.ENDS_WITH) {
6437 var endswithRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp(term + '$', regexpFlags));
6439 if (!endswithRE.test(value)) {
6443 else if (filter.condition === uiGridConstants.filter.CONTAINS) {
6444 var containsRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp(term, regexpFlags));
6446 if (!containsRE.test(value)) {
6450 else if (filter.condition === uiGridConstants.filter.EXACT) {
6451 var exactRE = termCache(cacheId) ? termCache(cacheId) : termCache(cacheId, new RegExp('^' + term + '$', regexpFlags));
6453 if (!exactRE.test(value)) {
6457 else if (filter.condition === uiGridConstants.filter.GREATER_THAN) {
6458 if (value <= term) {
6462 else if (filter.condition === uiGridConstants.filter.GREATER_THAN_OR_EQUAL) {
6467 else if (filter.condition === uiGridConstants.filter.LESS_THAN) {
6468 if (value >= term) {
6472 else if (filter.condition === uiGridConstants.filter.LESS_THAN_OR_EQUAL) {
6477 else if (filter.condition === uiGridConstants.filter.NOT_EQUAL) {
6478 if (!angular.equals(value, term)) {
6488 * @name useExternalFiltering
6489 * @propertyOf ui.grid.class:GridOptions
6490 * @description False by default. When enabled, this setting suppresses the internal filtering.
6491 * All UI logic will still operate, allowing filter conditions to be set and modified.
6493 * The external filter logic can listen for the `filterChange` event, which fires whenever
6494 * a filter has been adjusted.
6498 * @name searchColumn
6499 * @methodOf ui.grid.service:rowSearcher
6500 * @description Process filters on a given column against a given row. If the row meets the conditions on all the filters, return true.
6501 * @param {Grid} grid Grid to search in
6502 * @param {GridRow} row Row to search on
6503 * @param {GridCol} column Column with the filters to use
6504 * @returns {boolean} Whether the column matches or not.
6506 rowSearcher.searchColumn = function searchColumn(grid, row, column, termCache) {
6509 if (grid.options.useExternalFiltering) {
6513 if (typeof(column.filters) !== 'undefined' && column.filters && column.filters.length > 0) {
6514 filters = column.filters;
6516 // If filters array is not there, assume no filters for this column.
6517 // This array should have been built in GridColumn::updateColumnDef.
6521 for (var i in filters) {
6522 var filter = filters[i];
6526 term: 'blah', // Search term to search for, could be a string, integer, etc.
6527 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.
6528 flags: { // Flags for the conditions
6529 caseSensitive: false // Case-sensitivity defaults to false
6534 // Check for when no condition is supplied. In this case, guess the condition
6535 // to use based on the filter's term. Cache this result.
6536 if (!filter.condition) {
6537 // Cache custom conditions, building the RegExp takes time
6538 var conditionCacheId = 'cond-' + column.field + '-' + filter.term;
6539 var condition = termCache(conditionCacheId) ? termCache(conditionCacheId) : termCache(conditionCacheId, rowSearcher.guessCondition(filter));
6541 // Create a surrogate filter so as not to change
6542 // the actual columnDef.filters.
6544 // Copy over the search term
6546 // Use the guessed condition
6547 condition: condition,
6548 // Set flags, using passed flags if present
6549 flags: angular.extend({
6550 caseSensitive: false
6555 var ret = rowSearcher.runColumnFilter(grid, row, column, termCache, i, filter);
6564 // // No filter conditions, default to true
6572 * @methodOf ui.grid.service:rowSearcher
6573 * @description Run a search across
6574 * @param {Grid} grid Grid instance to search inside
6575 * @param {Array[GridRow]} rows GridRows to filter
6576 * @param {Array[GridColumn]} columns GridColumns with filters to process
6578 rowSearcher.search = function search(grid, rows, columns) {
6579 // Don't do anything if we weren't passed any rows
6584 // Create a term cache
6585 var termCache = new QuickCache();
6587 // Build filtered column list
6588 var filterCols = [];
6589 columns.forEach(function (col) {
6590 if (typeof(col.filters) !== 'undefined' && col.filters.length > 0) {
6591 filterCols.push(col);
6593 else if (typeof(col.filter) !== 'undefined' && col.filter && typeof(col.filter.term) !== 'undefined' && col.filter.term) {
6594 filterCols.push(col);
6598 if (filterCols.length > 0) {
6599 filterCols.forEach(function foreachFilterCol(col) {
6600 rows.forEach(function foreachRow(row) {
6601 if (row.forceInvisible || !rowSearcher.searchColumn(grid, row, col, termCache)) {
6602 row.visible = false;
6607 grid.api.core.raise.rowsVisibleChanged();
6609 // rows.forEach(function (row) {
6610 // var matchesAllColumns = true;
6612 // for (var i in filterCols) {
6613 // var col = filterCols[i];
6615 // if (!rowSearcher.searchColumn(grid, row, col, termCache)) {
6616 // matchesAllColumns = false;
6618 // // Stop processing other terms
6623 // // Row doesn't match all the terms, don't display it
6624 // if (!matchesAllColumns) {
6625 // row.visible = false;
6628 // row.visible = true;
6633 // Reset the term cache
6645 var module = angular.module('ui.grid');
6649 * @name ui.grid.class:RowSorter
6650 * @description RowSorter provides the default sorting mechanisms,
6651 * including guessing column types and applying appropriate sort
6656 module.service('rowSorter', ['$parse', 'uiGridConstants', function ($parse, uiGridConstants) {
6657 var currencyRegexStr =
6659 uiGridConstants.CURRENCY_SYMBOLS
6660 .map(function (a) { return '\\' + a; }) // Escape all the currency symbols ($ at least will jack up this regex)
6661 .join('|') + // Join all the symbols together with |s
6664 // /^[-+]?[£$¤¥]?[\d,.]+%?$/
6665 var numberStrRegex = new RegExp('^[-+]?' + currencyRegexStr + '[\\d,.]+' + currencyRegexStr + '%?$');
6668 // Cache of sorting functions. Once we create them, we don't want to keep re-doing it
6669 // this takes a piece of data from the cell and tries to determine its type and what sorting
6670 // function to use for it
6674 // Guess which sort function to use on this item
6675 rowSorter.guessSortFn = function guessSortFn(itemType) {
6677 // Check for numbers and booleans
6680 return rowSorter.sortNumber;
6682 return rowSorter.sortBool;
6684 return rowSorter.sortAlpha;
6686 return rowSorter.sortDate;
6688 return rowSorter.basicSort;
6690 throw new Error('No sorting function found for type:' + itemType);
6694 // Basic sorting function
6695 rowSorter.basicSort = function basicSort(a, b) {
6705 // Number sorting function
6706 rowSorter.sortNumber = function sortNumber(a, b) {
6710 rowSorter.sortNumberStr = function sortNumberStr(a, b) {
6711 var numA, // The parsed number form of 'a'
6712 numB, // The parsed number form of 'b'
6716 // Try to parse 'a' to a float
6717 numA = parseFloat(a.replace(/[^0-9.-]/g, ''));
6719 // If 'a' couldn't be parsed to float, flag it as bad
6724 // Try to parse 'b' to a float
6725 numB = parseFloat(b.replace(/[^0-9.-]/g, ''));
6727 // If 'b' couldn't be parsed to float, flag it as bad
6732 // We want bad ones to get pushed to the bottom... which effectively is "greater than"
6748 // String sorting function
6749 rowSorter.sortAlpha = function sortAlpha(a, b) {
6750 var strA = a.toLowerCase(),
6751 strB = b.toLowerCase();
6753 return strA === strB ? 0 : (strA < strB ? -1 : 1);
6756 // Date sorting function
6757 rowSorter.sortDate = function sortDate(a, b) {
6758 var timeA = a.getTime(),
6759 timeB = b.getTime();
6761 return timeA === timeB ? 0 : (timeA < timeB ? -1 : 1);
6764 // Boolean sorting function
6765 rowSorter.sortBool = function sortBool(a, b) {
6778 rowSorter.getSortFn = function getSortFn(grid, col, rows) {
6781 // See if we already figured out what to use to sort the column and have it in the cache
6782 if (rowSorter.colSortFnCache[col.colDef.name]) {
6783 sortFn = rowSorter.colSortFnCache[col.colDef.name];
6785 // If the column has its OWN sorting algorithm, use that
6786 else if (col.sortingAlgorithm !== undefined) {
6787 sortFn = col.sortingAlgorithm;
6788 rowSorter.colSortFnCache[col.colDef.name] = col.sortingAlgorithm;
6790 // Try and guess what sort function to use
6792 // Guess the sort function
6793 sortFn = rowSorter.guessSortFn(col.colDef.type);
6795 // If we found a sort function, cache it
6797 rowSorter.colSortFnCache[col.colDef.name] = sortFn;
6800 // We assign the alpha sort because anything that is null/undefined will never get passed to
6801 // the actual sorting function. It will get caught in our null check and returned to be sorted
6802 // down to the bottom
6803 sortFn = rowSorter.sortAlpha;
6810 rowSorter.prioritySort = function (a, b) {
6811 // Both columns have a sort priority
6812 if (a.sort.priority !== undefined && b.sort.priority !== undefined) {
6813 // A is higher priority
6814 if (a.sort.priority < b.sort.priority) {
6818 else if (a.sort.priority === b.sort.priority) {
6826 // Only A has a priority
6827 else if (a.sort.priority) {
6830 // Only B has a priority
6831 else if (b.sort.priority) {
6834 // Neither has a priority
6842 * @name useExternalSorting
6843 * @propertyOf ui.grid.class:GridOptions
6844 * @description Prevents the internal sorting from executing. Events will
6845 * still be fired when the sort changes, and the sort information on
6846 * the columns will be updated, allowing an external sorter (for example,
6847 * server sorting) to be implemented. Defaults to false.
6852 * @methodOf ui.grid.class:RowSorter
6854 * @description sorts the grid
6855 * @param {Object} grid the grid itself
6856 * @param {Object} rows the rows to be sorted
6857 * @param {Object} columns the columns in which to look
6860 rowSorter.sort = function rowSorterSort(grid, rows, columns) {
6861 // first make sure we are even supposed to do work
6866 if (grid.options.useExternalSorting){
6870 // Build the list of columns to sort by
6872 columns.forEach(function (col) {
6873 if (col.sort && col.sort.direction && (col.sort.direction === uiGridConstants.ASC || col.sort.direction === uiGridConstants.DESC)) {
6878 // Sort the "sort columns" by their sort priority
6879 sortCols = sortCols.sort(rowSorter.prioritySort);
6881 // Now rows to sort by, maintain original order
6882 if (sortCols.length === 0) {
6886 // Re-usable variables
6889 // IE9-11 HACK.... the 'rows' variable would be empty where we call rowSorter.getSortFn(...) below. We have to use a separate reference
6890 // var d = data.slice(0);
6891 var r = rows.slice(0);
6893 // Now actually sort the data
6894 return rows.sort(function rowSortFn(rowA, rowB) {
6899 while (tem === 0 && idx < sortCols.length) {
6900 // grab the metadata for the rest of the logic
6901 col = sortCols[idx];
6902 direction = sortCols[idx].sort.direction;
6904 sortFn = rowSorter.getSortFn(grid, col, r);
6906 var propA = grid.getCellValue(rowA, col);
6907 var propB = grid.getCellValue(rowB, col);
6909 // We want to allow zero values to be evaluated in the sort function
6910 if ((!propA && propA !== 0) || (!propB && propB !== 0)) {
6911 // We want to force nulls and such to the bottom when we sort... which effectively is "greater than"
6912 if (!propB && !propA) {
6923 tem = sortFn(propA, propB);
6929 // Made it this far, we don't have to worry about null & undefined
6930 if (direction === uiGridConstants.ASC) {
6944 var module = angular.module('ui.grid');
6947 function getStyles (elem) {
6948 return elem.ownerDocument.defaultView.getComputedStyle(elem, null);
6951 var rnumnonpx = new RegExp( "^(" + (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source + ")(?!px)[a-z%]+$", "i" ),
6952 // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
6953 // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
6954 rdisplayswap = /^(block|none|table(?!-c[ea]).+)/,
6955 cssShow = { position: "absolute", visibility: "hidden", display: "block" };
6957 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
6958 var i = extra === ( isBorderBox ? 'border' : 'content' ) ?
6959 // If we already have the right measurement, avoid augmentation
6961 // Otherwise initialize for horizontal or vertical properties
6962 name === 'width' ? 1 : 0,
6966 var sides = ['Top', 'Right', 'Bottom', 'Left'];
6968 for ( ; i < 4; i += 2 ) {
6969 var side = sides[i];
6970 // dump('side', side);
6972 // both box models exclude margin, so add it if we want it
6973 if ( extra === 'margin' ) {
6974 var marg = parseFloat(styles[extra + side]);
6979 // dump('val1', val);
6981 if ( isBorderBox ) {
6982 // border-box includes padding, so remove it if we want content
6983 if ( extra === 'content' ) {
6984 var padd = parseFloat(styles['padding' + side]);
6987 // dump('val2', val);
6991 // at this point, extra isn't border nor margin, so remove border
6992 if ( extra !== 'margin' ) {
6993 var bordermarg = parseFloat(styles['border' + side + 'Width']);
6994 if (!isNaN(bordermarg)) {
6996 // dump('val3', val);
7001 // at this point, extra isn't content, so add padding
7002 var nocontentPad = parseFloat(styles['padding' + side]);
7003 if (!isNaN(nocontentPad)) {
7004 val += nocontentPad;
7005 // dump('val4', val);
7008 // at this point, extra isn't content nor padding, so add border
7009 if ( extra !== 'padding') {
7010 var nocontentnopad = parseFloat(styles['border' + side + 'Width']);
7011 if (!isNaN(nocontentnopad)) {
7012 val += nocontentnopad;
7013 // dump('val5', val);
7019 // dump('augVal', val);
7024 function getWidthOrHeight( elem, name, extra ) {
7025 // Start with offset property, which is equivalent to the border-box value
7026 var valueIsBorderBox = true,
7027 val = name === 'width' ? elem.offsetWidth : elem.offsetHeight,
7028 styles = getStyles(elem),
7029 isBorderBox = styles['boxSizing'] === 'border-box';
7031 // some non-html elements return undefined for offsetWidth, so check for null/undefined
7032 // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
7033 // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
7034 if ( val <= 0 || val == null ) {
7035 // Fall back to computed then uncomputed css if necessary
7037 if ( val < 0 || val == null ) {
7038 val = elem.style[ name ];
7041 // Computed unit is not pixels. Stop here and return.
7042 if ( rnumnonpx.test(val) ) {
7046 // we need the check for style in case a browser which returns unreliable values
7047 // for getComputedStyle silently falls back to the reliable elem.style
7048 valueIsBorderBox = isBorderBox &&
7049 ( true || val === elem.style[ name ] ); // use 'true' instead of 'support.boxSizingReliable()'
7051 // Normalize "", auto, and prepare for extra
7052 val = parseFloat( val ) || 0;
7055 // use the active box-sizing model to add/subtract irrelevant styles
7057 augmentWidthOrHeight(
7060 extra || ( isBorderBox ? "border" : "content" ),
7066 // dump('ret', ret, val);
7070 var uid = ['0', '0', '0'];
7071 var uidPrefix = 'uiGrid-';
7075 * @name ui.grid.service:GridUtil
7077 * @description Grid utility functions
7079 module.service('gridUtil', ['$log', '$window', '$document', '$http', '$templateCache', '$timeout', '$injector', '$q', 'uiGridConstants',
7080 function ($log, $window, $document, $http, $templateCache, $timeout, $injector, $q, uiGridConstants) {
7085 * @name createBoundedWrapper
7086 * @methodOf ui.grid.service:GridUtil
7088 * @param {object} Object to bind 'this' to
7089 * @param {method} Method to bind
7090 * @returns {Function} The wrapper that performs the binding
7093 * Binds given method to given object.
7095 * By means of a wrapper, ensures that ``method`` is always bound to
7096 * ``object`` regardless of its calling environment.
7097 * Iow, inside ``method``, ``this`` always points to ``object``.
7099 * See http://alistapart.com/article/getoutbindingsituations
7102 createBoundedWrapper: function(object, method) {
7104 return method.apply(object, arguments);
7111 * @name readableColumnName
7112 * @methodOf ui.grid.service:GridUtil
7114 * @param {string} columnName Column name as a string
7115 * @returns {string} Column name appropriately capitalized and split apart
7118 <example module="app">
7119 <file name="app.js">
7120 var app = angular.module('app', ['ui.grid']);
7122 app.controller('MainCtrl', ['$scope', 'gridUtil', function ($scope, gridUtil) {
7123 $scope.name = 'firstName';
7124 $scope.columnName = function(name) {
7125 return gridUtil.readableColumnName(name);
7129 <file name="index.html">
7130 <div ng-controller="MainCtrl">
7131 <strong>Column name:</strong> <input ng-model="name" />
7133 <strong>Output:</strong> <span ng-bind="columnName(name)"></span>
7138 readableColumnName: function (columnName) {
7139 // Convert underscores to spaces
7140 if (typeof(columnName) === 'undefined' || columnName === undefined || columnName === null) { return columnName; }
7142 if (typeof(columnName) !== 'string') {
7143 columnName = String(columnName);
7146 return columnName.replace(/_+/g, ' ')
7147 // Replace a completely all-capsed word with a first-letter-capitalized version
7148 .replace(/^[A-Z]+$/, function (match) {
7149 return angular.lowercase(angular.uppercase(match.charAt(0)) + match.slice(1));
7151 // Capitalize the first letter of words
7152 .replace(/(\w+)/g, function (match) {
7153 return angular.uppercase(match.charAt(0)) + match.slice(1);
7155 // Put a space in between words that have partial capilizations (i.e. 'firstName' becomes 'First Name')
7156 // .replace(/([A-Z]|[A-Z]\w+)([A-Z])/g, "$1 $2");
7157 // .replace(/(\w+?|\w)([A-Z])/g, "$1 $2");
7158 .replace(/(\w+?(?=[A-Z]))/g, '$1 ');
7163 * @name getColumnsFromData
7164 * @methodOf ui.grid.service:GridUtil
7165 * @description Return a list of column names, given a data set
7167 * @param {string} data Data array for grid
7168 * @returns {Object} Column definitions with field accessor and column name
7173 { firstName: 'Bob', lastName: 'Jones' },
7174 { firstName: 'Frank', lastName: 'Smith' }
7177 var columnDefs = GridUtil.getColumnsFromData(data, excludeProperties);
7191 getColumnsFromData: function (data, excludeProperties) {
7192 var columnDefs = [];
7194 if (!data || typeof(data[0]) === 'undefined' || data[0] === undefined) { return []; }
7195 if (angular.isUndefined(excludeProperties)) { excludeProperties = []; }
7199 angular.forEach(item,function (prop, propName) {
7200 if ( excludeProperties.indexOf(propName) === -1){
7213 * @methodOf ui.grid.service:GridUtil
7214 * @description Return a unique ID string
7216 * @returns {string} Unique string
7220 var id = GridUtil.newId();
7225 newId: (function() {
7226 var seedId = new Date().getTime();
7236 * @methodOf ui.grid.service:GridUtil
7237 * @description Get's template from cache / element / url
7239 * @param {string|element|promise} Either a string representing the template id, a string representing the template url,
7240 * an jQuery/Angualr element, or a promise that returns the template contents to use.
7241 * @returns {object} a promise resolving to template contents
7245 GridUtil.getTemplate(url).then(function (contents) {
7250 getTemplate: function (template) {
7251 // Try to fetch the template out of the templateCache
7252 if ($templateCache.get(template)) {
7253 return $q.when($templateCache.get(template));
7256 // See if the template is itself a promise
7257 if (template.hasOwnProperty('then')) {
7261 // If the template is an element, return the element
7263 if (angular.element(template).length > 0) {
7264 return $q.when(template);
7268 //do nothing; not valid html
7271 $log.debug('Fetching url', template);
7273 // Default to trying to fetch the template as a url with $http
7274 return $http({ method: 'GET', url: template})
7277 var templateHtml = result.data.trim();
7278 //put in templateCache for next call
7279 $templateCache.put(template, templateHtml);
7280 return templateHtml;
7283 throw new Error("Could not get template " + template + ": " + err);
7291 * @methodOf ui.grid.service:GridUtil
7292 * @description guesses the type of an argument
7294 * @param {string/number/bool/object} item variable to examine
7295 * @returns {string} one of the following
7302 guessType : function (item) {
7303 var itemType = typeof(item);
7305 // Check for numbers and booleans
7312 if (angular.isDate(item)) {
7322 * @name elementWidth
7323 * @methodOf ui.grid.service:GridUtil
7325 * @param {element} element DOM element
7326 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
7328 * @returns {number} Element width in pixels, accounting for any borders, etc.
7330 elementWidth: function (elem) {
7336 * @name elementHeight
7337 * @methodOf ui.grid.service:GridUtil
7339 * @param {element} element DOM element
7340 * @param {string} [extra] Optional modifier for calculation. Use 'margin' to account for margins on element
7342 * @returns {number} Element height in pixels, accounting for any borders, etc.
7344 elementHeight: function (elem) {
7348 // Thanks to http://stackoverflow.com/a/13382873/888165
7349 getScrollbarWidth: function() {
7350 var outer = document.createElement("div");
7351 outer.style.visibility = "hidden";
7352 outer.style.width = "100px";
7353 outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
7355 document.body.appendChild(outer);
7357 var widthNoScroll = outer.offsetWidth;
7359 outer.style.overflow = "scroll";
7362 var inner = document.createElement("div");
7363 inner.style.width = "100%";
7364 outer.appendChild(inner);
7366 var widthWithScroll = inner.offsetWidth;
7369 outer.parentNode.removeChild(outer);
7371 return widthNoScroll - widthWithScroll;
7374 swap: function( elem, options, callback, args ) {
7378 // Remember the old values, and insert the new ones
7379 for ( name in options ) {
7380 old[ name ] = elem.style[ name ];
7381 elem.style[ name ] = options[ name ];
7384 ret = callback.apply( elem, args || [] );
7386 // Revert the old values
7387 for ( name in options ) {
7388 elem.style[ name ] = old[ name ];
7394 fakeElement: function( elem, options, callback, args ) {
7396 newElement = angular.element(elem).clone()[0];
7398 for ( name in options ) {
7399 newElement.style[ name ] = options[ name ];
7402 angular.element(document.body).append(newElement);
7404 ret = callback.call( newElement, newElement );
7406 angular.element(newElement).remove();
7413 * @name normalizeWheelEvent
7414 * @methodOf ui.grid.service:GridUtil
7416 * @param {event} event A mouse wheel event
7418 * @returns {event} A normalized event
7421 * Given an event from this list:
7423 * `wheel, mousewheel, DomMouseScroll, MozMousePixelScroll`
7426 * so that it stays consistent no matter what browser it comes from (i.e. scale it correctly and make sure the direction is right.)
7428 normalizeWheelEvent: function (event) {
7429 // var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
7430 // var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
7431 var lowestDelta, lowestDeltaXY;
7433 var orgEvent = event || window.event,
7434 args = [].slice.call(arguments, 1),
7442 // event = $.event.fix(orgEvent);
7443 // event.type = 'mousewheel';
7445 // NOTE: jQuery masks the event and stores it in the event as originalEvent
7446 if (orgEvent.originalEvent) {
7447 orgEvent = orgEvent.originalEvent;
7450 // Old school scrollwheel delta
7451 if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
7452 if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
7454 // At a minimum, setup the deltaY to be delta
7457 // Firefox < 17 related to DOMMouseScroll event
7458 if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
7460 deltaX = delta * -1;
7463 // New school wheel delta (wheel event)
7464 if ( orgEvent.deltaY ) {
7465 deltaY = orgEvent.deltaY * -1;
7468 if ( orgEvent.deltaX ) {
7469 deltaX = orgEvent.deltaX;
7470 delta = deltaX * -1;
7474 if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
7475 if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX; }
7477 // Look for lowest delta to normalize the delta values
7478 absDelta = Math.abs(delta);
7479 if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
7480 absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
7481 if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
7483 // Get a whole value for the deltas
7484 fn = delta > 0 ? 'floor' : 'ceil';
7485 delta = Math[fn](delta / lowestDelta);
7486 deltaX = Math[fn](deltaX / lowestDeltaXY);
7487 deltaY = Math[fn](deltaY / lowestDeltaXY);
7496 // Stolen from Modernizr
7497 // TODO: make this, and everythign that flows from it, robust
7498 //http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
7499 isTouchEnabled: function() {
7502 if (('ontouchstart' in $window) || $window.DocumentTouch && $document instanceof DocumentTouch) {
7509 isNullOrUndefined: function(obj) {
7510 if (obj === undefined || obj === null) {
7516 endsWith: function(str, suffix) {
7517 if (!str || !suffix || typeof str !== "string") {
7520 return str.indexOf(suffix, str.length - suffix.length) !== -1;
7523 // Shim requestAnimationFrame
7524 requestAnimationFrame: $window.requestAnimationFrame && $window.requestAnimationFrame.bind($window) ||
7525 $window.webkitRequestAnimationFrame && $window.webkitRequestAnimationFrame.bind($window) ||
7527 return $timeout(fn, 10, false);
7530 numericAndNullSort: function (a, b) {
7531 if (a === null) { return 1; }
7532 if (b === null) { return -1; }
7533 if (a === null && b === null) { return 0; }
7537 // Disable ngAnimate animations on an element
7538 disableAnimations: function (element) {
7541 $animate = $injector.get('$animate');
7542 $animate.enabled(false, element);
7547 enableAnimations: function (element) {
7550 $animate = $injector.get('$animate');
7551 $animate.enabled(true, element);
7556 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
7557 nextUid: function nextUid() {
7558 var index = uid.length;
7563 digit = uid[index].charCodeAt(0);
7564 if (digit === 57 /*'9'*/) {
7566 return uidPrefix + uid.join('');
7568 if (digit === 90 /*'Z'*/) {
7571 uid[index] = String.fromCharCode(digit + 1);
7572 return uidPrefix + uid.join('');
7577 return uidPrefix + uid.join('');
7580 // Blatantly stolen from Angular as it isn't exposed (yet. 2.0 maybe?)
7581 hashKey: function hashKey(obj) {
7582 var objType = typeof obj,
7585 if (objType === 'object' && obj !== null) {
7586 if (typeof (key = obj.$$hashKey) === 'function') {
7587 // must invoke on object to keep the right this
7588 key = obj.$$hashKey();
7590 else if (typeof(obj.$$hashKey) !== 'undefined' && obj.$$hashKey) {
7591 key = obj.$$hashKey;
7593 else if (key === undefined) {
7594 key = obj.$$hashKey = s.nextUid();
7601 return objType + ':' + key;
7604 // setHashKey: function setHashKey(obj, h) {
7606 // obj.$$hashKey = h;
7609 // delete obj.$$hashKey;
7614 ['width', 'height'].forEach(function (name) {
7615 var capsName = angular.uppercase(name.charAt(0)) + name.substr(1);
7616 s['element' + capsName] = function (elem, extra) {
7618 if (typeof(e.length) !== 'undefined' && e.length) {
7623 var styles = getStyles(e);
7624 return e.offsetWidth === 0 && rdisplayswap.test(styles.display) ?
7625 s.fakeElement(e, cssShow, function(newElm) {
7626 return getWidthOrHeight( newElm, name, extra );
7628 getWidthOrHeight( e, name, extra );
7635 s['outerElement' + capsName] = function (elem, margin) {
7636 return elem ? s['element' + capsName].call(this, elem, margin ? 'margin' : 'border') : null;
7640 // http://stackoverflow.com/a/24107550/888165
7641 s.closestElm = function closestElm(el, selector) {
7642 if (typeof(el.length) !== 'undefined' && el.length) {
7648 // find vendor prefix
7649 ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
7650 if (typeof document.body[fn] === 'function') {
7659 while (el !== null) {
7660 parent = el.parentElement;
7661 if (parent !== null && parent[matchesFn](selector)) {
7670 s.type = function (obj) {
7671 var text = Function.prototype.toString.call(obj.constructor);
7672 return text.match(/function (.*?)\(/)[1];
7675 s.getBorderSize = function getBorderSize(elem, borderType) {
7676 if (typeof(elem.length) !== 'undefined' && elem.length) {
7680 var styles = getStyles(elem);
7683 borderType = 'border-' + borderType;
7686 borderType = 'border';
7689 var val = parseInt(styles[borderType], 10);
7699 // http://stackoverflow.com/a/22948274/888165
7700 // TODO: Opera? Mobile?
7701 s.detectBrowser = function detectBrowser() {
7702 var userAgent = $window.navigator.userAgent;
7704 var browsers = {chrome: /chrome/i, safari: /safari/i, firefox: /firefox/i, ie: /internet explorer|trident\//i};
7706 for (var key in browsers) {
7707 if (browsers[key].test(userAgent)) {
7717 * @name normalizeScrollLeft
7718 * @methodOf ui.grid.service:GridUtil
7720 * @param {element} element The element to get the `scrollLeft` from.
7722 * @returns {int} A normalized scrollLeft value for the current browser.
7725 * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method normalizes them
7727 s.normalizeScrollLeft = function normalizeScrollLeft(element) {
7728 if (typeof(element.length) !== 'undefined' && element.length) {
7729 element = element[0];
7732 var browser = s.detectBrowser();
7734 var scrollLeft = element.scrollLeft;
7736 var dir = angular.element(element).css('direction');
7738 // IE stays normal in RTL
7739 if (browser === 'ie') {
7742 // 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;
7743 else if (browser === 'chrome') {
7744 if (dir === 'rtl') {
7745 // Get the max scroll for the element
7746 var maxScrollLeft = element.scrollWidth - element.clientWidth;
7748 // Subtract the current scroll amount from the max scroll
7749 return maxScrollLeft - scrollLeft;
7755 // Firefox goes negative!
7756 else if (browser === 'firefox') {
7757 return Math.abs(scrollLeft);
7760 // TODO(c0bra): Handle other browsers? Android? iOS? Opera?
7767 * @name normalizeScrollLeft
7768 * @methodOf ui.grid.service:GridUtil
7770 * @param {element} element The element to normalize the `scrollLeft` value for
7771 * @param {int} scrollLeft The `scrollLeft` value to denormalize.
7773 * @returns {int} A normalized scrollLeft value for the current browser.
7776 * Browsers currently handle RTL in different ways, resulting in inconsistent scrollLeft values. This method denormalizes a value for the current browser.
7778 s.denormalizeScrollLeft = function denormalizeScrollLeft(element, scrollLeft) {
7779 if (typeof(element.length) !== 'undefined' && element.length) {
7780 element = element[0];
7783 var browser = s.detectBrowser();
7785 var dir = angular.element(element).css('direction');
7787 // IE stays normal in RTL
7788 if (browser === 'ie') {
7791 // 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;
7792 else if (browser === 'chrome') {
7793 if (dir === 'rtl') {
7794 // Get the max scroll for the element
7795 var maxScrollLeft = element.scrollWidth - element.clientWidth;
7797 // Subtract the current scroll amount from the max scroll
7798 return maxScrollLeft - scrollLeft;
7804 // Firefox goes negative!
7805 else if (browser === 'firefox') {
7806 if (dir === 'rtl') {
7807 return scrollLeft * -1;
7814 // TODO(c0bra): Handle other browsers? Android? iOS? Opera?
7822 * @methodOf ui.grid.service:GridUtil
7824 * @param {string} path Path to evaluate
7826 * @returns {string} A path that is normalized.
7829 * Takes a field path and converts it to bracket notation to allow for special characters in path
7832 * gridUtil.preEval('property') == 'property'
7833 * gridUtil.preEval('nested.deep.prop-erty') = "nested['deep']['prop-erty']"
7836 s.preEval = function (path) {
7837 var m = uiGridConstants.BRACKET_REGEXP.exec(path);
7839 return (m[1] ? s.preEval(m[1]) : m[1]) + m[2] + (m[3] ? s.preEval(m[3]) : m[3]);
7841 path = path.replace(uiGridConstants.APOS_REGEXP, '\\\'');
7842 var parts = path.split(uiGridConstants.DOT_REGEXP);
7843 var preparsed = [parts.shift()]; // first item must be var notation, thus skip
7844 angular.forEach(parts, function (part) {
7845 preparsed.push(part.replace(uiGridConstants.FUNC_REGEXP, '\']$1'));
7847 return preparsed.join('[\'');
7854 * @methodOf ui.grid.service:GridUtil
7856 * @param {function} func function to debounce
7857 * @param {number} wait milliseconds to delay
7858 * @param {bool} immediate execute before delay
7860 * @returns {function} A function that can be executed as debounced function
7863 * Copied from https://github.com/shahata/angular-debounce
7864 * Takes a function, decorates it to execute only 1 time after multiple calls, and returns the decorated function
7867 * var debouncedFunc = gridUtil.debounce(function(){alert('debounced');}, 500);
7873 s.debounce = function (func, wait, immediate) {
7874 var timeout, args, context, result;
7875 function debounce() {
7876 /* jshint validthis:true */
7879 var later = function () {
7882 result = func.apply(context, args);
7885 var callNow = immediate && !timeout;
7887 $timeout.cancel(timeout);
7889 timeout = $timeout(later, wait);
7891 result = func.apply(context, args);
7895 debounce.cancel = function () {
7896 $timeout.cancel(timeout);
7905 // Add 'px' to the end of a number string if it doesn't have it already
7906 module.filter('px', function() {
7907 return function(str) {
7908 if (str.match(/^[\d\.]+$/)) {
7920 angular.module('ui.grid').config(['$provide', function($provide) {
7921 $provide.decorator('i18nService', ['$delegate', function($delegate) {
7922 $delegate.add('da', {
7927 description: 'Grupér rækker udfra en kolonne ved at trække dens overskift hertil.'
7930 placeholder: 'Søg...',
7931 showingItems: 'Viste rækker:',
7932 selectedItems: 'Valgte rækker:',
7933 totalItems: 'Rækker totalt:',
7934 size: 'Side størrelse:',
7935 first: 'Første side',
7937 previous: 'Forrige side',
7941 text: 'Vælg kolonner:',
7944 hide: 'Skjul kolonne'
7952 angular.module('ui.grid').config(['$provide', function($provide) {
7953 $provide.decorator('i18nService', ['$delegate', function($delegate) {
7954 $delegate.add('de', {
7959 description: 'Ziehen Sie eine Spaltenüberschrift hierhin um nach dieser Spalte zu gruppieren.'
7962 placeholder: 'Suche...',
7963 showingItems: 'Zeige Einträge:',
7964 selectedItems: 'Ausgewählte Einträge:',
7965 totalItems: 'Einträge gesamt:',
7966 size: 'Einträge pro Seite:',
7967 first: 'Erste Seite',
7968 next: 'Nächste Seite',
7969 previous: 'Vorherige Seite',
7970 last: 'Letzte Seite'
7973 text: 'Spalten auswählen:'
7976 hide: 'Spalte ausblenden'
7984 angular.module('ui.grid').config(['$provide', function($provide) {
7985 $provide.decorator('i18nService', ['$delegate', function($delegate) {
7986 $delegate.add('en', {
7991 description: 'Drag a column header here and drop it to group by that column.'
7994 placeholder: 'Search...',
7995 showingItems: 'Showing Items:',
7996 selectedItems: 'Selected Items:',
7997 totalItems: 'Total Items:',
7999 first: 'First Page',
8001 previous: 'Previous Page',
8005 text: 'Choose Columns:'
8008 ascending: 'Sort Ascending',
8009 descending: 'Sort Descending',
8010 remove: 'Remove Sort'
8021 angular.module('ui.grid').config(['$provide', function($provide) {
8022 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8023 $delegate.add('es', {
8028 description: 'Arrastre un encabezado de columna aquí y soltarlo para agrupar por esa columna.'
8031 placeholder: 'Buscar...',
8032 showingItems: 'Artículos Mostrando:',
8033 selectedItems: 'Artículos Seleccionados:',
8034 totalItems: 'Artículos Totales:',
8035 size: 'Tamaño de Página:',
8036 first: 'Primera Página',
8037 next: 'Página Siguiente',
8038 previous: 'Página Anterior',
8039 last: 'Última Página'
8042 text: 'Elegir columnas:'
8045 hide: 'Ocultar la columna'
8053 angular.module('ui.grid').config(['$provide', function($provide) {
8054 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8055 $delegate.add('fa', {
8060 description: 'یک عنوان ستون اینجا را بردار و به گروهی از آن ستون بیانداز.'
8063 placeholder: 'جستجو...',
8064 showingItems: 'نمایش موردها:',
8065 selectedItems: 'موردهای انتخاب\u200cشده:',
8066 totalItems: 'همهٔ موردها:',
8067 size: 'اندازهٔ صفحه:',
8070 previous: 'صفحهٔ قبل',
8074 text: 'انتخاب ستون\u200cها:'
8077 hide: 'ستون پنهان کن'
8085 angular.module('ui.grid').config(['$provide', function($provide) {
8086 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8087 $delegate.add('fr', {
8092 description: 'Faites glisser un en-tête de colonne ici et déposez-le vers un groupe par cette colonne.'
8095 placeholder: 'Recherche...',
8096 showingItems: 'Articles Affichage des:',
8097 selectedItems: 'Éléments Articles:',
8098 totalItems: 'Nombre total d\'articles:',
8099 size: 'Taille de page:',
8100 first: 'Première page',
8101 next: 'Page Suivante',
8102 previous: 'Page précédente',
8103 last: 'Dernière page'
8106 text: 'Choisir des colonnes:'
8109 hide: 'Colonne de peau'
8117 angular.module('ui.grid').config(['$provide', function ($provide) {
8118 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
8119 $delegate.add('he', {
8124 description: 'גרור עמודה לכאן ושחרר בכדי לקבץ עמודה זו.'
8127 placeholder: 'חפש...',
8128 showingItems: 'מציג:',
8129 selectedItems: 'סה"כ נבחרו:',
8130 totalItems: 'סה"כ רשומות:',
8131 size: 'תוצאות בדף:',
8134 previous: 'דף קודם',
8141 ascending: 'סדר עולה',
8142 descending: 'סדר יורד',
8154 angular.module('ui.grid').config(['$provide', function($provide) {
8155 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8156 $delegate.add('nl', {
8161 description: 'Sleep hier een kolomnaam heen om op te groeperen.'
8164 placeholder: 'Zoeken...',
8165 showingItems: 'Getoonde items:',
8166 selectedItems: 'Geselecteerde items:',
8167 totalItems: 'Totaal aantal items:',
8168 size: 'Items per pagina:',
8169 first: 'Eerste pagina',
8170 next: 'Volgende pagina',
8171 previous: 'Vorige pagina',
8172 last: 'Laatste pagina'
8175 text: 'Kies kolommen:'
8178 ascending: 'Sorteer oplopend',
8179 descending: 'Sorteer aflopend',
8180 remove: 'Verwijder sortering'
8183 hide: 'Kolom te verbergen'
8191 angular.module('ui.grid').config(['$provide', function($provide) {
8192 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8193 $delegate.add('pt-br', {
8198 description: 'Arraste e solte uma coluna aqui para agrupar por essa coluna'
8201 placeholder: 'Procurar...',
8202 showingItems: 'Mostrando os Itens:',
8203 selectedItems: 'Items Selecionados:',
8204 totalItems: 'Total de Itens:',
8205 size: 'Tamanho da Página:',
8206 first: 'Primeira Página',
8207 next: 'Próxima Página',
8208 previous: 'Página Anterior',
8209 last: 'Última Página'
8212 text: 'Selecione as colunas:'
8215 hide: 'Esconder coluna'
8223 angular.module('ui.grid').config(['$provide', function($provide) {
8224 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8225 $delegate.add('ru', {
8230 description: 'Для группировки по столбцу перетащите сюда его название.'
8233 placeholder: 'Поиск...',
8234 showingItems: 'Показать элементы:',
8235 selectedItems: 'Выбранные элементы:',
8236 totalItems: 'Всего элементов:',
8237 size: 'Размер страницы:',
8238 first: 'Первая страница',
8239 next: 'Следующая страница',
8240 previous: 'Предыдущая страница',
8241 last: 'Последняя страница'
8244 text: 'Выбрать столбцы:'
8247 ascending: 'По возрастанию',
8248 descending: 'По убыванию',
8249 remove: 'Убрать сортировку'
8252 hide: 'спрятать столбец'
8260 angular.module('ui.grid').config(['$provide', function($provide) {
8261 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8262 $delegate.add('sk', {
8267 description: 'Pretiahni sem názov stĺpca pre zoskupenie podľa toho stĺpca.'
8270 placeholder: 'Hľadaj...',
8271 showingItems: 'Zobrazujem položky:',
8272 selectedItems: 'Vybraté položky:',
8273 totalItems: 'Počet položiek:',
8275 first: 'Prvá strana',
8276 next: 'Ďalšia strana',
8277 previous: 'Predchádzajúca strana',
8278 last: 'Posledná strana'
8281 text: 'Vyberte stĺpce:'
8284 ascending: 'Zotriediť vzostupne',
8285 descending: 'Zotriediť zostupne',
8286 remove: 'Vymazať triedenie'
8296 * @name ui.grid.i18n
8300 * This module provides i18n functions to ui.grid and any application that wants to use it
8303 * <div doc-module-components="ui.grid.i18n"></div>
8307 var DIRECTIVE_ALIASES = ['uiT', 'uiTranslate'];
8308 var FILTER_ALIASES = ['t', 'uiTranslate'];
8310 var module = angular.module('ui.grid.i18n');
8315 * @name ui.grid.i18n.constant:i18nConstants
8317 * @description constants available in i18n module
8319 module.constant('i18nConstants', {
8320 MISSING: '[MISSING]',
8321 UPDATE_EVENT: '$uiI18n',
8323 LOCALE_DIRECTIVE_ALIAS: 'uiI18n',
8324 // default to english
8328 // module.config(['$provide', function($provide) {
8329 // $provide.decorator('i18nService', ['$delegate', function($delegate) {}])}]);
8333 * @name ui.grid.i18n.service:i18nService
8335 * @description Services for i18n
8337 module.service('i18nService', ['$log', 'i18nConstants', '$rootScope',
8338 function ($log, i18nConstants, $rootScope) {
8343 get: function (lang) {
8344 return this._langs[lang.toLowerCase()];
8346 add: function (lang, strings) {
8347 var lower = lang.toLowerCase();
8348 if (!this._langs[lower]) {
8349 this._langs[lower] = {};
8351 angular.extend(this._langs[lower], strings);
8353 getAllLangs: function () {
8359 for (var key in this._langs) {
8365 setCurrent: function (lang) {
8366 this.current = lang.toLowerCase();
8368 getCurrentLang: function () {
8369 return this.current;
8378 * @methodOf ui.grid.i18n.service:i18nService
8379 * @description Adds the languages and strings to the cache. Decorate this service to
8380 * add more translation strings
8381 * @param {string} lang language to add
8382 * @param {object} stringMaps of strings to add grouped by property names
8385 * i18nService.add('en', {
8388 * label2: 'some more items'
8392 * description: 'Drag a column header here and drop it to group by that column.'
8397 add: function (langs, stringMaps) {
8398 if (typeof(langs) === 'object') {
8399 angular.forEach(langs, function (lang) {
8401 langCache.add(lang, stringMaps);
8405 langCache.add(langs, stringMaps);
8412 * @methodOf ui.grid.i18n.service:i18nService
8413 * @description return all currently loaded languages
8414 * @returns {array} string
8416 getAllLangs: function () {
8417 return langCache.getAllLangs();
8423 * @methodOf ui.grid.i18n.service:i18nService
8424 * @description return all currently loaded languages
8425 * @param {string} lang to return. If not specified, returns current language
8426 * @returns {object} the translation string maps for the language
8428 get: function (lang) {
8429 var language = lang ? lang : service.getCurrentLang();
8430 return langCache.get(language);
8436 * @methodOf ui.grid.i18n.service:i18nService
8437 * @description returns the text specified in the path or a Missing text if text is not found
8438 * @param {string} path property path to use for retrieving text from string map
8439 * @param {string} lang to return. If not specified, returns current language
8440 * @returns {object} the translation for the path
8443 * i18nService.getSafeText('sort.ascending')
8446 getSafeText: function (path, lang) {
8447 var language = lang ? lang : service.getCurrentLang();
8448 var trans = langCache.get(language);
8451 return i18nConstants.MISSING;
8454 var paths = path.split('.');
8455 var current = trans;
8457 for (var i = 0; i < paths.length; ++i) {
8458 if (current[paths[i]] === undefined || current[paths[i]] === null) {
8459 return i18nConstants.MISSING;
8461 current = current[paths[i]];
8471 * @name setCurrentLang
8472 * @methodOf ui.grid.i18n.service:i18nService
8473 * @description sets the current language to use in the application
8474 * $broadcasts the Update_Event on the $rootScope
8475 * @param {string} lang to set
8478 * i18nService.setCurrentLang('fr');
8482 setCurrentLang: function (lang) {
8484 langCache.setCurrent(lang);
8485 $rootScope.$broadcast(i18nConstants.UPDATE_EVENT);
8491 * @name getCurrentLang
8492 * @methodOf ui.grid.i18n.service:i18nService
8493 * @description returns the current language used in the application
8495 getCurrentLang: function () {
8496 var lang = langCache.getCurrentLang();
8498 lang = i18nConstants.DEFAULT_LANG;
8499 langCache.setCurrent(lang);
8510 var localeDirective = function (i18nService, i18nConstants) {
8512 compile: function () {
8514 pre: function ($scope, $elm, $attrs) {
8515 var alias = i18nConstants.LOCALE_DIRECTIVE_ALIAS;
8516 // check for watchable property
8517 var lang = $scope.$eval($attrs[alias]);
8519 $scope.$watch($attrs[alias], function () {
8520 i18nService.setCurrentLang(lang);
8522 } else if ($attrs.$$observers) {
8523 $attrs.$observe(alias, function () {
8524 i18nService.setCurrentLang($attrs[alias] || i18nConstants.DEFAULT_LANG);
8533 module.directive('uiI18n', ['i18nService', 'i18nConstants', localeDirective]);
8536 var uitDirective = function ($parse, i18nService, i18nConstants) {
8539 compile: function () {
8541 pre: function ($scope, $elm, $attrs) {
8542 var alias1 = DIRECTIVE_ALIASES[0],
8543 alias2 = DIRECTIVE_ALIASES[1];
8544 var token = $attrs[alias1] || $attrs[alias2] || $elm.html();
8545 var missing = i18nConstants.MISSING + token;
8547 if ($attrs.$$observers) {
8548 var prop = $attrs[alias1] ? alias1 : alias2;
8549 observer = $attrs.$observe(prop, function (result) {
8551 $elm.html($parse(result)(i18nService.getCurrentLang()) || missing);
8555 var getter = $parse(token);
8556 var listener = $scope.$on(i18nConstants.UPDATE_EVENT, function (evt) {
8558 observer($attrs[alias1] || $attrs[alias2]);
8560 // set text based on i18n current language
8561 $elm.html(getter(i18nService.get()) || missing);
8564 $scope.$on('$destroy', listener);
8566 $elm.html(getter(i18nService.get()) || missing);
8573 DIRECTIVE_ALIASES.forEach(function (alias) {
8574 module.directive(alias, ['$parse', 'i18nService', 'i18nConstants', uitDirective]);
8577 // optional filter syntax
8578 var uitFilter = function ($parse, i18nService, i18nConstants) {
8579 return function (data) {
8580 var getter = $parse(data);
8581 // set text based on i18n current language
8582 return getter(i18nService.get()) || i18nConstants.MISSING + data;
8586 FILTER_ALIASES.forEach(function (alias) {
8587 module.filter(alias, ['$parse', 'i18nService', 'i18nConstants', uitFilter]);
8592 angular.module('ui.grid').config(['$provide', function($provide) {
8593 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8594 $delegate.add('zh-cn', {
8599 description: '拖曳表头到此处以进行分组'
8602 placeholder: '搜索...',
8603 showingItems: '当前显示条目:',
8604 selectedItems: '选中条目:',
8605 totalItems: '条目总数:',
8625 angular.module('ui.grid').config(['$provide', function($provide) {
8626 $provide.decorator('i18nService', ['$delegate', function($delegate) {
8627 $delegate.add('zh-tw', {
8632 description: '拖拉表頭到此處以進行分組'
8635 placeholder: '搜尋...',
8636 showingItems: '目前顯示筆數:',
8637 selectedItems: '選取筆數:',
8660 * @name ui.grid.autoResize
8664 * #ui.grid.autoResize
8665 * This module provides auto-resizing functionality to ui-grid
8668 var module = angular.module('ui.grid.autoResize', ['ui.grid']);
8671 module.directive('uiGridAutoResize', ['$log', '$timeout', 'gridUtil', function ($log, $timeout, gridUtil) {
8675 link: function ($scope, $elm, $attrs, uiGridCtrl) {
8676 var prevGridWidth, prevGridHeight;
8678 function getDimensions() {
8679 prevGridHeight = gridUtil.elementHeight($elm);
8680 prevGridWidth = gridUtil.elementWidth($elm);
8683 // Initialize the dimensions
8687 function startTimeout() {
8688 $timeout.cancel(canceler);
8690 canceler = $timeout(function () {
8691 var newGridHeight = gridUtil.elementHeight($elm);
8692 var newGridWidth = gridUtil.elementWidth($elm);
8694 if (newGridHeight !== prevGridHeight || newGridWidth !== prevGridWidth) {
8695 uiGridCtrl.grid.gridHeight = newGridHeight;
8696 uiGridCtrl.grid.gridWidth = newGridWidth;
8698 uiGridCtrl.grid.queueRefresh()
8713 $scope.$on('$destroy', function() {
8714 $timeout.cancel(canceler);
8722 var module = angular.module('ui.grid.cellNav', ['ui.grid']);
8724 function RowCol(row, col) {
8731 * @name ui.grid.cellNav.constant:uiGridCellNavConstants
8733 * @description constants available in cellNav
8735 module.constant('uiGridCellNavConstants', {
8736 FEATURE_NAME : 'gridCellNav',
8737 CELL_NAV_EVENT: 'cellNav',
8738 direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3}
8743 * @name ui.grid.cellNav.service:uiGridCellNavService
8745 * @description Services for cell navigation features. If you don't like the key maps we use,
8746 * or the direction cells navigation, override with a service decorator (see angular docs)
8748 module.service('uiGridCellNavService', ['$log', 'uiGridConstants', 'uiGridCellNavConstants', '$q',
8749 function ($log, uiGridConstants, uiGridCellNavConstants, $q) {
8753 initializeGrid: function (grid) {
8754 grid.registerColumnBuilder(service.cellNavColumnBuilder);
8756 //create variables for state
8758 grid.cellNav.lastRowCol = null;
8762 * @name ui.grid.cellNav.api:PublicApi
8764 * @description Public Api for cellNav feature
8772 * @eventOf ui.grid.cellNav.api:PublicApi
8773 * @description raised when the active cell is changed
8775 * gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
8777 * @param {object} newRowCol new position
8778 * @param {object} oldRowCol old position
8780 navigate : function(newRowCol, oldRowCol){}
8788 * @methodOf ui.grid.cellNav.api:PublicApi
8789 * @description brings the specified row and column into view
8790 * @param {Grid} grid the grid you'd like to act upon, usually available
8792 * @param {object} $scope a scope we can broadcast events from
8793 * @param {object} rowEntity gridOptions.data[] array instance to make visible
8794 * @param {object} colDef to make visible
8796 scrollTo: function (grid, $scope, rowEntity, colDef) {
8797 service.scrollTo(grid, $scope, rowEntity, colDef);
8801 * @name getFocusedCell
8802 * @methodOf ui.grid.cellNav.api:PublicApi
8803 * @description returns the current (or last if Grid does not have focus) focused row and column
8804 * <br> value is null if no selection has occurred
8806 getFocusedCell: function () {
8807 return grid.cellNav.lastRowCol;
8813 grid.api.registerEventsFromObject(publicApi.events);
8815 grid.api.registerMethodsFromObject(publicApi.methods);
8822 * @name getDirection
8823 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
8824 * @description determines which direction to for a given keyDown event
8825 * @returns {uiGridCellNavConstants.direction} direction
8827 getDirection: function (evt) {
8828 if (evt.keyCode === uiGridConstants.keymap.LEFT ||
8829 (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
8830 return uiGridCellNavConstants.direction.LEFT;
8832 if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
8833 evt.keyCode === uiGridConstants.keymap.TAB) {
8834 return uiGridCellNavConstants.direction.RIGHT;
8837 if (evt.keyCode === uiGridConstants.keymap.UP ||
8838 (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey)) {
8839 return uiGridCellNavConstants.direction.UP;
8842 if (evt.keyCode === uiGridConstants.keymap.DOWN ||
8843 evt.keyCode === uiGridConstants.keymap.ENTER) {
8844 return uiGridCellNavConstants.direction.DOWN;
8852 * @name getNextRowCol
8853 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
8854 * @description returns the next row and column for a given direction
8855 * columns that are not focusable are skipped
8856 * @param {object} direction navigation direction
8857 * @param {Grid} grid current grid
8858 * @param {GridRow} curRow Gridrow
8859 * @param {GridCol} curCol Gridcol
8860 * @returns {uiGridCellNavConstants.direction} rowCol object
8862 getNextRowCol: function (direction, grid, curRow, curCol) {
8863 switch (direction) {
8864 case uiGridCellNavConstants.direction.LEFT:
8865 return service.getRowColLeft(grid.rows, grid.columns, curRow, curCol);
8866 case uiGridCellNavConstants.direction.RIGHT:
8867 return service.getRowColRight(grid.rows, grid.columns, curRow, curCol);
8868 case uiGridCellNavConstants.direction.UP:
8869 return service.getRowColUp(grid.rows, grid.columns, curRow, curCol);
8870 case uiGridCellNavConstants.direction.DOWN:
8871 return service.getRowColDown(grid.rows, grid.columns, curRow, curCol);
8875 getRowColLeft: function (rows, cols, curRow, curCol) {
8876 var colIndex = service.getNextColIndexLeft(cols, curCol);
8878 if (colIndex > curCol.index) {
8879 if (curRow.index === 0) {
8880 return new RowCol(curRow, cols[colIndex]); //return same row
8883 //up one row and far right column
8884 return new RowCol(rows[curRow.index - 1], cols[colIndex]);
8888 return new RowCol(curRow, cols[colIndex]);
8892 getRowColRight: function (rows, cols, curRow, curCol) {
8893 var colIndex = service.getNextColIndexRight(cols, curCol);
8895 if (colIndex < curCol.index) {
8896 if (curRow.index === rows.length - 1) {
8897 return new RowCol(curRow, cols[colIndex]); //return same row
8900 //down one row and far left column
8901 return new RowCol(rows[curRow.index + 1], cols[colIndex]);
8905 return new RowCol(curRow, cols[colIndex]);
8909 getNextColIndexLeft: function (cols, curCol) {
8910 //start with next col to the left or the end of the array if curCol is the first col
8911 var i = curCol.index === 0 ? cols.length - 1 : curCol.index - 1;
8913 //find first focusable column to the left
8914 //circle around to the end of the array if no col is found
8915 while (i !== curCol.index) {
8916 if (cols[i].colDef.allowCellFocus) {
8920 //go to end of array if at the beginning
8922 i = cols.length - 1;
8929 getNextColIndexRight: function (cols, curCol) {
8930 //start with next col to the right or the beginning of the array if curCol is the last col
8931 var i = curCol.index === cols.length - 1 ? 0 : curCol.index + 1;
8933 //find first focusable column to the right
8934 //circle around to the beginning of the array if no col is found
8935 while (i !== curCol.index) {
8936 if (cols[i].colDef.allowCellFocus) {
8940 //go to end of array if at the beginning
8941 if (i > cols.length - 1) {
8949 getRowColUp: function (rows, cols, curRow, curCol) {
8950 //if curCol is not focusable, then we need to find a focusable column to the right
8951 //this shouldn't ever happen in the grid, but we handle it anyway
8952 var colIndex = curCol.colDef.allowCellFocus ? curCol.index : service.getNextColIndexRight(cols, curCol);
8955 if (curRow.index === 0) {
8956 return new RowCol(curRow, cols[colIndex]); //return same row
8960 return new RowCol(rows[curRow.index - 1], cols[colIndex]);
8964 getRowColDown: function (rows, cols, curRow, curCol) {
8965 //if curCol is not focusable, then we need to find a focusable column to the right
8966 //this shouldn't ever happen in the grid, but we handle it anyway
8967 var colIndex = curCol.colDef.allowCellFocus ? curCol.index : service.getNextColIndexRight(cols, curCol);
8970 if (curRow.index === rows.length - 1) {
8971 return new RowCol(curRow, cols[colIndex]); //return same row
8975 return new RowCol(rows[curRow.index + 1], cols[colIndex]);
8981 * @name cellNavColumnBuilder
8982 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
8983 * @description columnBuilder function that adds cell navigation properties to grid column
8984 * @returns {promise} promise that will load any needed templates when resolved
8986 cellNavColumnBuilder: function (colDef, col, gridOptions) {
8991 * @name ui.grid.cellNav.api:ColumnDef
8993 * @description Column Definitions for cellNav feature, these are available to be
8994 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
8999 * @name allowCellFocus
9000 * @propertyOf ui.grid.cellNav.api:ColumnDef
9001 * @description Enable focus on a cell.
9002 * <br/>Defaults to true
9004 colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus ;
9006 return $q.all(promises);
9011 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
9012 * @name scrollVerticallyTo
9013 * @description Scroll the grid vertically such that the specified
9015 * @param {Grid} grid the grid you'd like to act upon, usually available
9017 * @param {object} $scope a scope we can broadcast events from
9018 * @param {object} rowEntity gridOptions.data[] array instance to make visible
9019 * @param {object} colDef to make visible
9021 scrollTo: function (grid, $scope, rowEntity, colDef) {
9024 if ( rowEntity !== null ){
9025 var row = grid.getRow(rowEntity);
9027 args.y = { percentage: row.index / grid.renderContainers.body.visibleRowCache.length };
9031 if ( colDef !== null ){
9032 var col = grid.getColumn(colDef.name ? colDef.name : colDef.field);
9034 args.x = { percentage: this.getLeftWidth(grid, col.index) / this.getLeftWidth(grid, grid.renderContainers.body.visibleColumnCache.length - 1) };
9038 if ( args.y || args.x ){
9039 $scope.$broadcast(uiGridConstants.events.GRID_SCROLL, args);
9046 * @methodOf ui.grid.cellNav.service:uiGridCellNavService
9047 * @name getLeftWidth
9048 * @description Get the current drawn width of the columns in the
9049 * grid up to and including the numbered column
9050 * @param {Grid} grid the grid you'd like to act upon, usually available
9052 * @param {object} colIndex the column to total up to and including
9054 getLeftWidth: function( grid, colIndex ){
9057 if ( !colIndex ){ return; }
9059 for ( var i=0; i <= colIndex; i++ ){
9060 if ( grid.renderContainers.body.visibleColumnCache[i].drawnWidth ){
9061 width += grid.renderContainers.body.visibleColumnCache[i].drawnWidth;
9074 * @name ui.grid.cellNav.directive:uiCellNav
9078 * @description Adds cell navigation features to the grid columns
9081 <example module="app">
9082 <file name="app.js">
9083 var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
9085 app.controller('MainCtrl', ['$scope', function ($scope) {
9087 { name: 'Bob', title: 'CEO' },
9088 { name: 'Frank', title: 'Lowly Developer' }
9091 $scope.columnDefs = [
9097 <file name="index.html">
9098 <div ng-controller="MainCtrl">
9099 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
9104 module.directive('uiGridCellnav', ['$log', 'uiGridCellNavService', 'uiGridCellNavConstants',
9105 function ($log, uiGridCellNavService, uiGridCellNavConstants) {
9111 compile: function () {
9113 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
9115 var grid = uiGridCtrl.grid;
9116 uiGridCellNavService.initializeGrid(grid);
9118 uiGridCtrl.cellNav = {};
9120 // $log.debug('uiGridEdit preLink');
9121 uiGridCtrl.cellNav.broadcastCellNav = function (newRowCol) {
9122 $scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol);
9123 uiGridCtrl.cellNav.broadcastFocus(newRowCol.row, newRowCol.col);
9126 uiGridCtrl.cellNav.broadcastFocus = function (row, col) {
9127 if (grid.cellNav.lastRowCol === null || (grid.cellNav.lastRowCol.row !== row || grid.cellNav.lastRowCol.col !== col)) {
9128 var newRowCol = new RowCol(row, col);
9129 grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol);
9130 grid.cellNav.lastRowCol = newRowCol;
9135 post: function ($scope, $elm, $attrs, uiGridCtrl) {
9145 * @name ui.grid.cellNav.directive:uiGridCell
9148 * @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
9150 module.directive('uiGridCell', ['uiGridCellNavService', '$log', 'uiGridCellNavConstants',
9151 function (uiGridCellNavService, $log, uiGridCellNavConstants) {
9153 priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
9157 link: function ($scope, $elm, $attrs, uiGridCtrl) {
9158 if (!$scope.col.colDef.allowCellFocus) {
9164 $elm.on('keydown', function (evt) {
9165 var direction = uiGridCellNavService.getDirection(evt);
9166 if (direction === null) {
9170 var rowCol = uiGridCellNavService.getNextRowCol(direction, $scope.grid, $scope.row, $scope.col);
9172 //$log.debug('next row ' + rowCol.row.index + ' next Col ' + rowCol.col.colDef.name);
9173 uiGridCtrl.cellNav.broadcastCellNav(rowCol);
9179 $elm.find('div').on('focus', function (evt) {
9180 uiGridCtrl.cellNav.broadcastFocus($scope.row, $scope.col);
9183 $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function(evt,rowCol){
9184 if (rowCol.row === $scope.row &&
9185 rowCol.col === $scope.col){
9186 // $log.debug('Setting focus on Row ' + rowCol.row.index + ' Col ' + rowCol.col.colDef.name);
9191 function setTabEnabled(){
9192 $elm.find('div').attr("tabindex", -1);
9195 function setFocused(){
9196 var div = $elm.find('div');
9198 div.attr("tabindex", 0);
9211 * @name ui.grid.edit
9215 * This module provides cell editing capability to ui.grid. The goal was to emulate keying data in a spreadsheet via
9219 * To really get the full spreadsheet-like data entry, the ui.grid.cellNav module should be used. This will allow the
9220 * user to key data and then tab, arrow, or enter to the cells beside or below.
9222 * <div doc-module-components="ui.grid.edit"></div>
9225 var module = angular.module('ui.grid.edit', ['ui.grid']);
9229 * @name ui.grid.edit.constant:uiGridEditConstants
9231 * @description constants available in edit module
9233 module.constant('uiGridEditConstants', {
9234 EDITABLE_CELL_TEMPLATE: /EDITABLE_CELL_TEMPLATE/g,
9235 //must be lowercase because template bulder converts to lower
9236 EDITABLE_CELL_DIRECTIVE: /editable_cell_directive/g,
9238 BEGIN_CELL_EDIT: 'uiGridEventBeginCellEdit',
9239 END_CELL_EDIT: 'uiGridEventEndCellEdit',
9240 CANCEL_CELL_EDIT: 'uiGridEventCancelCellEdit'
9246 * @name ui.grid.edit.service:uiGridEditService
9248 * @description Services for editing features
9250 module.service('uiGridEditService', ['$log', '$q', '$templateCache', 'uiGridConstants', 'gridUtil',
9251 function ($log, $q, $templateCache, uiGridConstants, gridUtil) {
9255 initializeGrid: function (grid) {
9257 service.defaultGridOptions(grid.options);
9259 grid.registerColumnBuilder(service.editColumnBuilder);
9263 * @name ui.grid.edit.api:PublicApi
9265 * @description Public Api for edit feature
9272 * @name afterCellEdit
9273 * @eventOf ui.grid.edit.api:PublicApi
9274 * @description raised when cell editing is complete
9276 * gridApi.edit.on.afterCellEdit(scope,function(rowEntity, colDef){})
9278 * @param {object} rowEntity the options.data element that was edited
9279 * @param {object} colDef the column that was edited
9280 * @param {object} newValue new value
9281 * @param {object} oldValue old value
9283 afterCellEdit: function (rowEntity, colDef, newValue, oldValue) {
9287 * @name beginCellEdit
9288 * @eventOf ui.grid.edit.api:PublicApi
9289 * @description raised when cell editing starts on a cell
9291 * gridApi.edit.on.beginCellEdit(scope,function(rowEntity, colDef){})
9293 * @param {object} rowEntity the options.data element that was edited
9294 * @param {object} colDef the column that was edited
9296 beginCellEdit: function (rowEntity, colDef) {
9300 * @name cancelCellEdit
9301 * @eventOf ui.grid.edit.api:PublicApi
9302 * @description raised when cell editing is cancelled on a cell
9304 * gridApi.edit.on.cancelCellEdit(scope,function(rowEntity, colDef){})
9306 * @param {object} rowEntity the options.data element that was edited
9307 * @param {object} colDef the column that was edited
9309 cancelCellEdit: function (rowEntity, colDef) {
9318 grid.api.registerEventsFromObject(publicApi.events);
9319 //grid.api.registerMethodsFromObject(publicApi.methods);
9323 defaultGridOptions: function (gridOptions) {
9327 * @name ui.grid.edit.api:GridOptions
9329 * @description Options for configuring the edit feature, these are available to be
9330 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
9335 * @name enableCellEdit
9336 * @propertyOf ui.grid.edit.api:GridOptions
9337 * @description If defined, sets the default value for the editable flag on each individual colDefs
9338 * if their individual enableCellEdit configuration is not defined. Defaults to undefined.
9343 * @name cellEditableCondition
9344 * @propertyOf ui.grid.edit.api:GridOptions
9345 * @description If specified, either a value or function to be used by all columns before editing.
9346 * If falsy, then editing of cell is not allowed.
9350 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
9355 gridOptions.cellEditableCondition = gridOptions.cellEditableCondition === undefined ? true : gridOptions.cellEditableCondition;
9359 * @name editableCellTemplate
9360 * @propertyOf ui.grid.edit.api:GridOptions
9361 * @description If specified, cellTemplate to use as the editor for all columns.
9362 * <br/> defaults to 'ui-grid/cellTextEditor'
9367 * @name enableCellEditOnFocus
9368 * @propertyOf ui.grid.edit.api:GridOptions
9369 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
9370 * <br/>_requires cellNav feature and the edit feature to be enabled_
9372 //enableCellEditOnFocus can only be used if cellnav module is used
9373 gridOptions.enableCellEditOnFocus = gridOptions.enableCellEditOnFocus === undefined ?
9374 false: gridOptions.enableCellEditOnFocus;
9379 * @name editColumnBuilder
9380 * @methodOf ui.grid.edit.service:uiGridEditService
9381 * @description columnBuilder function that adds edit properties to grid column
9382 * @returns {promise} promise that will load any needed templates when resolved
9384 editColumnBuilder: function (colDef, col, gridOptions) {
9390 * @name ui.grid.edit.api:ColumnDef
9392 * @description Column Definition for edit feature, these are available to be
9393 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
9398 * @name enableCellEdit
9399 * @propertyOf ui.grid.edit.api:ColumnDef
9400 * @description enable editing on column
9402 colDef.enableCellEdit = colDef.enableCellEdit === undefined ? (gridOptions.enableCellEdit === undefined ?
9403 (colDef.type !== 'object'):gridOptions.enableCellEdit) : colDef.enableCellEdit;
9407 * @name cellEditableCondition
9408 * @propertyOf ui.grid.edit.api:ColumnDef
9409 * @description If specified, either a value or function evaluated before editing cell. If falsy, then editing of cell is not allowed.
9413 * //use $scope.row.entity and $scope.col.colDef to determine if editing is allowed
9418 colDef.cellEditableCondition = colDef.cellEditableCondition === undefined ? gridOptions.cellEditableCondition : colDef.cellEditableCondition;
9422 * @name editableCellTemplate
9423 * @propertyOf ui.grid.edit.api:ColumnDef
9424 * @description cell template to be used when editing this column. Can be Url or text template
9425 * <br/>Defaults to gridOptions.editableCellTemplate
9427 if (colDef.enableCellEdit) {
9428 colDef.editableCellTemplate = colDef.editableCellTemplate || gridOptions.editableCellTemplate || 'ui-grid/cellEditor';
9430 promises.push(gridUtil.getTemplate(colDef.editableCellTemplate)
9432 function (template) {
9433 col.editableCellTemplate = template;
9436 // Todo handle response error here?
9437 throw new Error("Couldn't fetch/use colDef.editableCellTemplate '" + colDef.editableCellTemplate + "'");
9443 * @name enableCellEditOnFocus
9444 * @propertyOf ui.grid.edit.api:ColumnDef
9445 * @requires ui.grid.cellNav
9446 * @description If true, then editor is invoked as soon as cell receives focus. Default false.
9447 * <br>_requires both the cellNav feature and the edit feature to be enabled_
9449 //enableCellEditOnFocus can only be used if cellnav module is used
9450 colDef.enableCellEditOnFocus = colDef.enableCellEditOnFocus === undefined ? gridOptions.enableCellEditOnFocus : colDef.enableCellEditOnFocus;
9452 return $q.all(promises);
9457 * @name isStartEditKey
9458 * @methodOf ui.grid.edit.service:uiGridEditService
9459 * @description Determines if a keypress should start editing. Decorate this service to override with your
9460 * own key events. See service decorator in angular docs.
9461 * @param {Event} evt keydown event
9462 * @returns {boolean} true if an edit should start
9464 isStartEditKey: function (evt) {
9465 if (evt.keyCode === uiGridConstants.keymap.LEFT ||
9466 (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey) ||
9468 evt.keyCode === uiGridConstants.keymap.RIGHT ||
9469 evt.keyCode === uiGridConstants.keymap.TAB ||
9471 evt.keyCode === uiGridConstants.keymap.UP ||
9472 (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ||
9474 evt.keyCode === uiGridConstants.keymap.DOWN ||
9475 evt.keyCode === uiGridConstants.keymap.ENTER) {
9491 * @name ui.grid.edit.directive:uiGridEdit
9495 * @description Adds editing features to the ui-grid directive.
9498 <example module="app">
9499 <file name="app.js">
9500 var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
9502 app.controller('MainCtrl', ['$scope', function ($scope) {
9504 { name: 'Bob', title: 'CEO' },
9505 { name: 'Frank', title: 'Lowly Developer' }
9508 $scope.columnDefs = [
9509 {name: 'name', enableCellEdit: true},
9510 {name: 'title', enableCellEdit: true}
9514 <file name="index.html">
9515 <div ng-controller="MainCtrl">
9516 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit></div>
9521 module.directive('uiGridEdit', ['$log', 'uiGridEditService', function ($log, uiGridEditService) {
9527 compile: function () {
9529 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
9530 uiGridEditService.initializeGrid(uiGridCtrl.grid);
9532 post: function ($scope, $elm, $attrs, uiGridCtrl) {
9541 * @name ui.grid.edit.directive:uiGridCell
9545 * @description Stacks on top of ui.grid.uiGridCell to provide in-line editing capabilities to the cell
9548 * Binds edit start events to the uiGridCell element. When the events fire, the gridCell element is appended
9549 * with the columnDef.editableCellTemplate element ('cellEditor.html' by default).
9551 * The editableCellTemplate should respond to uiGridEditConstants.events.BEGIN\_CELL\_EDIT angular event
9552 * and do the initial steps needed to edit the cell (setfocus on input element, etc).
9554 * When the editableCellTemplate recognizes that the editing is ended (blur event, Enter key, etc.)
9555 * it should emit the uiGridEditConstants.events.END\_CELL\_EDIT event.
9557 * If editableCellTemplate recognizes that the editing has been cancelled (esc key)
9558 * it should emit the uiGridEditConstants.events.CANCEL\_CELL\_EDIT event. The original value
9559 * will be set back on the model by the uiGridCell directive.
9561 * Events that invoke editing:
9563 * - F2 keydown (when using cell selection)
9565 * Events that end editing:
9566 * - Dependent on the specific editableCellTemplate
9567 * - Standards should be blur and enter keydown
9569 * Events that cancel editing:
9570 * - Dependent on the specific editableCellTemplate
9571 * - Standards should be Esc keydown
9573 * Grid Events that end editing:
9574 * - uiGridConstants.events.GRID_SCROLL
9577 module.directive('uiGridCell',
9578 ['$compile', 'uiGridConstants', 'uiGridEditConstants', '$log', '$parse', 'uiGridEditService',
9579 function ($compile, uiGridConstants, uiGridEditConstants, $log, $parse, uiGridEditService) {
9581 priority: -100, // run after default uiGridCell directive
9584 link: function ($scope, $elm, $attrs) {
9585 if (!$scope.col.colDef.enableCellEdit) {
9592 var isFocusedBeforeEdit = false;
9595 registerBeginEditEvents();
9597 function registerBeginEditEvents() {
9598 $elm.on('dblclick', beginEdit);
9599 $elm.on('keydown', beginEditKeyDown);
9600 if ($scope.col.colDef.enableCellEditOnFocus) {
9601 $elm.find('div').on('focus', beginEditFocus);
9605 function cancelBeginEditEvents() {
9606 $elm.off('dblclick', beginEdit);
9607 $elm.off('keydown', beginEditKeyDown);
9608 if ($scope.col.colDef.enableCellEditOnFocus) {
9609 $elm.find('div').off('focus', beginEditFocus);
9613 function beginEditFocus(evt) {
9614 evt.stopPropagation();
9618 function beginEditKeyDown(evt) {
9619 if (uiGridEditService.isStartEditKey(evt)) {
9624 function shouldEdit(col, row) {
9625 return !row.isSaving &&
9626 ( angular.isFunction(col.colDef.cellEditableCondition) ?
9627 col.colDef.cellEditableCondition($scope) :
9628 col.colDef.cellEditableCondition );
9634 * @name editDropdownOptionsArray
9635 * @propertyOf ui.grid.edit.api:ColumnDef
9636 * @description an array of values in the format
9637 * [ {id: xxx, value: xxx} ], which is populated
9638 * into the edit dropdown
9643 * @name editDropdownIdLabel
9644 * @propertyOf ui.grid.edit.api:ColumnDef
9645 * @description the label for the "id" field
9646 * in the editDropdownOptionsArray. Defaults
9650 * $scope.gridOptions = {
9652 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
9653 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
9654 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
9661 * @name editDropdownValueLabel
9662 * @propertyOf ui.grid.edit.api:ColumnDef
9663 * @description the label for the "value" field
9664 * in the editDropdownOptionsArray. Defaults
9668 * $scope.gridOptions = {
9670 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
9671 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
9672 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status' }
9679 * @name editDropdownFilter
9680 * @propertyOf ui.grid.edit.api:ColumnDef
9681 * @description A filter that you would like to apply to the values in the options list
9682 * of the dropdown. For example if you were using angular-translate you might set this
9686 * $scope.gridOptions = {
9688 * {name: 'status', editableCellTemplate: 'ui-grid/dropdownEditor',
9689 * editDropdownOptionsArray: [{code: 1, status: 'active'}, {code: 2, status: 'inactive'}],
9690 * editDropdownIdLabel: 'code', editDropdownValueLabel: 'status', editDropdownFilter: 'translate' }
9695 function beginEdit() {
9696 if (!shouldEdit($scope.col, $scope.row)) {
9700 cellModel = $parse($scope.row.getQualifiedColField($scope.col));
9701 //get original value from the cell
9702 origCellValue = cellModel($scope);
9704 html = $scope.col.editableCellTemplate;
9705 html = html.replace(uiGridConstants.COL_FIELD, $scope.row.getQualifiedColField($scope.col));
9707 var optionFilter = $scope.col.colDef.editDropdownFilter ? '|' + $scope.col.colDef.editDropdownFilter : '';
9708 html = html.replace(uiGridConstants.CUSTOM_FILTERS, optionFilter);
9710 $scope.inputType = 'text';
9711 switch ($scope.col.colDef.type){
9713 $scope.inputType = 'checkbox';
9716 $scope.inputType = 'number';
9719 $scope.inputType = 'date';
9723 $scope.editDropdownOptionsArray = $scope.col.colDef.editDropdownOptionsArray;
9724 $scope.editDropdownIdLabel = $scope.col.colDef.editDropdownIdLabel ? $scope.col.colDef.editDropdownIdLabel : 'id';
9725 $scope.editDropdownValueLabel = $scope.col.colDef.editDropdownValueLabel ? $scope.col.colDef.editDropdownValueLabel : 'value';
9728 $scope.$apply(function () {
9730 cancelBeginEditEvents();
9731 cellElement = $compile(html)($scope.$new());
9732 var gridCellContentsEl = angular.element($elm.children()[0]);
9733 isFocusedBeforeEdit = gridCellContentsEl.hasClass(':focus');
9734 gridCellContentsEl.addClass('ui-grid-cell-contents-hidden');
9735 $elm.append(cellElement);
9739 //stop editing when grid is scrolled
9740 var deregOnGridScroll = $scope.$on(uiGridConstants.events.GRID_SCROLL, function () {
9742 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
9743 deregOnGridScroll();
9747 var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function (evt, retainFocus) {
9748 endEdit(retainFocus);
9749 $scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
9750 deregOnEndCellEdit();
9754 var deregOnCancelCellEdit = $scope.$on(uiGridEditConstants.events.CANCEL_CELL_EDIT, function () {
9756 deregOnCancelCellEdit();
9759 $scope.$broadcast(uiGridEditConstants.events.BEGIN_CELL_EDIT);
9760 $scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef);
9763 function endEdit(retainFocus) {
9767 var gridCellContentsEl = angular.element($elm.children()[0]);
9768 //remove edit element
9769 angular.element($elm.children()[1]).remove();
9770 gridCellContentsEl.removeClass('ui-grid-cell-contents-hidden');
9771 if (retainFocus && isFocusedBeforeEdit) {
9772 gridCellContentsEl.focus();
9774 isFocusedBeforeEdit = false;
9776 registerBeginEditEvents();
9779 function cancelEdit() {
9783 cellModel.assign($scope, origCellValue);
9786 $scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
9796 * @name ui.grid.edit.directive:uiGridEditor
9800 * @description input editor directive for editable fields.
9801 * Provides EndEdit and CancelEdit events
9803 * Events that end editing:
9804 * blur and enter keydown
9806 * Events that cancel editing:
9810 module.directive('uiGridEditor',
9811 ['uiGridConstants', 'uiGridEditConstants',
9812 function (uiGridConstants, uiGridEditConstants) {
9815 compile: function () {
9817 pre: function ($scope, $elm, $attrs) {
9820 post: function ($scope, $elm, $attrs) {
9822 //set focus at start of edit
9823 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
9826 $elm.on('blur', function (evt) {
9827 $scope.stopEdit(evt);
9832 $scope.deepEdit = false;
9834 $scope.stopEdit = function (evt) {
9835 if ($scope.inputForm && !$scope.inputForm.$valid) {
9836 evt.stopPropagation();
9837 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
9840 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
9842 $scope.deepEdit = false;
9845 $elm.on('click', function (evt) {
9846 $scope.deepEdit = true;
9849 $elm.on('keydown', function (evt) {
9850 switch (evt.keyCode) {
9851 case uiGridConstants.keymap.ESC:
9852 evt.stopPropagation();
9853 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
9855 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
9856 $scope.stopEdit(evt);
9858 case uiGridConstants.keymap.TAB:
9859 $scope.stopEdit(evt);
9863 if ($scope.deepEdit) {
9864 switch (evt.keyCode) {
9865 case uiGridConstants.keymap.LEFT:
9866 evt.stopPropagation();
9868 case uiGridConstants.keymap.RIGHT:
9869 evt.stopPropagation();
9871 case uiGridConstants.keymap.UP:
9872 evt.stopPropagation();
9874 case uiGridConstants.keymap.DOWN:
9875 evt.stopPropagation();
9890 * @name ui.grid.edit.directive:input
9894 * @description directive to provide binding between input[date] value and ng-model for angular 1.2
9895 * It is similar to input[date] directive of angular 1.3
9897 * Supported date format for input is 'yyyy-MM-dd'
9898 * The directive will set the $valid property of input element and the enclosing form to false if
9899 * model is invalid date or value of input is entered wrong.
9902 module.directive('input', ['$filter', function ($filter) {
9903 function parseDateString(dateString) {
9904 if ('undefined' === typeof dateString || '' === dateString) {
9907 var parts = dateString.split('-');
9908 if (3 !== parts.length) {
9911 var year = parseInt(parts[0], 10);
9912 var month = parseInt(parts[1], 10);
9913 var day = parseInt(parts[2], 10);
9915 if (month < 1 || year < 1 || day < 1) {
9918 return new Date(year, (month - 1), day);
9922 require: '?ngModel',
9923 link: function (scope, element, attrs, ngModel) {
9925 if (angular.version.minor === 2 && attrs.type && 'date' === attrs.type && ngModel) {
9927 ngModel.$formatters.push(function (modelValue) {
9928 ngModel.$setValidity(null,(!modelValue || !isNaN(modelValue.getTime())));
9929 return $filter('date')(modelValue, 'yyyy-MM-dd');
9932 ngModel.$parsers.push(function (viewValue) {
9933 if (viewValue && viewValue.length > 0) {
9934 var dateValue = parseDateString(viewValue);
9935 ngModel.$setValidity(null, (dateValue && !isNaN(dateValue.getTime())));
9939 ngModel.$setValidity(null, true);
9951 * @name ui.grid.edit.directive:uiGridEditDropdown
9955 * @description dropdown editor for editable fields.
9956 * Provides EndEdit and CancelEdit events
9958 * Events that end editing:
9959 * blur and enter keydown, and any left/right nav
9961 * Events that cancel editing:
9965 module.directive('uiGridEditDropdown',
9966 ['uiGridConstants', 'uiGridEditConstants',
9967 function (uiGridConstants, uiGridEditConstants) {
9970 compile: function () {
9972 pre: function ($scope, $elm, $attrs) {
9975 post: function ($scope, $elm, $attrs) {
9977 //set focus at start of edit
9978 $scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
9980 $elm[0].style.width = ($elm[0].parentElement.offsetWidth - 1) + 'px';
9981 $elm.on('blur', function (evt) {
9982 $scope.stopEdit(evt);
9987 $scope.stopEdit = function (evt) {
9988 // no need to validate a dropdown - invalid values shouldn't be
9989 // available in the list
9990 $scope.$emit(uiGridEditConstants.events.END_CELL_EDIT);
9993 $elm.on('keydown', function (evt) {
9994 switch (evt.keyCode) {
9995 case uiGridConstants.keymap.ESC:
9996 evt.stopPropagation();
9997 $scope.$emit(uiGridEditConstants.events.CANCEL_CELL_EDIT);
9999 case uiGridConstants.keymap.ENTER: // Enter (Leave Field)
10000 $scope.stopEdit(evt);
10002 case uiGridConstants.keymap.LEFT:
10003 $scope.stopEdit(evt);
10005 case uiGridConstants.keymap.RIGHT:
10006 $scope.stopEdit(evt);
10008 case uiGridConstants.keymap.UP:
10009 evt.stopPropagation();
10011 case uiGridConstants.keymap.DOWN:
10012 evt.stopPropagation();
10014 case uiGridConstants.keymap.TAB:
10015 $scope.stopEdit(evt);
10031 var module = angular.module('ui.grid.expandable', ['ui.grid']);
10033 module.service('uiGridExpandableService', ['gridUtil', '$log', '$compile', function (gridUtil, $log, $compile) {
10035 initializeGrid: function (grid) {
10039 rowExpandedStateChanged: function (scope, row) {
10045 toggleRowExpansion: function (rowEntity) {
10046 var row = grid.getRow(rowEntity);
10047 if (row !== null) {
10048 service.toggleRowExpansion(grid, row);
10051 expandAllRows: function() {
10052 service.expandAllRows(grid);
10054 collapseAllRows: function() {
10055 service.collapseAllRows(grid);
10060 grid.api.registerEventsFromObject(publicApi.events);
10061 grid.api.registerMethodsFromObject(publicApi.methods);
10063 toggleRowExpansion: function (grid, row) {
10064 row.isExpanded = !row.isExpanded;
10066 if (row.isExpanded) {
10067 row.height = row.grid.options.rowHeight + grid.options.expandable.expandableRowHeight;
10070 row.height = row.grid.options.rowHeight;
10073 grid.api.expandable.raise.rowExpandedStateChanged(row);
10075 expandAllRows: function(grid, $scope) {
10076 angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
10077 if (!row.isExpanded) {
10078 service.toggleRowExpansion(grid, row);
10083 collapseAllRows: function(grid) {
10084 angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
10085 if (row.isExpanded) {
10086 service.toggleRowExpansion(grid, row);
10095 module.directive('uiGridExpandable', ['$log', 'uiGridExpandableService', '$templateCache',
10096 function ($log, uiGridExpandableService, $templateCache) {
10100 require: '^uiGrid',
10102 compile: function () {
10104 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
10105 var expandableRowHeaderColDef = {name: 'expandableButtons', width: 40};
10106 expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
10107 uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
10108 uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
10110 post: function ($scope, $elm, $attrs, uiGridCtrl) {
10117 module.directive('uiGridExpandableRow',
10118 ['uiGridExpandableService', '$timeout', '$log', '$compile', 'uiGridConstants','gridUtil','$interval',
10119 function (uiGridExpandableService, $timeout, $log, $compile, uiGridConstants, gridUtil, $interval) {
10126 compile: function () {
10128 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
10129 gridUtil.getTemplate($scope.grid.options.expandable.rowExpandableTemplate).then(
10130 function (template) {
10131 var expandedRowElement = $compile(template)($scope);
10132 $elm.append(expandedRowElement);
10133 $scope.row.expandedRendered = true;
10137 post: function ($scope, $elm, $attrs, uiGridCtrl) {
10138 $scope.$on('$destroy', function() {
10139 $scope.row.expandedRendered = false;
10147 module.directive('uiGridRow',
10148 ['$compile', '$log', '$templateCache',
10149 function ($compile, $log, $templateCache) {
10153 compile: function ($elm, $attrs) {
10155 pre: function ($scope, $elm, $attrs, controllers) {
10157 $scope.expandableRow = {};
10159 $scope.expandableRow.shouldRenderExpand = function () {
10160 var ret = $scope.colContainer.name === 'body' && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
10164 $scope.expandableRow.shouldRenderFiller = function () {
10165 var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
10170 post: function ($scope, $elm, $attrs, controllers) {
10177 module.directive('uiGridViewport',
10178 ['$compile', '$log', '$templateCache',
10179 function ($compile, $log, $templateCache) {
10183 compile: function ($elm, $attrs) {
10184 var rowRepeatDiv = angular.element($elm.children().children()[0]);
10185 var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
10186 var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
10187 rowRepeatDiv.append(expandedRowElement);
10188 rowRepeatDiv.append(expandedRowFillerElement);
10190 pre: function ($scope, $elm, $attrs, controllers) {
10192 post: function ($scope, $elm, $attrs, controllers) {
10206 * @name ui.grid.exporter
10209 * # ui.grid.exporter
10210 * This module provides the ability to exporter data from the grid.
10212 * Data can be exported in a range of formats, and all data, visible
10213 * data, or selected rows can be exported, with all columns or visible
10216 * No UI is provided, the caller should provide their own UI/buttons
10222 * <div doc-module-components="ui.grid.exporter"></div>
10225 var module = angular.module('ui.grid.exporter', ['ui.grid']);
10229 * @name ui.grid.exporter.constant:uiGridExporterConstants
10231 * @description constants available in exporter module
10235 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
10237 * @description export all data, including data not visible. Can
10238 * be set for either rowTypes or colTypes
10242 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
10244 * @description export only visible data, including data not visible. Can
10245 * be set for either rowTypes or colTypes
10249 * @propertyOf ui.grid.exporter.constant:uiGridExporterConstants
10251 * @description export all data, including data not visible. Can
10252 * be set only for rowTypes, selection of only some columns is
10255 module.constant('uiGridExporterConstants', {
10256 featureName: 'exporter',
10258 VISIBLE: 'visible',
10259 SELECTED: 'selected',
10260 CSV_CONTENT: 'CSV_CONTENT',
10261 LINK_LABEL: 'LINK_LABEL',
10262 BUTTON_LABEL: 'BUTTON_LABEL'
10267 * @name ui.grid.exporter.service:uiGridExporterService
10269 * @description Services for exporter feature
10271 module.service('uiGridExporterService', ['$log', '$q', 'uiGridExporterConstants', 'gridUtil', '$compile',
10272 function ($log, $q, uiGridExporterConstants, gridUtil, $compile) {
10276 initializeGrid: function (grid) {
10278 //add feature namespace and any properties to grid for needed state
10279 grid.exporter = {};
10280 this.defaultGridOptions(grid.options);
10284 * @name ui.grid.exporter.api:PublicApi
10286 * @description Public Api for exporter feature
10298 * @methodOf ui.grid.exporter.api:PublicApi
10299 * @description Exports rows from the grid in csv format,
10300 * the data exported is selected based on the provided options
10301 * @param {string} rowTypes which rows to export, valid values are
10302 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10303 * uiGridExporterConstants.SELECTED
10304 * @param {string} colTypes which columns to export, valid values are
10305 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
10306 * @param {element} $elm (Optional) A UI element into which the
10307 * resulting download link will be placed.
10309 csvExport: function (rowTypes, colTypes, $elm) {
10310 service.csvExport(grid, rowTypes, colTypes, $elm);
10315 * @methodOf ui.grid.exporter.api:PublicApi
10316 * @description Exports rows from the grid in pdf format,
10317 * the data exported is selected based on the provided options
10318 * Note that this function has a dependency on pdfMake, all
10319 * going well this has been installed for you.
10320 * The resulting pdf opens in a new browser window.
10321 * @param {string} rowTypes which rows to export, valid values are
10322 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10323 * uiGridExporterConstants.SELECTED
10324 * @param {string} colTypes which columns to export, valid values are
10325 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE
10327 pdfExport: function (rowTypes, colTypes) {
10328 service.pdfExport(grid, rowTypes, colTypes);
10334 grid.api.registerEventsFromObject(publicApi.events);
10336 grid.api.registerMethodsFromObject(publicApi.methods);
10340 defaultGridOptions: function (gridOptions) {
10341 //default option to true unless it was explicitly set to false
10344 * @name ui.grid.exporter.api:GridOptions
10346 * @description GridOptions for selection feature, these are available to be
10347 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
10352 * @name exporterSuppressButton
10353 * @propertyOf ui.grid.exporter.api:GridOptions
10354 * @description Don't show the export menu button, implying the user
10355 * will roll their own UI for calling the exporter
10356 * <br/>Defaults to false
10358 gridOptions.exporterSuppressButton = gridOptions.exporterSuppressButton === true;
10361 * @name exporterLinkTemplate
10362 * @propertyOf ui.grid.exporter.api:GridOptions
10363 * @description A custom template to use for the resulting
10364 * link (for csv export)
10365 * <br/>Defaults to ui-grid/csvLink
10367 gridOptions.exporterLinkTemplate = gridOptions.exporterLinkTemplate ? gridOptions.exporterLinkTemplate : 'ui-grid/csvLink';
10370 * @name exporterHeaderTemplate
10371 * @propertyOf ui.grid.exporter.api:GridOptions
10372 * @description A custom template to use for the header
10373 * section, containing the button and csv download link. Not
10374 * needed if you've set suppressButton and are providing a custom
10375 * $elm into which the download link will go.
10376 * <br/>Defaults to ui-grid/exporterHeader
10378 gridOptions.exporterHeaderTemplate = gridOptions.exporterHeaderTemplate ? gridOptions.exporterHeaderTemplate : 'ui-grid/exporterHeader';
10381 * @name exporterLinkLabel
10382 * @propertyOf ui.grid.exporter.api:GridOptions
10383 * @description The text to show on the CSV download
10385 * <br/>Defaults to 'Download CSV'
10387 gridOptions.exporterLinkLabel = gridOptions.exporterLinkLabel ? gridOptions.exporterLinkLabel : 'Download CSV';
10390 * @name exporterButtonLabel
10391 * @propertyOf ui.grid.exporter.api:GridOptions
10392 * @description The text to show on the exporter menu button
10394 * <br/>Defaults to 'Export'
10396 gridOptions.exporterButtonLabel = gridOptions.exporterButtonLabel ? gridOptions.exporterButtonLabel : 'Export';
10399 * @name exporterPdfDefaultStyle
10400 * @propertyOf ui.grid.exporter.api:GridOptions
10401 * @description The default style in pdfMake format
10402 * <br/>Defaults to:
10409 gridOptions.exporterPdfDefaultStyle = gridOptions.exporterPdfDefaultStyle ? gridOptions.exporterPdfDefaultStyle : { fontSize: 11 };
10412 * @name exporterPdfTableStyle
10413 * @propertyOf ui.grid.exporter.api:GridOptions
10414 * @description The table style in pdfMake format
10415 * <br/>Defaults to:
10418 * margin: [0, 5, 0, 15]
10422 gridOptions.exporterPdfTableStyle = gridOptions.exporterPdfTableStyle ? gridOptions.exporterPdfTableStyle : { margin: [0, 5, 0, 15] };
10425 * @name exporterPdfTableHeaderStyle
10426 * @propertyOf ui.grid.exporter.api:GridOptions
10427 * @description The tableHeader style in pdfMake format
10428 * <br/>Defaults to:
10437 gridOptions.exporterPdfTableHeaderStyle = gridOptions.exporterPdfTableHeaderStyle ? gridOptions.exporterPdfTableHeaderStyle : { bold: true, fontSize: 12, color: 'black' };
10440 * @name exporterPdfOrientation
10441 * @propertyOf ui.grid.exporter.api:GridOptions
10442 * @description The orientation, should be a valid pdfMake value,
10443 * 'landscape' or 'portrait'
10444 * <br/>Defaults to landscape
10446 gridOptions.exporterPdfOrientation = gridOptions.exporterPdfOrientation ? gridOptions.exporterPdfOrientation : 'landscape';
10449 * @name exporterPdfPageSize
10450 * @propertyOf ui.grid.exporter.api:GridOptions
10451 * @description The orientation, should be a valid pdfMake
10452 * paper size, usually 'A4' or 'LETTER'
10453 * {@link https://github.com/bpampuch/pdfmake/blob/master/src/standardPageSizes.js pdfMake page sizes}
10454 * <br/>Defaults to A4
10456 gridOptions.exporterPdfPageSize = gridOptions.exporterPdfPageSize ? gridOptions.exporterPdfPageSize : 'A4';
10459 * @name exporterPdfMaxGridWidth
10460 * @propertyOf ui.grid.exporter.api:GridOptions
10461 * @description The maxium grid width - the current grid width
10462 * will be scaled to match this, with any fixed width columns
10463 * being adjusted accordingly.
10464 * <br/>Defaults to 720 (for A4 landscape), use 670 for LETTER
10466 gridOptions.exporterPdfMaxGridWidth = gridOptions.exporterPdfMaxGridWidth ? gridOptions.exporterPdfMaxGridWidth : 720;
10469 * @name exporterPdfTableLayout
10470 * @propertyOf ui.grid.exporter.api:GridOptions
10471 * @description A tableLayout in pdfMake format,
10472 * controls gridlines and the like. We use the default
10474 * <br/>Defaults to null, which means no layout
10483 * @methodOf ui.grid.exporter.service:uiGridExporterService
10484 * @description Shows the grid menu with exporter content,
10485 * allowing the user to select export options
10486 * @param {Grid} grid the grid from which data should be exported
10488 showMenu: function ( grid ) {
10489 grid.exporter.$scope.menuItems = [
10491 title: 'Export all data as csv',
10492 action: function ($event) {
10493 this.grid.api.exporter.csvExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
10497 title: 'Export visible data as csv',
10498 action: function ($event) {
10499 this.grid.api.exporter.csvExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
10503 title: 'Export selected data as csv',
10504 action: function ($event) {
10505 this.grid.api.exporter.csvExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
10509 title: 'Export all data as pdf',
10510 action: function ($event) {
10511 this.grid.api.exporter.pdfExport( uiGridExporterConstants.ALL, uiGridExporterConstants.ALL );
10515 title: 'Export visible data as pdf',
10516 action: function ($event) {
10517 this.grid.api.exporter.pdfExport( uiGridExporterConstants.VISIBLE, uiGridExporterConstants.VISIBLE );
10521 title: 'Export selected data as pdf',
10522 action: function ($event) {
10523 this.grid.api.exporter.pdfExport( uiGridExporterConstants.SELECTED, uiGridExporterConstants.VISIBLE );
10528 grid.exporter.$scope.$broadcast('toggleExporterMenu');
10535 * @methodOf ui.grid.exporter.service:uiGridExporterService
10536 * @description Exports rows from the grid in csv format,
10537 * the data exported is selected based on the provided options
10538 * @param {Grid} grid the grid from which data should be exported
10539 * @param {string} rowTypes which rows to export, valid values are
10540 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10541 * uiGridExporterConstants.SELECTED
10542 * @param {string} colTypes which columns to export, valid values are
10543 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10544 * uiGridExporterConstants.SELECTED
10545 * @param {element} $elm (Optional) A UI element into which the
10546 * resulting download link will be placed.
10548 csvExport: function (grid, rowTypes, colTypes, $elm) {
10549 var exportColumnHeaders = this.getColumnHeaders(grid, colTypes);
10550 var exportData = this.getData(grid, rowTypes, colTypes);
10551 var csvContent = this.formatAsCsv(exportColumnHeaders, exportData);
10552 this.renderCsvLink(grid, csvContent, $elm);
10554 // this.grid.exporter.$scope.$broadcast('clearExporterMenu');
10560 * @name getColumnHeaders
10561 * @methodOf ui.grid.exporter.service:uiGridExporterService
10562 * @description Gets the column headers from the grid to use
10563 * as a title row for the exported file, all headers have
10564 * headerCellFilters applied as appropriate.
10568 * Column headers are an array of objects, each object has
10569 * name, displayName, width and align attributes. Only name is
10570 * used for csv, all attributes are used for pdf.
10572 * @param {Grid} grid the grid from which data should be exported
10573 * @param {string} colTypes which columns to export, valid values are
10574 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10575 * uiGridExporterConstants.SELECTED
10577 getColumnHeaders: function (grid, colTypes) {
10579 angular.forEach(grid.columns, function( gridCol, index ) {
10580 if (gridCol.visible || colTypes === uiGridExporterConstants.ALL){
10582 name: gridCol.field,
10583 displayName: gridCol.displayName,
10584 // TODO: should we do something to normalise here if too wide?
10585 width: gridCol.drawnWidth ? gridCol.drawnWidth : gridCol.width,
10586 // TODO: if/when we have an alignment attribute, use it here
10587 align: gridCol.colDef.type === 'number' ? 'right' : 'left'
10599 * @methodOf ui.grid.exporter.service:uiGridExporterService
10600 * @description Gets data from the grid based on the provided options,
10601 * all cells have cellFilters applied as appropriate
10602 * @param {Grid} grid the grid from which data should be exported
10603 * @param {string} rowTypes which rows to export, valid values are
10604 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10605 * uiGridExporterConstants.SELECTED
10606 * @param {string} colTypes which columns to export, valid values are
10607 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10608 * uiGridExporterConstants.SELECTED
10610 getData: function (grid, rowTypes, colTypes) {
10615 switch ( rowTypes ) {
10616 case uiGridExporterConstants.ALL:
10619 case uiGridExporterConstants.VISIBLE:
10620 rows = grid.getVisibleRows();
10622 case uiGridExporterConstants.SELECTED:
10623 if ( grid.api.selection ){
10624 rows = grid.api.selection.getSelectedGridRows();
10626 $log.error('selection feature must be enabled to allow selected rows to be exported');
10631 if ( uiGridExporterConstants.ALL ) {
10632 angular.forEach(rows, function( row, index ) {
10634 var extractedRow = [];
10635 angular.forEach(grid.columns, function( gridCol, index ) {
10636 if (gridCol.visible || colTypes === uiGridExporterConstants.ALL){
10637 extractedRow.push(grid.getCellValue(row, gridCol));
10641 data.push(extractedRow);
10651 * @name formatAsCSV
10652 * @methodOf ui.grid.exporter.service:uiGridExporterService
10653 * @description Formats the column headers and data as a CSV,
10654 * and sends that data to the user
10655 * @param {array} exportColumnHeaders an array of column headers,
10656 * where each header is an object with name, width and maybe alignment
10657 * @param {array} exportData an array of rows, where each row is
10658 * an array of column data
10659 * @returns {string} csv the formatted csv as a string
10661 formatAsCsv: function (exportColumnHeaders, exportData) {
10664 var bareHeaders = exportColumnHeaders.map(function(header){return header.displayName;});
10666 var csv = self.formatRowAsCsv(this)(bareHeaders) + '\n';
10668 csv += exportData.map(this.formatRowAsCsv(this)).join('\n');
10675 * @name formatRowAsCsv
10676 * @methodOf ui.grid.exporter.service:uiGridExporterService
10677 * @description Renders a single field as a csv field, including
10678 * quotes around the value
10679 * @param {exporterService} exporter pass in exporter
10680 * @param {array} row the row to be turned into a csv string
10681 * @returns {string} a csv-ified version of the row
10683 formatRowAsCsv: function ( exporter ) {
10684 return function( row ) {
10685 return row.map(exporter.formatFieldAsCsv).join(',');
10691 * @name formatFieldAsCsv
10692 * @methodOf ui.grid.exporter.service:uiGridExporterService
10693 * @description Renders a single field as a csv field, including
10694 * quotes around the value
10695 * @param {field} field the field to be turned into a csv string,
10696 * may be of any type
10697 * @returns {string} a csv-ified version of the field
10699 formatFieldAsCsv: function (field) {
10700 if (field == null) { // we want to catch anything null-ish, hence just == not ===
10703 if (typeof(field) === 'number') {
10706 if (typeof(field) === 'boolean') {
10707 return (field ? 'TRUE' : 'FALSE') ;
10709 if (typeof(field) === 'string') {
10710 return '"' + field.replace(/"/g,'""') + '"';
10713 return JSON.stringify(field);
10718 * @name renderCsvLink
10719 * @methodOf ui.grid.exporter.service:uiGridExporterService
10720 * @description Creates a download link with the csv content,
10721 * putting it into the default exporter element, or into the element
10722 * passed in if provided
10723 * @param {Grid} grid the grid from which data should be exported
10724 * @param {string} csvContent the csv content that we'd like to
10725 * make available as a download link
10726 * @param {element} $elm (Optional) A UI element into which the
10727 * resulting download link will be placed. If not provided, the
10728 * link is put into the default exporter element.
10730 renderCsvLink: function (grid, csvContent, $elm) {
10731 var targetElm = $elm ? $elm : angular.element( grid.exporter.gridElm[0].querySelectorAll('.ui-grid-exporter-csv-link') );
10732 if ( angular.element( targetElm[0].querySelectorAll('.ui-grid-exporter-csv-link-span')) ) {
10733 angular.element( targetElm[0].querySelectorAll('.ui-grid-exporter-csv-link-span')).remove();
10736 var linkTemplate = gridUtil.getTemplate(grid.options.exporterLinkTemplate)
10737 .then(function (contents) {
10738 contents = contents.replace(uiGridExporterConstants.LINK_LABEL, grid.options.exporterLinkLabel);
10739 contents = contents.replace(uiGridExporterConstants.CSV_CONTENT, encodeURIComponent(csvContent));
10741 var template = angular.element(contents);
10743 var newElm = $compile(template)(grid.exporter.$scope);
10744 targetElm.append(newElm);
10752 * @methodOf ui.grid.exporter.service:uiGridExporterService
10753 * @description Exports rows from the grid in pdf format,
10754 * the data exported is selected based on the provided options.
10755 * Note that this function has a dependency on jsPDF, which must
10756 * be either included as a script on your page, or downloaded and
10757 * served as part of your site. The resulting pdf opens in a new
10759 * @param {Grid} grid the grid from which data should be exported
10760 * @param {string} rowTypes which rows to export, valid values are
10761 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10762 * uiGridExporterConstants.SELECTED
10763 * @param {string} colTypes which columns to export, valid values are
10764 * uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE,
10765 * uiGridExporterConstants.SELECTED
10767 pdfExport: function (grid, rowTypes, colTypes) {
10768 var exportColumnHeaders = this.getColumnHeaders(grid, colTypes);
10769 var exportData = this.getData(grid, rowTypes, colTypes);
10770 var docDefinition = this.prepareAsPdf(grid, exportColumnHeaders, exportData);
10772 pdfMake.createPdf(docDefinition).open();
10778 * @name renderAsPdf
10779 * @methodOf ui.grid.exporter.service:uiGridExporterService
10780 * @description Renders the data into a pdf, and opens that pdf.
10782 * @param {Grid} grid the grid from which data should be exported
10783 * @param {array} exportColumnHeaders an array of column headers,
10784 * where each header is an object with name, width and maybe alignment
10785 * @param {array} exportData an array of rows, where each row is
10786 * an array of column data
10787 * @returns {object} a pdfMake format document definition, ready
10790 prepareAsPdf: function(grid, exportColumnHeaders, exportData) {
10791 var headerWidths = this.calculatePdfHeaderWidths( grid, exportColumnHeaders );
10793 var headerColumns = exportColumnHeaders.map( function( header ) {
10794 return { text: header.displayName, style: 'tableHeader' };
10797 var stringData = exportData.map(this.formatRowAsPdf(this));
10799 var allData = [headerColumns].concat(stringData);
10801 var docDefinition = {
10802 pageOrientation: grid.options.exporterPdfOrientation,
10804 style: 'tableStyle',
10807 widths: headerWidths,
10812 tableStyle: grid.options.exporterPdfTableStyle,
10813 tableHeader: grid.options.exporterPdfTableHeaderStyle,
10815 defaultStyle: grid.options.exporterPdfDefaultStyle
10818 if ( grid.options.exporterPdfLayout ){
10819 docDefinition.layout = grid.options.exporterPdfLayout;
10822 return docDefinition;
10829 * @name calculatePdfHeaderWidths
10830 * @methodOf ui.grid.exporter.service:uiGridExporterService
10831 * @description Determines the column widths base on the
10832 * widths we got from the grid. If the column is drawn
10833 * then we have a drawnWidth. If the column is not visible
10834 * then we have '*', 'x%' or a width. When columns are
10835 * not visible they don't contribute to the overall gridWidth,
10836 * so we need to adjust to allow for extra columns
10838 * Our basic heuristic is to take the current gridWidth, plus
10839 * numeric columns and call this the base gridwidth.
10841 * To that we add 100 for any '*' column, and x% of the base gridWidth
10842 * for any column that is a %
10844 * @param {Grid} grid the grid from which data should be exported
10845 * @param {object} exportHeaders array of header information
10846 * @returns {object} an array of header widths
10848 calculatePdfHeaderWidths: function ( grid, exportHeaders ) {
10849 var baseGridWidth = 0;
10850 angular.forEach(exportHeaders, function(value){
10851 if (typeof(value.width) === 'number'){
10852 baseGridWidth += value.width;
10856 var extraColumns = 0;
10857 angular.forEach(exportHeaders, function(value){
10858 if (value.width === '*'){
10859 extraColumns += 100;
10861 if (typeof(value.width) === 'string' && value.width.match(/(\d)*%/)) {
10862 var percent = parseInt(value.width.match(/(\d)*%/)[0]);
10864 value.width = baseGridWidth * percent / 100;
10865 extraColumns += value.width;
10869 var gridWidth = baseGridWidth + extraColumns;
10871 return exportHeaders.map(function( header ) {
10872 return header.width === '*' ? header.width : header.width * grid.options.exporterPdfMaxGridWidth / gridWidth;
10879 * @name formatRowAsPdf
10880 * @methodOf ui.grid.exporter.service:uiGridExporterService
10881 * @description Renders a row in a format consumable by PDF,
10882 * mainly meaning casting everything to a string
10883 * @param {exporterService} exporter pass in exporter
10884 * @param {array} row the row to be turned into a csv string
10885 * @returns {string} a csv-ified version of the row
10887 formatRowAsPdf: function ( exporter ) {
10888 return function( row ) {
10889 return row.map(exporter.formatFieldAsPdfString);
10896 * @name formatFieldAsCsv
10897 * @methodOf ui.grid.exporter.service:uiGridExporterService
10898 * @description Renders a single field as a pdf-able field, which
10899 * is different from a csv field only in that strings don't have quotes
10901 * @param {field} field the field to be turned into a pdf string,
10902 * may be of any type
10903 * @returns {string} a string-ified version of the field
10905 formatFieldAsPdfString: function (field) {
10906 if (field == null) { // we want to catch anything null-ish, hence just == not ===
10909 if (typeof(field) === 'number') {
10910 return field.toString();
10912 if (typeof(field) === 'boolean') {
10913 return (field ? 'TRUE' : 'FALSE') ;
10915 if (typeof(field) === 'string') {
10916 return field.replace(/"/g,'""');
10919 return JSON.stringify(field).replace(/^"/,'').replace(/"$/,'');
10930 * @name ui.grid.exporter.directive:uiGridExporter
10934 * @description Adds exporter features to grid
10937 <example module="app">
10938 <file name="app.js">
10939 var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
10941 app.controller('MainCtrl', ['$scope', function ($scope) {
10943 { name: 'Bob', title: 'CEO' },
10944 { name: 'Frank', title: 'Lowly Developer' }
10947 $scope.gridOptions = {
10949 {name: 'name', enableCellEdit: true},
10950 {name: 'title', enableCellEdit: true}
10956 <file name="index.html">
10957 <div ng-controller="MainCtrl">
10958 <div ui-grid="gridOptions" ui-grid-exporter></div>
10963 module.directive('uiGridExporter', ['$log', 'uiGridExporterConstants', 'uiGridExporterService', 'gridUtil', '$compile',
10964 function ($log, uiGridExporterConstants, uiGridExporterService, gridUtil, $compile) {
10968 require: '^uiGrid',
10970 compile: function () {
10972 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
10973 uiGridExporterService.initializeGrid(uiGridCtrl.grid);
10974 uiGridCtrl.grid.exporter.$scope = $scope;
10976 post: function ($scope, $elm, $attrs, uiGridCtrl) {
10988 * @name ui.grid.infiniteScroll
10992 * #ui.grid.infiniteScroll
10993 * This module provides infinite scroll functionality to ui-grid
10996 var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
10999 * @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
11001 * @description Service for infinite scroll features
11003 module.service('uiGridInfiniteScrollService', ['gridUtil', '$log', '$compile', '$timeout', function (gridUtil, $log, $compile, $timeout) {
11009 * @name initializeGrid
11010 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
11011 * @description This method register events and methods into grid public API
11014 initializeGrid: function(grid) {
11017 * @name ui.grid.infiniteScroll.api:PublicAPI
11019 * @description Public API for infinite scroll feature
11027 * @name needLoadMoreData
11028 * @eventOf ui.grid.infiniteScroll.api:PublicAPI
11029 * @description This event fires when scroll reached bottom percentage of grid
11030 * and needs to load data
11033 needLoadMoreData: function ($scope, fn) {
11043 * @methodOf ui.grid.infiniteScroll.api:PublicAPI
11044 * @description This function is used as a promise when data finished loading.
11045 * See infinite_scroll ngdoc for example of usage
11048 dataLoaded: function() {
11049 grid.options.loadTimout = false;
11054 grid.options.loadTimout = false;
11055 grid.api.registerEventsFromObject(publicApi.events);
11056 grid.api.registerMethodsFromObject(publicApi.methods);
11062 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
11063 * @description This function fires 'needLoadMoreData' event
11066 loadData: function (grid) {
11067 grid.api.infiniteScroll.raise.needLoadMoreData();
11068 grid.options.loadTimout = true;
11073 * @name checkScroll
11074 * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
11075 * @description This function checks scroll position inside grid and
11076 * calls 'loadData' function when scroll reaches 'infiniteScrollPercentage'
11079 checkScroll: function(grid, scrollTop) {
11081 /* Take infiniteScrollPercentage value or use 20% as default */
11082 var infiniteScrollPercentage = grid.options.infiniteScrollPercentage ? grid.options.infiniteScrollPercentage : 20;
11084 if (!grid.options.loadTimout && scrollTop <= infiniteScrollPercentage) {
11085 this.loadData(grid);
11092 * @name infiniteScrollPercentage
11093 * @propertyOf ui.grid.class:GridOptions
11094 * @description This setting controls at what percentage of the scroll more data
11095 * is requested by the infinite scroll
11102 * @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
11106 * @description Adds infinite scroll features to grid
11109 <example module="app">
11110 <file name="app.js">
11111 var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
11113 app.controller('MainCtrl', ['$scope', function ($scope) {
11115 { name: 'Alex', car: 'Toyota' },
11116 { name: 'Sam', car: 'Lexus' }
11119 $scope.columnDefs = [
11125 <file name="index.html">
11126 <div ng-controller="MainCtrl">
11127 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
11133 module.directive('uiGridInfiniteScroll', ['$log', 'uiGridInfiniteScrollService',
11134 function ($log, uiGridInfiniteScrollService) {
11138 require: '^uiGrid',
11139 compile: function($scope, $elm, $attr){
11141 pre: function($scope, $elm, $attr, uiGridCtrl) {
11142 uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid);
11144 post: function($scope, $elm, $attr) {
11151 module.directive('uiGridViewport',
11152 ['$compile', '$log', 'uiGridInfiniteScrollService', 'uiGridConstants',
11153 function ($compile, $log, uiGridInfiniteScrollService, uiGridConstants) {
11157 link: function ($scope, $elm, $attr){
11158 $scope.$on(uiGridConstants.events.GRID_SCROLL, function(evt, args) {
11160 var percentage = 100 - (args.y.percentage * 100);
11161 uiGridInfiniteScrollService.checkScroll($scope.grid, percentage);
11172 * @name ui.grid.pinning
11175 * # ui.grid.pinning
11176 * This module provides column pinning to the end user via menu options in the column header
11180 * <div doc-module-components="ui.grid.pinning"></div>
11183 var module = angular.module('ui.grid.pinning', ['ui.grid']);
11185 module.config(['$provide', function ($provide) {
11186 $provide.decorator('i18nService', ['$delegate', function ($delegate) {
11187 $delegate.add('en',
11189 pinLeft: 'Pin Left',
11190 pinRight: 'Pin Right',
11200 module.service('uiGridPinningService', ['$log', 'GridRenderContainer', 'i18nService', function ($log, GridRenderContainer, i18nService) {
11203 initializeGrid: function (grid) {
11204 service.defaultGridOptions(grid.options);
11206 // Register a column builder to add new menu items for pinning left and right
11207 grid.registerColumnBuilder(service.pinningColumnBuilder);
11210 defaultGridOptions: function (gridOptions) {
11211 //default option to true unless it was explicitly set to false
11214 * @name ui.grid.pinning.api:GridOptions
11216 * @description GridOptions for pinning feature, these are available to be
11217 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
11222 * @name enableRowSelection
11223 * @propertyOf ui.grid.pinning.api:GridOptions
11224 * @description Enable pinning for the entire grid.
11225 * <br/>Defaults to true
11227 gridOptions.enablePinning = gridOptions.enablePinning !== false;
11231 pinningColumnBuilder: function (colDef, col, gridOptions) {
11232 //default to true unless gridOptions or colDef is explicitly false
11236 * @name ui.grid.pinning.api:ColumnDef
11238 * @description ColumnDef for pinning feature, these are available to be
11239 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
11244 * @name enablePinning
11245 * @propertyOf ui.grid.pinning.api:ColumnDef
11246 * @description Enable pinning for the individual column.
11247 * <br/>Defaults to true
11249 colDef.enablePinning = colDef.enablePinning === undefined ? gridOptions.enablePinning : colDef.enablePinning;
11255 * @propertyOf ui.grid.pinning.api:ColumnDef
11256 * @description Column is pinned left when grid is rendered
11257 * <br/>Defaults to false
11262 * @name pinnedRight
11263 * @propertyOf ui.grid.pinning.api:ColumnDef
11264 * @description Column is pinned right when grid is rendered
11265 * <br/>Defaults to false
11267 if (colDef.pinnedLeft) {
11268 col.renderContainer = 'left';
11269 col.grid.createLeftContainer();
11271 else if (colDef.pinnedRight) {
11272 col.renderContainer = 'right';
11273 col.grid.createRightContainer();
11276 if (!colDef.enablePinning) {
11280 var pinColumnLeftAction = {
11281 title: i18nService.get().pinning.pinLeft,
11282 icon: 'ui-grid-icon-left-open',
11283 shown: function () {
11284 return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left';
11286 action: function () {
11287 this.context.col.renderContainer = 'left';
11288 this.context.col.grid.createLeftContainer();
11290 // Need to call refresh twice; once to move our column over to the new render container and then
11291 // a second time to update the grid viewport dimensions with our adjustments
11293 .then(function () {
11294 col.grid.refresh();
11299 var pinColumnRightAction = {
11300 title: i18nService.get().pinning.pinRight,
11301 icon: 'ui-grid-icon-right-open',
11302 shown: function () {
11303 return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right';
11305 action: function () {
11306 this.context.col.renderContainer = 'right';
11307 this.context.col.grid.createRightContainer();
11310 // Need to call refresh twice; once to move our column over to the new render container and then
11311 // a second time to update the grid viewport dimensions with our adjustments
11313 .then(function () {
11314 col.grid.refresh();
11319 var removePinAction = {
11320 title: i18nService.get().pinning.unpin,
11321 icon: 'ui-grid-icon-cancel',
11322 shown: function () {
11323 return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body';
11325 action: function () {
11326 this.context.col.renderContainer = null;
11328 // Need to call refresh twice; once to move our column over to the new render container and then
11329 // a second time to update the grid viewport dimensions with our adjustments
11331 .then(function () {
11332 col.grid.refresh();
11337 col.menuItems.push(pinColumnLeftAction);
11338 col.menuItems.push(pinColumnRightAction);
11339 col.menuItems.push(removePinAction);
11346 module.directive('uiGridPinning', ['$log', 'uiGridPinningService',
11347 function ($log, uiGridPinningService) {
11351 compile: function () {
11353 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11354 uiGridPinningService.initializeGrid(uiGridCtrl.grid);
11356 post: function ($scope, $elm, $attrs, uiGridCtrl) {
11368 var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
11370 module.constant('columnBounds', {
11375 module.service('uiGridResizeColumnsService', ['$log','$q',
11376 function ($log,$q) {
11379 defaultGridOptions: function(gridOptions){
11380 //default option to true unless it was explicitly set to false
11383 * @name ui.grid.resizeColumns.api:GridOptions
11385 * @description GridOptions for resizeColumns feature, these are available to be
11386 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
11391 * @name enableColumnResizing
11392 * @propertyOf ui.grid.resizeColumns.api:GridOptions
11393 * @description Enable column resizing on the entire grid
11394 * <br/>Defaults to true
11396 gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
11399 //use old name if it is explicitly false
11400 if (gridOptions.enableColumnResize === false){
11401 gridOptions.enableColumnResizing = false;
11405 colResizerColumnBuilder: function (colDef, col, gridOptions) {
11410 * @name ui.grid.resizeColumns.api:ColumnDef
11412 * @description ColumnDef for resizeColumns feature, these are available to be
11413 * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
11418 * @name enableColumnResizing
11419 * @propertyOf ui.grid.resizeColumns.api:ColumnDef
11420 * @description Enable column resizing on an individual column
11421 * <br/>Defaults to GridOptions.enableColumnResizing
11423 //default to true unless gridOptions or colDef is explicitly false
11424 colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
11427 //legacy support of old option name
11428 if (colDef.enableColumnResize === false){
11429 colDef.enableColumnResizing = false;
11432 return $q.all(promises);
11443 * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
11447 * 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
11448 * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
11451 <doc:example module="app">
11454 var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
11456 app.controller('MainCtrl', ['$scope', function ($scope) {
11457 $scope.gridOpts = {
11459 { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
11460 { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
11461 { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
11462 { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
11468 <div ng-controller="MainCtrl">
11469 <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
11477 module.directive('uiGridResizeColumns', ['$log', 'uiGridResizeColumnsService', function ($log, uiGridResizeColumnsService) {
11481 require: '^uiGrid',
11483 compile: function () {
11485 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
11487 uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
11488 uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
11491 post: function ($scope, $elm, $attrs, uiGridCtrl) {
11498 // Extend the uiGridHeaderCell directive
11499 module.directive('uiGridHeaderCell', ['$log', '$templateCache', '$compile', '$q', function ($log, $templateCache, $compile, $q) {
11501 // Run after the original uiGridHeaderCell
11503 require: '^uiGrid',
11505 compile: function() {
11507 post: function ($scope, $elm, $attrs, uiGridCtrl) {
11508 if (uiGridCtrl.grid.options.enableColumnResizing) {
11509 var renderIndexDefer = $q.defer();
11511 $attrs.$observe('renderIndex', function (n, o) {
11512 $scope.renderIndex = $scope.$eval(n);
11514 renderIndexDefer.resolve();
11517 renderIndexDefer.promise.then(function() {
11518 var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
11520 var resizerLeft = angular.element(columnResizerElm).clone();
11521 var resizerRight = angular.element(columnResizerElm).clone();
11523 resizerLeft.attr('position', 'left');
11524 resizerRight.attr('position', 'right');
11526 var col = $scope.col;
11527 var renderContainer = col.getRenderContainer();
11530 // Get the column to the left of this one
11531 var otherCol = renderContainer.renderedColumns[$scope.renderIndex - 1];
11533 // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
11534 if (otherCol && $scope.col.index !== 0 && otherCol.colDef.enableColumnResizing !== false) {
11535 $elm.prepend(resizerLeft);
11538 // Don't append the right resizer if this column has resizing disabled
11539 //if ($scope.col.index !== $scope.grid.renderedColumns.length - 1 && $scope.col.colDef.enableColumnResizing !== false) {
11540 if ($scope.col.colDef.enableColumnResizing !== false) {
11541 $elm.append(resizerRight);
11544 $compile(resizerLeft)($scope);
11545 $compile(resizerRight)($scope);
11558 * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
11563 * Draggable handle that controls column resizing.
11566 <doc:example module="app">
11569 var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
11571 app.controller('MainCtrl', ['$scope', function ($scope) {
11572 $scope.gridOpts = {
11573 enableColumnResizing: true,
11575 { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
11576 { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
11577 { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
11578 { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
11584 <div ng-controller="MainCtrl">
11585 <div class="testGrid" ui-grid="gridOpts"></div>
11589 // TODO: e2e specs?
11590 // TODO: Obey minWidth and maxWIdth;
11592 // TODO: post-resize a horizontal scroll event should be fired
11596 module.directive('uiGridColumnResizer', ['$log', '$document', 'gridUtil', 'uiGridConstants', 'columnBounds', function ($log, $document, gridUtil, uiGridConstants, columnBounds) {
11597 var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
11606 require: '?^uiGrid',
11607 link: function ($scope, $elm, $attrs, uiGridCtrl) {
11612 if ($scope.position === 'left') {
11613 $elm.addClass('left');
11615 else if ($scope.position === 'right') {
11616 $elm.addClass('right');
11619 // Resize all the other columns around col
11620 function resizeAroundColumn(col) {
11621 // Get this column's render container
11622 var renderContainer = col.getRenderContainer();
11624 renderContainer.visibleColumnCache.forEach(function (column) {
11625 // Skip the column we just resized
11626 if (column.index === col.index) { return; }
11628 var colDef = column.colDef;
11629 if (!colDef.width || (angular.isString(colDef.width) && (colDef.width.indexOf('*') !== -1 || colDef.width.indexOf('%') !== -1))) {
11630 colDef.width = column.drawnWidth;
11635 // Build the columns then refresh the grid canvas
11636 // takes an argument representing the diff along the X-axis that the resize had
11637 function buildColumnsAndRefresh(xDiff) {
11638 // Build the columns
11639 uiGridCtrl.grid.buildColumns()
11641 // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
11642 uiGridCtrl.grid.refreshCanvas(true);
11646 function mousemove(event, args) {
11647 if (event.originalEvent) { event = event.originalEvent; }
11648 event.preventDefault();
11650 x = event.clientX - gridLeft;
11652 if (x < 0) { x = 0; }
11653 else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
11655 // The other column to resize (the one next to this one)
11656 var col = $scope.col;
11657 var renderContainer = col.getRenderContainer();
11659 if ($scope.position === 'left') {
11660 // Get the column to the left of this one
11661 col = renderContainer.renderedColumns[$scope.renderIndex - 1];
11662 otherCol = $scope.col;
11664 else if ($scope.position === 'right') {
11665 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
11668 // Don't resize if it's disabled on this column
11669 if (col.colDef.enableColumnResizing === false) {
11673 if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
11674 uiGridCtrl.grid.element.addClass('column-resizing');
11677 // Get the diff along the X axis
11678 var xDiff = x - startX;
11680 // Get the width that this mouse would give the column
11681 var newWidth = col.drawnWidth + xDiff;
11683 // If the new width would be less than the column's allowably minimum width, don't allow it
11684 if (col.colDef.minWidth && newWidth < col.colDef.minWidth) {
11685 x = x + (col.colDef.minWidth - newWidth);
11687 else if (!col.colDef.minWidth && columnBounds.minWidth && newWidth < columnBounds.minWidth) {
11688 x = x + (col.colDef.minWidth - newWidth);
11690 else if (col.colDef.maxWidth && newWidth > col.colDef.maxWidth) {
11691 x = x + (col.colDef.maxWidth - newWidth);
11694 resizeOverlay.css({ left: x + 'px' });
11696 uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
11699 function mouseup(event, args) {
11700 if (event.originalEvent) { event = event.originalEvent; }
11701 event.preventDefault();
11703 uiGridCtrl.grid.element.removeClass('column-resizing');
11705 resizeOverlay.remove();
11707 // Resize the column
11708 x = event.clientX - gridLeft;
11709 var xDiff = x - startX;
11712 $document.off('mouseup', mouseup);
11713 $document.off('mousemove', mousemove);
11717 // The other column to resize (the one next to this one)
11718 var col = $scope.col;
11719 var renderContainer = col.getRenderContainer();
11722 if ($scope.position === 'left') {
11723 // Get the column to the left of this one
11724 col = renderContainer.renderedColumns[$scope.renderIndex - 1];
11725 otherCol = $scope.col;
11727 else if ($scope.position === 'right') {
11728 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
11731 // Don't resize if it's disabled on this column
11732 if (col.colDef.enableColumnResizing === false) {
11736 // Get the new width
11737 var newWidth = col.drawnWidth + xDiff;
11739 // If the new width is less than the minimum width, make it the minimum width
11740 if (col.colDef.minWidth && newWidth < col.colDef.minWidth) {
11741 newWidth = col.colDef.minWidth;
11743 else if (!col.colDef.minWidth && columnBounds.minWidth && newWidth < columnBounds.minWidth) {
11744 newWidth = columnBounds.minWidth;
11747 if (col.colDef.maxWidth && newWidth > col.colDef.maxWidth) {
11748 newWidth = col.colDef.maxWidth;
11751 col.colDef.width = newWidth;
11753 // All other columns because fixed to their drawn width, if they aren't already
11754 resizeAroundColumn(col);
11756 buildColumnsAndRefresh(xDiff);
11758 $document.off('mouseup', mouseup);
11759 $document.off('mousemove', mousemove);
11762 $elm.on('mousedown', function(event, args) {
11763 if (event.originalEvent) { event = event.originalEvent; }
11764 event.stopPropagation();
11766 // Get the left offset of the grid
11767 // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
11768 gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
11770 // Get the starting X position, which is the X coordinate of the click minus the grid's offset
11771 startX = event.clientX - gridLeft;
11773 // Append the resizer overlay
11774 uiGridCtrl.grid.element.append(resizeOverlay);
11776 // Place the resizer overlay at the start position
11777 resizeOverlay.css({ left: startX });
11779 // Add handlers for mouse move and up events
11780 $document.on('mouseup', mouseup);
11781 $document.on('mousemove', mousemove);
11784 // On doubleclick, resize to fit all rendered cells
11785 $elm.on('dblclick', function(event, args) {
11786 event.stopPropagation();
11788 var col = $scope.col;
11789 var renderContainer = col.getRenderContainer();
11791 var otherCol, multiplier;
11793 // 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
11794 if ($scope.position === 'left') {
11795 col = renderContainer.renderedColumns[$scope.renderIndex - 1];
11796 otherCol = $scope.col;
11799 else if ($scope.position === 'right') {
11800 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
11801 otherCol = renderContainer.renderedColumns[$scope.renderIndex + 1];
11805 // Go through the rendered rows and find out the max size for the data in this column
11809 // Get the parent render container element
11810 var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
11812 // 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
11813 var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.index + ' .ui-grid-cell-contents');
11814 Array.prototype.forEach.call(cells, function (cell) {
11815 // Get the cell width
11816 // $log.debug('width', gridUtil.elementWidth(cell));
11818 // Account for the menu button if it exists
11820 if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
11821 menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
11824 gridUtil.fakeElement(cell, {}, function(newElm) {
11825 // Make the element float since it's a div and can expand to fill its container
11826 var e = angular.element(newElm);
11827 e.attr('style', 'float: left');
11829 var width = gridUtil.elementWidth(e);
11832 var menuButtonWidth = gridUtil.elementWidth(menuButton);
11833 width = width + menuButtonWidth;
11836 if (width > maxWidth) {
11838 xDiff = maxWidth - width;
11843 // If the new width is less than the minimum width, make it the minimum width
11844 if (col.colDef.minWidth && maxWidth < col.colDef.minWidth) {
11845 maxWidth = col.colDef.minWidth;
11847 else if (!col.colDef.minWidth && columnBounds.minWidth && maxWidth < columnBounds.minWidth) {
11848 maxWidth = columnBounds.minWidth;
11851 if (col.colDef.maxWidth && maxWidth > col.colDef.maxWidth) {
11852 maxWidth = col.colDef.maxWidth;
11855 col.colDef.width = maxWidth;
11857 // All other columns because fixed to their drawn width, if they aren't already
11858 resizeAroundColumn(col);
11860 buildColumnsAndRefresh(xDiff);
11863 $elm.on('$destroy', function() {
11864 $elm.off('mousedown');
11865 $elm.off('dblclick');
11866 $document.off('mousemove', mousemove);
11867 $document.off('mouseup', mouseup);
11881 * @name ui.grid.rowEdit
11884 * # ui.grid.rowEdit
11885 * This module extends the edit feature to provide tracking and saving of rows
11886 * of data. The tutorial provides more information on how this feature is best
11887 * used {@link tutorial/205_row_editable here}.
11889 * This feature depends on usage of the ui-grid-edit feature, and also benefits
11890 * from use of ui-grid-cellNav to provide the full spreadsheet-like editing
11895 var module = angular.module('ui.grid.rowEdit', ['ui.grid', 'ui.grid.edit', 'ui.grid.cellNav']);
11899 * @name ui.grid.rowEdit.constant:uiGridRowEditConstants
11901 * @description constants available in row edit module
11903 module.constant('uiGridRowEditConstants', {
11908 * @name ui.grid.rowEdit.service:uiGridRowEditService
11910 * @description Services for row editing features
11912 module.service('uiGridRowEditService', ['$interval', '$log', '$q', 'uiGridConstants', 'uiGridRowEditConstants', 'gridUtil',
11913 function ($interval, $log, $q, uiGridConstants, uiGridRowEditConstants, gridUtil) {
11917 initializeGrid: function (scope, grid) {
11920 * @name ui.grid.rowEdit.api:PublicApi
11922 * @description Public Api for rowEdit feature
11929 * @eventOf ui.grid.rowEdit.api:PublicApi
11931 * @description raised when a row is ready for saving. Once your
11932 * row has saved you may need to use angular.extend to update the
11933 * data entity with any changed data from your save (for example,
11934 * lock version information if you're using optimistic locking,
11935 * or last update time/user information).
11937 * Your method should call setSavePromise somewhere in the body before
11938 * returning control. The feature will then wait, with the gridRow greyed out
11939 * whilst this promise is being resolved.
11942 * gridApi.rowEdit.on.saveRow(scope,function(rowEntity){})
11944 * and somewhere within the event handler:
11946 * gridApi.rowEdit.setSavePromise( grid, rowEntity, savePromise)
11948 * @param {object} rowEntity the options.data element that was edited
11949 * @returns {promise} Your saveRow method should return a promise, the
11950 * promise should either be resolved (implying successful save), or
11951 * rejected (implying an error).
11953 saveRow: function (rowEntity) {
11961 * @methodOf ui.grid.rowEdit.api:PublicApi
11962 * @name setSavePromise
11963 * @description Sets the promise associated with the row save, mandatory that
11964 * the saveRow event handler calls this method somewhere before returning.
11966 * gridApi.rowEdit.setSavePromise(grid, rowEntity)
11968 * @param {object} grid the grid for which dirty rows should be returned
11969 * @param {object} rowEntity a data row from the grid for which a save has
11971 * @param {promise} savePromise the promise that will be resolved when the
11972 * save is successful, or rejected if the save fails
11975 setSavePromise: function (grid, rowEntity, savePromise) {
11976 service.setSavePromise(grid, rowEntity, savePromise);
11980 * @methodOf ui.grid.rowEdit.api:PublicApi
11981 * @name getDirtyRows
11982 * @description Returns all currently dirty rows
11984 * gridApi.rowEdit.getDirtyRows(grid)
11986 * @param {object} grid the grid for which dirty rows should be returned
11987 * @returns {array} An array of gridRows that are currently dirty
11990 getDirtyRows: function (grid) {
11991 return grid.rowEditDirtyRows;
11995 * @methodOf ui.grid.rowEdit.api:PublicApi
11996 * @name getErrorRows
11997 * @description Returns all currently errored rows
11999 * gridApi.rowEdit.getErrorRows(grid)
12001 * @param {object} grid the grid for which errored rows should be returned
12002 * @returns {array} An array of gridRows that are currently in error
12005 getErrorRows: function (grid) {
12006 return grid.rowEditErrorRows;
12010 * @methodOf ui.grid.rowEdit.api:PublicApi
12011 * @name flushDirtyRows
12012 * @description Triggers a save event for all currently dirty rows, could
12013 * be used where user presses a save button or navigates away from the page
12015 * gridApi.rowEdit.flushDirtyRows(grid)
12017 * @param {object} grid the grid for which dirty rows should be flushed
12018 * @returns {promise} a promise that represents the aggregate of all
12019 * of the individual save promises - i.e. it will be resolved when all
12020 * the individual save promises have been resolved.
12023 flushDirtyRows: function (grid) {
12024 return service.flushDirtyRows(grid);
12030 grid.api.registerEventsFromObject(publicApi.events);
12031 grid.api.registerMethodsFromObject(publicApi.methods);
12033 grid.api.core.on.renderingComplete( scope, function ( gridApi ) {
12034 grid.api.edit.on.afterCellEdit( scope, service.endEditCell );
12035 grid.api.edit.on.beginCellEdit( scope, service.beginEditCell );
12036 grid.api.edit.on.cancelCellEdit( scope, service.cancelEditCell );
12038 if ( grid.api.cellNav ) {
12039 grid.api.cellNav.on.navigate( scope, service.navigate );
12045 defaultGridOptions: function (gridOptions) {
12049 * @name ui.grid.rowEdit.api:GridOptions
12051 * @description Options for configuring the rowEdit feature, these are available to be
12052 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
12060 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12062 * @description Returns a function that saves the specified row from the grid,
12063 * and returns a promise
12064 * @param {object} grid the grid for which dirty rows should be flushed
12065 * @param {GridRow} gridRow the row that should be saved
12066 * @returns {function} the saveRow function returns a function. That function
12067 * in turn, when called, returns a promise relating to the save callback
12069 saveRow: function ( grid, gridRow ) {
12072 return function() {
12073 gridRow.isSaving = true;
12075 var promise = grid.api.rowEdit.raise.saveRow( gridRow.entity );
12077 if ( gridRow.rowEditSavePromise ){
12078 gridRow.rowEditSavePromise.then( self.processSuccessPromise( grid, gridRow ), self.processErrorPromise( grid, gridRow ));
12080 $log.log( 'A promise was not returned when saveRow event was raised, either nobody is listening to event, or event handler did not return a promise' );
12089 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12090 * @name setSavePromise
12091 * @description Sets the promise associated with the row save, mandatory that
12092 * the saveRow event handler calls this method somewhere before returning.
12094 * gridApi.rowEdit.setSavePromise(grid, rowEntity)
12096 * @param {object} grid the grid for which dirty rows should be returned
12097 * @param {object} rowEntity a data row from the grid for which a save has
12099 * @param {promise} savePromise the promise that will be resolved when the
12100 * save is successful, or rejected if the save fails
12103 setSavePromise: function (grid, rowEntity, savePromise) {
12104 var gridRow = grid.getRow( rowEntity );
12105 gridRow.rowEditSavePromise = savePromise;
12111 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12112 * @name processSuccessPromise
12113 * @description Returns a function that processes the successful
12114 * resolution of a save promise
12115 * @param {object} grid the grid for which the promise should be processed
12116 * @param {GridRow} gridRow the row that has been saved
12117 * @returns {function} the success handling function
12119 processSuccessPromise: function ( grid, gridRow ) {
12122 return function() {
12123 delete gridRow.isSaving;
12124 delete gridRow.isDirty;
12125 delete gridRow.isError;
12126 delete gridRow.rowEditSaveTimer;
12127 self.removeRow( grid.rowEditErrorRows, gridRow );
12128 self.removeRow( grid.rowEditDirtyRows, gridRow );
12135 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12136 * @name processErrorPromise
12137 * @description Returns a function that processes the failed
12138 * resolution of a save promise
12139 * @param {object} grid the grid for which the promise should be processed
12140 * @param {GridRow} gridRow the row that is now in error
12141 * @returns {function} the error handling function
12143 processErrorPromise: function ( grid, gridRow ) {
12144 return function() {
12145 delete gridRow.isSaving;
12146 delete gridRow.rowEditSaveTimer;
12148 gridRow.isError = true;
12150 if (!grid.rowEditErrorRows){
12151 grid.rowEditErrorRows = [];
12153 grid.rowEditErrorRows.push( gridRow );
12160 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12162 * @description Removes a row from a cache of rows - either
12163 * grid.rowEditErrorRows or grid.rowEditDirtyRows. If the row
12164 * is not present silently does nothing.
12165 * @param {array} rowArray the array from which to remove the row
12166 * @param {GridRow} gridRow the row that should be removed
12168 removeRow: function( rowArray, removeGridRow ){
12169 angular.forEach( rowArray, function( gridRow, index ){
12170 if ( gridRow.uid === removeGridRow.uid ){
12171 rowArray.splice( index, 1);
12179 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12180 * @name flushDirtyRows
12181 * @description Triggers a save event for all currently dirty rows, could
12182 * be used where user presses a save button or navigates away from the page
12184 * gridApi.rowEdit.flushDirtyRows(grid)
12186 * @param {object} grid the grid for which dirty rows should be flushed
12187 * @returns {promise} a promise that represents the aggregate of all
12188 * of the individual save promises - i.e. it will be resolved when all
12189 * the individual save promises have been resolved.
12192 flushDirtyRows: function(grid){
12194 angular.forEach(grid.rowEditDirtyRows, function( gridRow ){
12195 service.saveRow( grid, gridRow )();
12196 promises.push( gridRow.rowEditSavePromise );
12199 return $q.all( promises );
12205 * @propertyOf ui.grid.rowEdit.api:GridOptions
12206 * @name rowEditWaitInterval
12207 * @description How long the grid should wait for another change on this row
12208 * before triggering a save (in milliseconds)
12211 * Setting the wait interval to 4 seconds
12213 * $scope.gridOptions = { rowEditWaitInterval: 4000 }
12219 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12220 * @name endEditCell
12221 * @description Receives an afterCellEdit event from the edit function,
12222 * and sets flags as appropriate. Only the rowEntity parameter
12223 * is processed, although other params are available. Grid
12224 * is automatically provided by the gridApi.
12225 * @param {object} rowEntity the data entity for which the cell
12228 endEditCell: function( rowEntity, colDef, newValue, previousValue ){
12229 var grid = this.grid;
12230 var gridRow = grid.getRow( rowEntity );
12231 if ( !gridRow ){ $log.log( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; }
12233 if ( newValue !== previousValue || gridRow.isDirty ){
12234 if ( !grid.rowEditDirtyRows ){
12235 grid.rowEditDirtyRows = [];
12238 if ( !gridRow.isDirty ){
12239 gridRow.isDirty = true;
12240 grid.rowEditDirtyRows.push( gridRow );
12243 delete gridRow.isError;
12245 service.considerSetTimer( grid, gridRow );
12252 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12253 * @name beginEditCell
12254 * @description Receives a beginCellEdit event from the edit function,
12255 * and cancels any rowEditSaveTimers if present, as the user is still editing
12256 * this row. Only the rowEntity parameter
12257 * is processed, although other params are available. Grid
12258 * is automatically provided by the gridApi.
12259 * @param {object} rowEntity the data entity for which the cell
12260 * editing has commenced
12262 beginEditCell: function( rowEntity, colDef ){
12263 var grid = this.grid;
12264 var gridRow = grid.getRow( rowEntity );
12265 if ( !gridRow ){ $log.log( 'Unable to find rowEntity in grid data, timer cannot be cancelled' ); return; }
12267 service.cancelTimer( grid, gridRow );
12273 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12274 * @name cancelEditCell
12275 * @description Receives a cancelCellEdit event from the edit function,
12276 * and if the row was already dirty, restarts the save timer. If the row
12277 * was not already dirty, then it's not dirty now either and does nothing.
12279 * Only the rowEntity parameter
12280 * is processed, although other params are available. Grid
12281 * is automatically provided by the gridApi.
12283 * @param {object} rowEntity the data entity for which the cell
12284 * editing was cancelled
12286 cancelEditCell: function( rowEntity, colDef ){
12287 var grid = this.grid;
12288 var gridRow = grid.getRow( rowEntity );
12289 if ( !gridRow ){ $log.log( 'Unable to find rowEntity in grid data, timer cannot be set' ); return; }
12291 service.considerSetTimer( grid, gridRow );
12297 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12299 * @description cellNav tells us that the selected cell has changed. If
12300 * the new row had a timer running, then stop it similar to in a beginCellEdit
12301 * call. If the old row is dirty and not the same as the new row, then
12302 * start a timer on it.
12303 * @param {object} newRowCol the row and column that were selected
12304 * @param {object} oldRowCol the row and column that was left
12307 navigate: function( newRowCol, oldRowCol ){
12308 var grid = this.grid;
12309 if ( newRowCol.row.rowEditSaveTimer ){
12310 service.cancelTimer( grid, newRowCol.row );
12313 if ( oldRowCol && oldRowCol.row && oldRowCol.row !== newRowCol.row ){
12314 service.considerSetTimer( grid, oldRowCol.row );
12321 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12322 * @name considerSetTimer
12323 * @description Consider setting a timer on this row (if it is dirty). if there is a timer running
12324 * on the row and the row isn't currently saving, cancel it, using cancelTimer, then if the row is
12325 * dirty and not currently saving then set a new timer
12326 * @param {object} grid the grid for which we are processing
12327 * @param {GridRow} gridRow the row for which the timer should be adjusted
12330 considerSetTimer: function( grid, gridRow ){
12331 service.cancelTimer( grid, gridRow );
12333 if ( gridRow.isDirty && !gridRow.isSaving ){
12334 var waitTime = grid.options.rowEditWaitInterval ? grid.options.rowEditWaitInterval : 2000;
12335 gridRow.rowEditSaveTimer = $interval( service.saveRow( grid, gridRow ), waitTime, 1);
12342 * @methodOf ui.grid.rowEdit.service:uiGridRowEditService
12343 * @name cancelTimer
12344 * @description cancel the $interval for any timer running on this row
12345 * then delete the timer itself
12346 * @param {object} grid the grid for which we are processing
12347 * @param {GridRow} gridRow the row for which the timer should be adjusted
12350 cancelTimer: function( grid, gridRow ){
12351 if ( gridRow.rowEditSaveTimer && !gridRow.isSaving ){
12352 $interval.cancel(gridRow.rowEditSaveTimer);
12353 delete gridRow.rowEditSaveTimer;
12364 * @name ui.grid.rowEdit.directive:uiGridEdit
12368 * @description Adds row editing features to the ui-grid-edit directive.
12371 module.directive('uiGridRowEdit', ['$log', 'uiGridRowEditService', 'uiGridEditConstants',
12372 function ($log, uiGridRowEditService, uiGridEditConstants) {
12376 require: '^uiGrid',
12378 compile: function () {
12380 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
12381 uiGridRowEditService.initializeGrid($scope, uiGridCtrl.grid);
12383 post: function ($scope, $elm, $attrs, uiGridCtrl) {
12393 * @name ui.grid.rowEdit.directive:uiGridViewport
12396 * @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
12397 * for the grid row to allow coloring of saving and error rows
12399 module.directive('uiGridViewport',
12400 ['$compile', 'uiGridConstants', '$log', '$parse',
12401 function ($compile, uiGridConstants, $log, $parse) {
12403 priority: -200, // run after default directive
12405 compile: function ($elm, $attrs) {
12406 var rowRepeatDiv = angular.element($elm.children().children()[0]);
12407 rowRepeatDiv.attr("ng-class", "{'ui-grid-row-saving': row.isSaving, 'ui-grid-row-error': row.isError}");
12409 pre: function ($scope, $elm, $attrs, controllers) {
12412 post: function ($scope, $elm, $attrs, controllers) {
12426 * @name ui.grid.selection
12429 * # ui.grid.selection
12430 * This module provides row selection
12434 * <div doc-module-components="ui.grid.selection"></div>
12437 var module = angular.module('ui.grid.selection', ['ui.grid']);
12441 * @name ui.grid.selection.constant:uiGridSelectionConstants
12443 * @description constants available in selection module
12445 module.constant('uiGridSelectionConstants', {
12446 featureName: "selection"
12451 * @name ui.grid.selection.service:uiGridSelectionService
12453 * @description Services for selection features
12455 module.service('uiGridSelectionService', ['$log', '$q', '$templateCache', 'uiGridSelectionConstants', 'gridUtil',
12456 function ($log, $q, $templateCache, uiGridSelectionConstants, gridUtil) {
12460 initializeGrid: function (grid) {
12462 //add feature namespace and any properties to grid for needed state
12463 grid.selection = {};
12464 grid.selection.lastSelectedRow = null;
12466 service.defaultGridOptions(grid.options);
12470 * @name ui.grid.selection.api:PublicApi
12472 * @description Public Api for selection feature
12479 * @name rowSelectionChanged
12480 * @eventOf ui.grid.selection.api:PublicApi
12481 * @description is raised after the row.isSelected state is changed
12482 * @param {GridRow} row the row that was selected/deselected
12484 rowSelectionChanged: function (scope, row) {
12492 * @name toggleRowSelection
12493 * @methodOf ui.grid.selection.api:PublicApi
12494 * @description Toggles data row as selected or unselected
12495 * @param {object} rowEntity gridOptions.data[] array instance
12497 toggleRowSelection: function (rowEntity) {
12498 var row = grid.getRow(rowEntity);
12499 if (row !== null) {
12500 service.toggleRowSelection(grid, row, grid.options.multiSelect);
12506 * @methodOf ui.grid.selection.api:PublicApi
12507 * @description Select the data row
12508 * @param {object} rowEntity gridOptions.data[] array instance
12510 selectRow: function (rowEntity) {
12511 var row = grid.getRow(rowEntity);
12512 if (row !== null && !row.isSelected) {
12513 service.toggleRowSelection(grid, row, grid.options.multiSelect);
12518 * @name unSelectRow
12519 * @methodOf ui.grid.selection.api:PublicApi
12520 * @description UnSelect the data row
12521 * @param {object} rowEntity gridOptions.data[] array instance
12523 unSelectRow: function (rowEntity) {
12524 var row = grid.getRow(rowEntity);
12525 if (row !== null && row.isSelected) {
12526 service.toggleRowSelection(grid, row, grid.options.multiSelect);
12531 * @name selectAllRows
12532 * @methodOf ui.grid.selection.api:PublicApi
12533 * @description Selects all rows. Does nothing if multiSelect = false
12535 selectAllRows: function () {
12536 if (grid.options.multiSelect === false) {
12540 grid.rows.forEach(function (row) {
12541 row.isSelected = true;
12546 * @name selectAllVisibleRows
12547 * @methodOf ui.grid.selection.api:PublicApi
12548 * @description Selects all visible rows. Does nothing if multiSelect = false
12550 selectAllVisibleRows: function () {
12551 if (grid.options.multiSelect === false) {
12555 grid.rows.forEach(function (row) {
12557 row.isSelected = true;
12559 row.isSelected = false;
12565 * @name clearSelectedRows
12566 * @methodOf ui.grid.selection.api:PublicApi
12567 * @description Unselects all rows
12569 clearSelectedRows: function () {
12570 service.clearSelectedRows(grid);
12574 * @name getSelectedRows
12575 * @methodOf ui.grid.selection.api:PublicApi
12576 * @description returns all selectedRow's entity references
12578 getSelectedRows: function () {
12579 return service.getSelectedRows(grid).map(function (gridRow) {
12580 return gridRow.entity;
12585 * @name getSelectedGridRows
12586 * @methodOf ui.grid.selection.api:PublicApi
12587 * @description returns all selectedRow's as gridRows
12589 getSelectedGridRows: function () {
12590 return service.getSelectedRows(grid);
12594 * @name setMultiSelect
12595 * @methodOf ui.grid.selection.api:PublicApi
12596 * @description Sets the current gridOption.multiSelect to true or false
12597 * @param {bool} multiSelect true to allow multiple rows
12599 setMultiSelect: function (multiSelect) {
12600 grid.options.multiSelect = multiSelect;
12606 grid.api.registerEventsFromObject(publicApi.events);
12608 grid.api.registerMethodsFromObject(publicApi.methods);
12612 defaultGridOptions: function (gridOptions) {
12613 //default option to true unless it was explicitly set to false
12616 * @name ui.grid.selection.api:GridOptions
12618 * @description GridOptions for selection feature, these are available to be
12619 * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
12624 * @name enableRowSelection
12625 * @propertyOf ui.grid.selection.api:GridOptions
12626 * @description Enable row selection for entire grid.
12627 * <br/>Defaults to true
12629 gridOptions.enableRowSelection = gridOptions.enableRowSelection !== false;
12632 * @name multiSelect
12633 * @propertyOf ui.grid.selection.api:GridOptions
12634 * @description Enable multiple row selection for entire grid
12635 * <br/>Defaults to true
12637 gridOptions.multiSelect = gridOptions.multiSelect !== false;
12640 * @name enableRowHeaderSelection
12641 * @propertyOf ui.grid.selection.api:GridOptions
12642 * @description Enable a row header to be used for selection
12643 * <br/>Defaults to true
12645 gridOptions.enableRowHeaderSelection = gridOptions.enableRowHeaderSelection !== false;
12650 * @name toggleRowSelection
12651 * @methodOf ui.grid.selection.service:uiGridSelectionService
12652 * @description Toggles row as selected or unselected
12653 * @param {Grid} grid grid object
12654 * @param {GridRow} row row to select or deselect
12655 * @param {bool} multiSelect if false, only one row at time can be selected
12657 toggleRowSelection: function (grid, row, multiSelect) {
12658 var selected = row.isSelected;
12659 if (!multiSelect && !selected) {
12660 service.clearSelectedRows(grid);
12662 row.isSelected = !selected;
12663 if (row.isSelected === true) {
12664 grid.selection.lastSelectedRow = row;
12666 grid.api.selection.raise.rowSelectionChanged(row);
12670 * @name shiftSelect
12671 * @methodOf ui.grid.selection.service:uiGridSelectionService
12672 * @description selects a group of rows from the last selected row using the shift key
12673 * @param {Grid} grid grid object
12674 * @param {GridRow} clicked row
12675 * @param {bool} multiSelect if false, does nothing this is for multiSelect only
12677 shiftSelect: function (grid, row, multiSelect) {
12678 if (!multiSelect) {
12681 var selectedRows = service.getSelectedRows(grid);
12682 var fromRow = selectedRows.length > 0 ? grid.renderContainers.body.visibleRowCache.indexOf(grid.selection.lastSelectedRow) : 0;
12683 var toRow = grid.renderContainers.body.visibleRowCache.indexOf(row);
12684 //reverse select direction
12685 if (fromRow > toRow) {
12690 for (var i = fromRow; i <= toRow; i++) {
12691 var rowToSelect = grid.renderContainers.body.visibleRowCache[i];
12693 rowToSelect.isSelected = true;
12694 grid.selection.lastSelectedRow = rowToSelect;
12695 grid.api.selection.raise.rowSelectionChanged(rowToSelect);
12701 * @name getSelectedRows
12702 * @methodOf ui.grid.selection.service:uiGridSelectionService
12703 * @description Returns all the selected rows
12704 * @param {Grid} grid grid object
12706 getSelectedRows: function (grid) {
12707 return grid.rows.filter(function (row) {
12708 return row.isSelected;
12714 * @name clearSelectedRows
12715 * @methodOf ui.grid.selection.service:uiGridSelectionService
12716 * @description Clears all selected rows
12717 * @param {Grid} grid grid object
12719 clearSelectedRows: function (grid) {
12720 service.getSelectedRows(grid).forEach(function (row) {
12721 row.isSelected = false;
12722 grid.api.selection.raise.rowSelectionChanged(row);
12735 * @name ui.grid.selection.directive:uiGridSelection
12739 * @description Adds selection features to grid
12742 <example module="app">
12743 <file name="app.js">
12744 var app = angular.module('app', ['ui.grid', 'ui.grid.edit']);
12746 app.controller('MainCtrl', ['$scope', function ($scope) {
12748 { name: 'Bob', title: 'CEO' },
12749 { name: 'Frank', title: 'Lowly Developer' }
12752 $scope.columnDefs = [
12753 {name: 'name', enableCellEdit: true},
12754 {name: 'title', enableCellEdit: true}
12758 <file name="index.html">
12759 <div ng-controller="MainCtrl">
12760 <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-selection></div>
12765 module.directive('uiGridSelection', ['$log', 'uiGridSelectionConstants', 'uiGridSelectionService', '$templateCache',
12766 function ($log, uiGridSelectionConstants, uiGridSelectionService, $templateCache) {
12770 require: '^uiGrid',
12772 compile: function () {
12774 pre: function ($scope, $elm, $attrs, uiGridCtrl) {
12775 uiGridSelectionService.initializeGrid(uiGridCtrl.grid);
12776 if (uiGridCtrl.grid.options.enableRowHeaderSelection) {
12777 var cellTemplate = 'ui-grid/selectionRowHeader';
12778 var selectionRowHeaderDef = { name: 'selectionRowHeaderCol', displayName: '', width: 30, cellTemplate: cellTemplate};
12779 uiGridCtrl.grid.addRowHeaderColumn(selectionRowHeaderDef);
12782 post: function ($scope, $elm, $attrs, uiGridCtrl) {
12790 module.directive('uiGridSelectionRowHeaderButtons', ['$log', '$templateCache', 'uiGridSelectionService',
12791 function ($log, $templateCache, uiGridSelectionService) {
12795 template: $templateCache.get('ui-grid/selectionRowHeaderButtons'),
12797 require: '^uiGrid',
12798 link: function($scope, $elm, $attrs, uiGridCtrl) {
12799 var self = uiGridCtrl.grid;
12800 $scope.selectButtonClick = function(row, evt) {
12801 if (evt.shiftKey) {
12802 uiGridSelectionService.shiftSelect(self, row, self.options.multiSelect);
12806 uiGridSelectionService.toggleRowSelection(self, row, self.options.multiSelect);
12815 * @name ui.grid.selection.directive:uiGridViewport
12818 * @description Stacks on top of ui.grid.uiGridViewport to alter the attributes used
12821 module.directive('uiGridViewport',
12822 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', '$log', '$parse', 'uiGridSelectionService',
12823 function ($compile, uiGridConstants, uiGridSelectionConstants, $log, $parse, uiGridSelectionService) {
12825 priority: -200, // run after default directive
12827 compile: function ($elm, $attrs) {
12828 var rowRepeatDiv = angular.element($elm.children().children()[0]);
12829 rowRepeatDiv.attr("ng-class", "{'ui-grid-row-selected': row.isSelected}");
12831 pre: function ($scope, $elm, $attrs, controllers) {
12834 post: function ($scope, $elm, $attrs, controllers) {
12843 * @name ui.grid.selection.directive:uiGridCell
12847 * @description Stacks on top of ui.grid.uiGridCell to provide selection feature
12849 module.directive('uiGridCell',
12850 ['$compile', 'uiGridConstants', 'uiGridSelectionConstants', '$log', '$parse', 'uiGridSelectionService',
12851 function ($compile, uiGridConstants, uiGridSelectionConstants, $log, $parse, uiGridSelectionService) {
12853 priority: -200, // run after default uiGridCell directive
12856 link: function ($scope, $elm, $attrs) {
12858 if ($scope.grid.options.enableRowSelection && !$scope.grid.options.enableRowHeaderSelection) {
12859 $elm.addClass('ui-grid-disable-selection');
12860 registerRowSelectionEvents();
12863 function registerRowSelectionEvents() {
12864 $elm.on('click', function (evt) {
12865 if (evt.shiftKey) {
12866 uiGridSelectionService.shiftSelect($scope.grid, $scope.row, $scope.grid.options.multiSelect);
12870 uiGridSelectionService.toggleRowSelection($scope.grid, $scope.row, $scope.grid.options.multiSelect);
12880 angular.module('ui.grid').run(['$templateCache', function($templateCache) {
12883 $templateCache.put('ui-grid/ui-grid-footer',
12884 "<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>"
12888 $templateCache.put('ui-grid/ui-grid-group-panel',
12889 "<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>"
12893 $templateCache.put('ui-grid/ui-grid-header',
12894 "<div class=\"ui-grid-top-panel\"><div ui-grid-group-panel ng-show=\"grid.options.showGroupPanel\"></div><div class=\"ui-grid-header 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>"
12898 $templateCache.put('ui-grid/ui-grid-no-header',
12899 "<div class=\"ui-grid-top-panel\"></div>"
12903 $templateCache.put('ui-grid/ui-grid-row',
12904 "<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>"
12908 $templateCache.put('ui-grid/ui-grid',
12909 "<div ui-i18n=\"en\" class=\"ui-grid\"><!-- TODO (c0bra): add \"scoped\" attr here, eventually? --><style ui-grid-style>.grid{{ grid.id }} {\n" +
12910 " /* Styles for the grid */\n" +
12913 " .grid{{ grid.id }} .ui-grid-row, .grid{{ grid.id }} .ui-grid-cell, .grid{{ grid.id }} .ui-grid-cell .ui-grid-vertical-bar {\n" +
12914 " height: {{ grid.options.rowHeight }}px;\n" +
12917 " .grid{{ grid.id }} .ui-grid-row:last-child .ui-grid-cell {\n" +
12918 " border-bottom-width: {{ ((grid.getTotalRowHeight() < grid.getViewportHeight()) && '1') || '0' }}px;\n" +
12921 " {{ grid.verticalScrollbarStyles }}\n" +
12922 " {{ grid.horizontalScrollbarStyles }}\n" +
12924 " .ui-grid[dir=rtl] .ui-grid-viewport {\n" +
12925 " padding-left: {{ grid.verticalScrollbarWidth }}px;\n" +
12928 " {{ grid.customStyles }}</style><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-scrollbars=\"grid.options.enableScrollbars\"></div><div ui-grid-column-menu ng-if=\"grid.options.enableColumnMenu\"></div><div ng-transclude></div></div>"
12932 $templateCache.put('ui-grid/uiGridCell',
12933 "<div class=\"ui-grid-cell-contents\">{{COL_FIELD CUSTOM_FILTERS}}</div>"
12937 $templateCache.put('ui-grid/uiGridColumnFilter',
12938 "<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>"
12942 $templateCache.put('ui-grid/uiGridColumnMenu',
12943 "<div class=\"ui-grid-column-menu\"><div ui-grid-menu menu-items=\"menuItems\"><!-- <div class=\"ui-grid-column-menu\">\n" +
12944 " <div class=\"inner\" ng-show=\"menuShown\">\n" +
12946 " <div ng-show=\"grid.options.enableSorting\">\n" +
12947 " <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" +
12948 " <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" +
12949 " <li ng-show=\"col.sort.direction\" ng-click=\"unsortColumn()\"><i class=\"ui-grid-icon-cancel\"></i> Remove Sort</li>\n" +
12953 " </div> --></div></div>"
12957 $templateCache.put('ui-grid/uiGridFooterCell',
12958 "<div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\"><div>{{ col.getAggregationValue() }}</div></div>"
12962 $templateCache.put('ui-grid/uiGridHeaderCell',
12963 "<div ng-class=\"{ 'sortable': sortable }\"><div class=\"ui-grid-vertical-bar\"> </div><div class=\"ui-grid-cell-contents\" col-index=\"renderIndex\">{{ col.displayName CUSTOM_FILTERS }} <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 ng-if=\"grid.options.enableColumnMenu && !col.isRowHeader\" class=\"ui-grid-column-menu-button\" ng-click=\"toggleMenu($event)\"><i class=\"ui-grid-icon-angle-down\"> <i></i></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 right\" ng-show=\"!!colFilter.term\"> </i> <!-- use !! because angular interprets 'f' as false --></div></div></div>"
12967 $templateCache.put('ui-grid/uiGridMenu',
12968 "<div class=\"ui-grid-menu\"><div class=\"ui-grid-menu-inner\" ng-show=\"shown\"><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>"
12972 $templateCache.put('ui-grid/uiGridMenuItem',
12973 "<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>"
12977 $templateCache.put('ui-grid/uiGridRenderContainer',
12978 "<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=\"enableScrollbars\" type=\"vertical\"></div><div ui-grid-native-scrollbar ng-if=\"enableScrollbars\" type=\"horizontal\"></div></div>"
12982 $templateCache.put('ui-grid/uiGridViewport',
12983 "<div class=\"ui-grid-viewport\"><div class=\"ui-grid-canvas\"><div ng-repeat=\"(rowRenderIndex, row) in rowContainer.renderedRows track by row.uid\" class=\"ui-grid-row\" ng-style=\"containerCtrl.rowStyle(rowRenderIndex)\"><div ui-grid-row=\"row\" row-render-index=\"rowRenderIndex\"></div></div></div></div>"
12987 $templateCache.put('ui-grid/cellEditor',
12988 "<div><form name=\"inputForm\"><input type=\"{{inputType}}\" ng-class=\"'colt' + col.index\" ui-grid-editor ng-model=\"COL_FIELD\"></form></div>"
12992 $templateCache.put('ui-grid/dropdownEditor',
12993 "<div><form name=\"inputForm\"><select ng-class=\"'colt' + col.index\" ui-grid-edit-dropdown ng-model=\"COL_FIELD\" ng-options=\"field[editDropdownIdLabel] as field[editDropdownValueLabel] CUSTOM_FILTERS for field in editDropdownOptionsArray\"></select></form></div>"
12997 $templateCache.put('ui-grid/expandableRow',
12998 "<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)\n" +
12999 " ,height: grid.options.expandable.expandableRowHeight}\"></div>"
13003 $templateCache.put('ui-grid/expandableRowHeader',
13004 "<div class=\"ui-grid-row-header-cell uiGridExpandableButtonsCell\"><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>"
13008 $templateCache.put('ui-grid/expandableScrollFiller',
13009 "<div ng-if=\"expandableRow.shouldRenderFiller()\" style=\"float:left;margin-top: 2px;margin-bottom: 2px\" ng-style=\"{width: (grid.getViewportWidth())\n" +
13010 " ,height: grid.options.expandable.expandableRowHeight, 'margin-left': grid.options.rowHeader.rowHeaderWidth}\"><i class=\"ui-grid-icon-spin5 ui-grid-animate-spin\" ng-style=\"{'margin-top': ( grid.options.expandable.expandableRowHeight/2 - 5),\n" +
13011 " 'margin-left':((grid.getViewportWidth() - grid.options.rowHeader.rowHeaderWidth)/2 - 5)}\"></i></div>"
13015 $templateCache.put('ui-grid/csvLink',
13016 "<span class=\"ui-grid-exporter-csv-link-span\"><a href=\"data:text/csv;charset=UTF-8,CSV_CONTENT\">LINK_LABEL</a></span>"
13020 $templateCache.put('ui-grid/columnResizer',
13021 "<div ui-grid-column-resizer ng-if=\"grid.options.enableColumnResizing\" class=\"ui-grid-column-resizer\" col=\"col\" position=\"right\" render-index=\"renderIndex\"></div>"
13025 $templateCache.put('ui-grid/selectionRowHeader',
13026 "<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>"
13030 $templateCache.put('ui-grid/selectionRowHeaderButtons',
13031 "<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>"