ui-grid.resize-columns.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. /*!
  2. * ui-grid - v4.4.6 - 2018-04-06
  3. * Copyright (c) 2018 ; License: MIT
  4. */
  5. (function(){
  6. 'use strict';
  7. /**
  8. * @ngdoc overview
  9. * @name ui.grid.resizeColumns
  10. * @description
  11. *
  12. * # ui.grid.resizeColumns
  13. *
  14. * <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
  15. *
  16. * This module allows columns to be resized.
  17. */
  18. var module = angular.module('ui.grid.resizeColumns', ['ui.grid']);
  19. module.service('uiGridResizeColumnsService', ['gridUtil', '$q', '$rootScope',
  20. function (gridUtil, $q, $rootScope) {
  21. var service = {
  22. defaultGridOptions: function(gridOptions){
  23. //default option to true unless it was explicitly set to false
  24. /**
  25. * @ngdoc object
  26. * @name ui.grid.resizeColumns.api:GridOptions
  27. *
  28. * @description GridOptions for resizeColumns feature, these are available to be
  29. * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
  30. */
  31. /**
  32. * @ngdoc object
  33. * @name enableColumnResizing
  34. * @propertyOf ui.grid.resizeColumns.api:GridOptions
  35. * @description Enable column resizing on the entire grid
  36. * <br/>Defaults to true
  37. */
  38. gridOptions.enableColumnResizing = gridOptions.enableColumnResizing !== false;
  39. //legacy support
  40. //use old name if it is explicitly false
  41. if (gridOptions.enableColumnResize === false){
  42. gridOptions.enableColumnResizing = false;
  43. }
  44. },
  45. colResizerColumnBuilder: function (colDef, col, gridOptions) {
  46. var promises = [];
  47. /**
  48. * @ngdoc object
  49. * @name ui.grid.resizeColumns.api:ColumnDef
  50. *
  51. * @description ColumnDef for resizeColumns feature, these are available to be
  52. * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
  53. */
  54. /**
  55. * @ngdoc object
  56. * @name enableColumnResizing
  57. * @propertyOf ui.grid.resizeColumns.api:ColumnDef
  58. * @description Enable column resizing on an individual column
  59. * <br/>Defaults to GridOptions.enableColumnResizing
  60. */
  61. //default to true unless gridOptions or colDef is explicitly false
  62. colDef.enableColumnResizing = colDef.enableColumnResizing === undefined ? gridOptions.enableColumnResizing : colDef.enableColumnResizing;
  63. //legacy support of old option name
  64. if (colDef.enableColumnResize === false){
  65. colDef.enableColumnResizing = false;
  66. }
  67. return $q.all(promises);
  68. },
  69. registerPublicApi: function (grid) {
  70. /**
  71. * @ngdoc object
  72. * @name ui.grid.resizeColumns.api:PublicApi
  73. * @description Public Api for column resize feature.
  74. */
  75. var publicApi = {
  76. events: {
  77. /**
  78. * @ngdoc event
  79. * @name columnSizeChanged
  80. * @eventOf ui.grid.resizeColumns.api:PublicApi
  81. * @description raised when column is resized
  82. * <pre>
  83. * gridApi.colResizable.on.columnSizeChanged(scope,function(colDef, deltaChange){})
  84. * </pre>
  85. * @param {object} colDef the column that was resized
  86. * @param {integer} delta of the column size change
  87. */
  88. colResizable: {
  89. columnSizeChanged: function (colDef, deltaChange) {
  90. }
  91. }
  92. }
  93. };
  94. grid.api.registerEventsFromObject(publicApi.events);
  95. },
  96. fireColumnSizeChanged: function (grid, colDef, deltaChange) {
  97. $rootScope.$applyAsync(function () {
  98. if ( grid.api.colResizable ){
  99. grid.api.colResizable.raise.columnSizeChanged(colDef, deltaChange);
  100. } else {
  101. gridUtil.logError("The resizeable api is not registered, this may indicate that you've included the module but not added the 'ui-grid-resize-columns' directive to your grid definition. Cannot raise any events.");
  102. }
  103. });
  104. },
  105. // get either this column, or the column next to this column, to resize,
  106. // returns the column we're going to resize
  107. findTargetCol: function(col, position, rtlMultiplier){
  108. var renderContainer = col.getRenderContainer();
  109. if (position === 'left') {
  110. // Get the column to the left of this one
  111. var colIndex = renderContainer.visibleColumnCache.indexOf(col);
  112. return renderContainer.visibleColumnCache[colIndex - 1 * rtlMultiplier];
  113. } else {
  114. return col;
  115. }
  116. }
  117. };
  118. return service;
  119. }]);
  120. /**
  121. * @ngdoc directive
  122. * @name ui.grid.resizeColumns.directive:uiGridResizeColumns
  123. * @element div
  124. * @restrict A
  125. * @description
  126. * 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
  127. * option to false. This prevents resizing for the entire grid, regardless of individual columnDef options.
  128. *
  129. * @example
  130. <doc:example module="app">
  131. <doc:source>
  132. <script>
  133. var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
  134. app.controller('MainCtrl', ['$scope', function ($scope) {
  135. $scope.gridOpts = {
  136. data: [
  137. { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
  138. { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
  139. { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
  140. { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
  141. ]
  142. };
  143. }]);
  144. </script>
  145. <div ng-controller="MainCtrl">
  146. <div class="testGrid" ui-grid="gridOpts" ui-grid-resize-columns ></div>
  147. </div>
  148. </doc:source>
  149. <doc:scenario>
  150. </doc:scenario>
  151. </doc:example>
  152. */
  153. module.directive('uiGridResizeColumns', ['gridUtil', 'uiGridResizeColumnsService', function (gridUtil, uiGridResizeColumnsService) {
  154. return {
  155. replace: true,
  156. priority: 0,
  157. require: '^uiGrid',
  158. scope: false,
  159. compile: function () {
  160. return {
  161. pre: function ($scope, $elm, $attrs, uiGridCtrl) {
  162. uiGridResizeColumnsService.defaultGridOptions(uiGridCtrl.grid.options);
  163. uiGridCtrl.grid.registerColumnBuilder( uiGridResizeColumnsService.colResizerColumnBuilder);
  164. uiGridResizeColumnsService.registerPublicApi(uiGridCtrl.grid);
  165. },
  166. post: function ($scope, $elm, $attrs, uiGridCtrl) {
  167. }
  168. };
  169. }
  170. };
  171. }]);
  172. // Extend the uiGridHeaderCell directive
  173. module.directive('uiGridHeaderCell', ['gridUtil', '$templateCache', '$compile', '$q', 'uiGridResizeColumnsService', 'uiGridConstants', function (gridUtil, $templateCache, $compile, $q, uiGridResizeColumnsService, uiGridConstants) {
  174. return {
  175. // Run after the original uiGridHeaderCell
  176. priority: -10,
  177. require: '^uiGrid',
  178. // scope: false,
  179. compile: function() {
  180. return {
  181. post: function ($scope, $elm, $attrs, uiGridCtrl) {
  182. var grid = uiGridCtrl.grid;
  183. if (grid.options.enableColumnResizing) {
  184. var columnResizerElm = $templateCache.get('ui-grid/columnResizer');
  185. var rtlMultiplier = 1;
  186. //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
  187. if (grid.isRTL()) {
  188. $scope.position = 'left';
  189. rtlMultiplier = -1;
  190. }
  191. var displayResizers = function(){
  192. // remove any existing resizers.
  193. var resizers = $elm[0].getElementsByClassName('ui-grid-column-resizer');
  194. for ( var i = 0; i < resizers.length; i++ ){
  195. angular.element(resizers[i]).remove();
  196. }
  197. // get the target column for the left resizer
  198. var otherCol = uiGridResizeColumnsService.findTargetCol($scope.col, 'left', rtlMultiplier);
  199. var renderContainer = $scope.col.getRenderContainer();
  200. // Don't append the left resizer if this is the first column or the column to the left of this one has resizing disabled
  201. if (otherCol && renderContainer.visibleColumnCache.indexOf($scope.col) !== 0 && otherCol.colDef.enableColumnResizing !== false) {
  202. var resizerLeft = angular.element(columnResizerElm).clone();
  203. resizerLeft.attr('position', 'left');
  204. $elm.prepend(resizerLeft);
  205. $compile(resizerLeft)($scope);
  206. }
  207. // Don't append the right resizer if this column has resizing disabled
  208. if ($scope.col.colDef.enableColumnResizing !== false) {
  209. var resizerRight = angular.element(columnResizerElm).clone();
  210. resizerRight.attr('position', 'right');
  211. $elm.append(resizerRight);
  212. $compile(resizerRight)($scope);
  213. }
  214. };
  215. displayResizers();
  216. var waitDisplay = function() {
  217. $scope.$applyAsync(displayResizers);
  218. };
  219. var dataChangeDereg = grid.registerDataChangeCallback( waitDisplay, [uiGridConstants.dataChange.COLUMN] );
  220. $scope.$on( '$destroy', dataChangeDereg );
  221. }
  222. }
  223. };
  224. }
  225. };
  226. }]);
  227. /**
  228. * @ngdoc directive
  229. * @name ui.grid.resizeColumns.directive:uiGridColumnResizer
  230. * @element div
  231. * @restrict A
  232. *
  233. * @description
  234. * Draggable handle that controls column resizing.
  235. *
  236. * @example
  237. <doc:example module="app">
  238. <doc:source>
  239. <script>
  240. var app = angular.module('app', ['ui.grid', 'ui.grid.resizeColumns']);
  241. app.controller('MainCtrl', ['$scope', function ($scope) {
  242. $scope.gridOpts = {
  243. enableColumnResizing: true,
  244. data: [
  245. { "name": "Ethel Price", "gender": "female", "company": "Enersol" },
  246. { "name": "Claudine Neal", "gender": "female", "company": "Sealoud" },
  247. { "name": "Beryl Rice", "gender": "female", "company": "Velity" },
  248. { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" }
  249. ]
  250. };
  251. }]);
  252. </script>
  253. <div ng-controller="MainCtrl">
  254. <div class="testGrid" ui-grid="gridOpts"></div>
  255. </div>
  256. </doc:source>
  257. <doc:scenario>
  258. // TODO: e2e specs?
  259. // TODO: post-resize a horizontal scroll event should be fired
  260. </doc:scenario>
  261. </doc:example>
  262. */
  263. module.directive('uiGridColumnResizer', ['$document', 'gridUtil', 'uiGridConstants', 'uiGridResizeColumnsService', function ($document, gridUtil, uiGridConstants, uiGridResizeColumnsService) {
  264. var resizeOverlay = angular.element('<div class="ui-grid-resize-overlay"></div>');
  265. var resizer = {
  266. priority: 0,
  267. scope: {
  268. col: '=',
  269. position: '@',
  270. renderIndex: '='
  271. },
  272. require: '?^uiGrid',
  273. link: function ($scope, $elm, $attrs, uiGridCtrl) {
  274. var startX = 0,
  275. x = 0,
  276. gridLeft = 0,
  277. rtlMultiplier = 1;
  278. //when in RTL mode reverse the direction using the rtlMultiplier and change the position to left
  279. if (uiGridCtrl.grid.isRTL()) {
  280. $scope.position = 'left';
  281. rtlMultiplier = -1;
  282. }
  283. if ($scope.position === 'left') {
  284. $elm.addClass('left');
  285. }
  286. else if ($scope.position === 'right') {
  287. $elm.addClass('right');
  288. }
  289. // Refresh the grid canvas
  290. // takes an argument representing the diff along the X-axis that the resize had
  291. function refreshCanvas(xDiff) {
  292. // Then refresh the grid canvas, rebuilding the styles so that the scrollbar updates its size
  293. uiGridCtrl.grid.refreshCanvas(true).then( function() {
  294. uiGridCtrl.grid.queueGridRefresh();
  295. });
  296. }
  297. // Check that the requested width isn't wider than the maxWidth, or narrower than the minWidth
  298. // Returns the new recommended with, after constraints applied
  299. function constrainWidth(col, width){
  300. var newWidth = width;
  301. // If the new width would be less than the column's allowably minimum width, don't allow it
  302. if (col.minWidth && newWidth < col.minWidth) {
  303. newWidth = col.minWidth;
  304. }
  305. else if (col.maxWidth && newWidth > col.maxWidth) {
  306. newWidth = col.maxWidth;
  307. }
  308. return newWidth;
  309. }
  310. /*
  311. * Our approach to event handling aims to deal with both touch devices and mouse devices
  312. * We register down handlers on both touch and mouse. When a touchstart or mousedown event
  313. * occurs, we register the corresponding touchmove/touchend, or mousemove/mouseend events.
  314. *
  315. * This way we can listen for both without worrying about the fact many touch devices also emulate
  316. * mouse events - basically whichever one we hear first is what we'll go with.
  317. */
  318. function moveFunction(event, args) {
  319. if (event.originalEvent) { event = event.originalEvent; }
  320. event.preventDefault();
  321. x = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
  322. if (x < 0) { x = 0; }
  323. else if (x > uiGridCtrl.grid.gridWidth) { x = uiGridCtrl.grid.gridWidth; }
  324. var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
  325. // Don't resize if it's disabled on this column
  326. if (col.colDef.enableColumnResizing === false) {
  327. return;
  328. }
  329. if (!uiGridCtrl.grid.element.hasClass('column-resizing')) {
  330. uiGridCtrl.grid.element.addClass('column-resizing');
  331. }
  332. // Get the diff along the X axis
  333. var xDiff = x - startX;
  334. // Get the width that this mouse would give the column
  335. var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
  336. // check we're not outside the allowable bounds for this column
  337. x = x + ( constrainWidth(col, newWidth) - newWidth ) * rtlMultiplier;
  338. resizeOverlay.css({ left: x + 'px' });
  339. uiGridCtrl.fireEvent(uiGridConstants.events.ITEM_DRAGGING);
  340. }
  341. function upFunction(event, args) {
  342. if (event.originalEvent) { event = event.originalEvent; }
  343. event.preventDefault();
  344. uiGridCtrl.grid.element.removeClass('column-resizing');
  345. resizeOverlay.remove();
  346. // Resize the column
  347. x = (event.changedTouches ? event.changedTouches[0] : event).clientX - gridLeft;
  348. var xDiff = x - startX;
  349. if (xDiff === 0) {
  350. // no movement, so just reset event handlers, including turning back on both
  351. // down events - we turned one off when this event started
  352. offAllEvents();
  353. onDownEvents();
  354. return;
  355. }
  356. var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
  357. // Don't resize if it's disabled on this column
  358. if (col.colDef.enableColumnResizing === false) {
  359. return;
  360. }
  361. // Get the new width
  362. var newWidth = parseInt(col.drawnWidth + xDiff * rtlMultiplier, 10);
  363. // check we're not outside the allowable bounds for this column
  364. col.width = constrainWidth(col, newWidth);
  365. col.hasCustomWidth = true;
  366. refreshCanvas(xDiff);
  367. uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff);
  368. // stop listening of up and move events - wait for next down
  369. // reset the down events - we will have turned one off when this event started
  370. offAllEvents();
  371. onDownEvents();
  372. }
  373. var downFunction = function(event, args) {
  374. if (event.originalEvent) { event = event.originalEvent; }
  375. event.stopPropagation();
  376. // Get the left offset of the grid
  377. // gridLeft = uiGridCtrl.grid.element[0].offsetLeft;
  378. gridLeft = uiGridCtrl.grid.element[0].getBoundingClientRect().left;
  379. // Get the starting X position, which is the X coordinate of the click minus the grid's offset
  380. startX = (event.targetTouches ? event.targetTouches[0] : event).clientX - gridLeft;
  381. // Append the resizer overlay
  382. uiGridCtrl.grid.element.append(resizeOverlay);
  383. // Place the resizer overlay at the start position
  384. resizeOverlay.css({ left: startX });
  385. // Add handlers for move and up events - if we were mousedown then we listen for mousemove and mouseup, if
  386. // we were touchdown then we listen for touchmove and touchup. Also remove the handler for the equivalent
  387. // down event - so if we're touchdown, then remove the mousedown handler until this event is over, if we're
  388. // mousedown then remove the touchdown handler until this event is over, this avoids processing duplicate events
  389. if ( event.type === 'touchstart' ){
  390. $document.on('touchend', upFunction);
  391. $document.on('touchmove', moveFunction);
  392. $elm.off('mousedown', downFunction);
  393. } else {
  394. $document.on('mouseup', upFunction);
  395. $document.on('mousemove', moveFunction);
  396. $elm.off('touchstart', downFunction);
  397. }
  398. };
  399. var onDownEvents = function() {
  400. $elm.on('mousedown', downFunction);
  401. $elm.on('touchstart', downFunction);
  402. };
  403. var offAllEvents = function() {
  404. $document.off('mouseup', upFunction);
  405. $document.off('touchend', upFunction);
  406. $document.off('mousemove', moveFunction);
  407. $document.off('touchmove', moveFunction);
  408. $elm.off('mousedown', downFunction);
  409. $elm.off('touchstart', downFunction);
  410. };
  411. onDownEvents();
  412. // On doubleclick, resize to fit all rendered cells
  413. var dblClickFn = function(event, args){
  414. event.stopPropagation();
  415. var col = uiGridResizeColumnsService.findTargetCol($scope.col, $scope.position, rtlMultiplier);
  416. // Don't resize if it's disabled on this column
  417. if (col.colDef.enableColumnResizing === false) {
  418. return;
  419. }
  420. // Go through the rendered rows and find out the max size for the data in this column
  421. var maxWidth = 0;
  422. var xDiff = 0;
  423. // Get the parent render container element
  424. var renderContainerElm = gridUtil.closestElm($elm, '.ui-grid-render-container');
  425. // 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
  426. var cells = renderContainerElm.querySelectorAll('.' + uiGridConstants.COL_CLASS_PREFIX + col.uid + ' .ui-grid-cell-contents');
  427. Array.prototype.forEach.call(cells, function (cell) {
  428. // Get the cell width
  429. // gridUtil.logDebug('width', gridUtil.elementWidth(cell));
  430. // Account for the menu button if it exists
  431. var menuButton;
  432. if (angular.element(cell).parent().hasClass('ui-grid-header-cell')) {
  433. menuButton = angular.element(cell).parent()[0].querySelectorAll('.ui-grid-column-menu-button');
  434. }
  435. gridUtil.fakeElement(cell, {}, function(newElm) {
  436. // Make the element float since it's a div and can expand to fill its container
  437. var e = angular.element(newElm);
  438. e.attr('style', 'float: left');
  439. var width = gridUtil.elementWidth(e);
  440. if (menuButton) {
  441. var menuButtonWidth = gridUtil.elementWidth(menuButton);
  442. width = width + menuButtonWidth;
  443. }
  444. if (width > maxWidth) {
  445. maxWidth = width;
  446. }
  447. });
  448. });
  449. // check we're not outside the allowable bounds for this column
  450. var newWidth = constrainWidth(col, maxWidth);
  451. xDiff = newWidth - col.drawnWidth;
  452. col.width = newWidth;
  453. col.hasCustomWidth = true;
  454. refreshCanvas(xDiff);
  455. uiGridResizeColumnsService.fireColumnSizeChanged(uiGridCtrl.grid, col.colDef, xDiff); };
  456. $elm.on('dblclick', dblClickFn);
  457. $elm.on('$destroy', function() {
  458. $elm.off('dblclick', dblClickFn);
  459. offAllEvents();
  460. });
  461. }
  462. };
  463. return resizer;
  464. }]);
  465. })();