ui-grid.move-columns.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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.moveColumns
  10. * @description
  11. *
  12. * # ui.grid.moveColumns
  13. *
  14. * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div>
  15. *
  16. * This module provides column moving capability to ui.grid. It enables to change the position of columns.
  17. * <div doc-module-components="ui.grid.moveColumns"></div>
  18. */
  19. var module = angular.module('ui.grid.moveColumns', ['ui.grid']);
  20. /**
  21. * @ngdoc service
  22. * @name ui.grid.moveColumns.service:uiGridMoveColumnService
  23. * @description Service for column moving feature.
  24. */
  25. module.service('uiGridMoveColumnService', ['$q', '$rootScope', '$log', 'ScrollEvent', 'uiGridConstants', 'gridUtil', function ($q, $rootScope, $log, ScrollEvent, uiGridConstants, gridUtil) {
  26. var service = {
  27. initializeGrid: function (grid) {
  28. var self = this;
  29. this.registerPublicApi(grid);
  30. this.defaultGridOptions(grid.options);
  31. grid.moveColumns = {orderCache: []}; // Used to cache the order before columns are rebuilt
  32. grid.registerColumnBuilder(self.movableColumnBuilder);
  33. grid.registerDataChangeCallback(self.verifyColumnOrder, [uiGridConstants.dataChange.COLUMN]);
  34. },
  35. registerPublicApi: function (grid) {
  36. var self = this;
  37. /**
  38. * @ngdoc object
  39. * @name ui.grid.moveColumns.api:PublicApi
  40. * @description Public Api for column moving feature.
  41. */
  42. var publicApi = {
  43. events: {
  44. /**
  45. * @ngdoc event
  46. * @name columnPositionChanged
  47. * @eventOf ui.grid.moveColumns.api:PublicApi
  48. * @description raised when column is moved
  49. * <pre>
  50. * gridApi.colMovable.on.columnPositionChanged(scope,function(colDef, originalPosition, newPosition){})
  51. * </pre>
  52. * @param {object} colDef the column that was moved
  53. * @param {integer} originalPosition of the column
  54. * @param {integer} finalPosition of the column
  55. */
  56. colMovable: {
  57. columnPositionChanged: function (colDef, originalPosition, newPosition) {
  58. }
  59. }
  60. },
  61. methods: {
  62. /**
  63. * @ngdoc method
  64. * @name moveColumn
  65. * @methodOf ui.grid.moveColumns.api:PublicApi
  66. * @description Method can be used to change column position.
  67. * <pre>
  68. * gridApi.colMovable.moveColumn(oldPosition, newPosition)
  69. * </pre>
  70. * @param {integer} originalPosition of the column
  71. * @param {integer} finalPosition of the column
  72. */
  73. colMovable: {
  74. moveColumn: function (originalPosition, finalPosition) {
  75. var columns = grid.columns;
  76. if (!angular.isNumber(originalPosition) || !angular.isNumber(finalPosition)) {
  77. gridUtil.logError('MoveColumn: Please provide valid values for originalPosition and finalPosition');
  78. return;
  79. }
  80. var nonMovableColumns = 0;
  81. for (var i = 0; i < columns.length; i++) {
  82. if ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true) {
  83. nonMovableColumns++;
  84. }
  85. }
  86. if (originalPosition >= (columns.length - nonMovableColumns) || finalPosition >= (columns.length - nonMovableColumns)) {
  87. gridUtil.logError('MoveColumn: Invalid values for originalPosition, finalPosition');
  88. return;
  89. }
  90. var findPositionForRenderIndex = function (index) {
  91. var position = index;
  92. for (var i = 0; i <= position; i++) {
  93. if (angular.isDefined(columns[i]) && ((angular.isDefined(columns[i].colDef.visible) && columns[i].colDef.visible === false) || columns[i].isRowHeader === true)) {
  94. position++;
  95. }
  96. }
  97. return position;
  98. };
  99. self.redrawColumnAtPosition(grid, findPositionForRenderIndex(originalPosition), findPositionForRenderIndex(finalPosition));
  100. }
  101. }
  102. }
  103. };
  104. grid.api.registerEventsFromObject(publicApi.events);
  105. grid.api.registerMethodsFromObject(publicApi.methods);
  106. },
  107. defaultGridOptions: function (gridOptions) {
  108. /**
  109. * @ngdoc object
  110. * @name ui.grid.moveColumns.api:GridOptions
  111. *
  112. * @description Options for configuring the move column feature, these are available to be
  113. * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
  114. */
  115. /**
  116. * @ngdoc object
  117. * @name enableColumnMoving
  118. * @propertyOf ui.grid.moveColumns.api:GridOptions
  119. * @description If defined, sets the default value for the colMovable flag on each individual colDefs
  120. * if their individual enableColumnMoving configuration is not defined. Defaults to true.
  121. */
  122. gridOptions.enableColumnMoving = gridOptions.enableColumnMoving !== false;
  123. },
  124. movableColumnBuilder: function (colDef, col, gridOptions) {
  125. var promises = [];
  126. /**
  127. * @ngdoc object
  128. * @name ui.grid.moveColumns.api:ColumnDef
  129. *
  130. * @description Column Definition for move column feature, these are available to be
  131. * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
  132. */
  133. /**
  134. * @ngdoc object
  135. * @name enableColumnMoving
  136. * @propertyOf ui.grid.moveColumns.api:ColumnDef
  137. * @description Enable column moving for the column.
  138. */
  139. colDef.enableColumnMoving = colDef.enableColumnMoving === undefined ? gridOptions.enableColumnMoving
  140. : colDef.enableColumnMoving;
  141. return $q.all(promises);
  142. },
  143. /**
  144. * @ngdoc method
  145. * @name updateColumnCache
  146. * @methodOf ui.grid.moveColumns
  147. * @description Cache the current order of columns, so we can restore them after new columnDefs are defined
  148. */
  149. updateColumnCache: function(grid){
  150. grid.moveColumns.orderCache = grid.getOnlyDataColumns();
  151. },
  152. /**
  153. * @ngdoc method
  154. * @name verifyColumnOrder
  155. * @methodOf ui.grid.moveColumns
  156. * @description dataChangeCallback which uses the cached column order to restore the column order
  157. * when it is reset by altering the columnDefs array.
  158. */
  159. verifyColumnOrder: function(grid){
  160. var headerRowOffset = grid.rowHeaderColumns.length;
  161. var newIndex;
  162. angular.forEach(grid.moveColumns.orderCache, function(cacheCol, cacheIndex){
  163. newIndex = grid.columns.indexOf(cacheCol);
  164. if ( newIndex !== -1 && newIndex - headerRowOffset !== cacheIndex ){
  165. var column = grid.columns.splice(newIndex, 1)[0];
  166. grid.columns.splice(cacheIndex + headerRowOffset, 0, column);
  167. }
  168. });
  169. },
  170. redrawColumnAtPosition: function (grid, originalPosition, newPosition) {
  171. var columns = grid.columns;
  172. if (originalPosition === newPosition) {
  173. return;
  174. }
  175. //check columns in between move-range to make sure they are visible columns
  176. var pos = (originalPosition < newPosition) ? originalPosition + 1 : originalPosition - 1;
  177. var i0 = Math.min(pos, newPosition);
  178. for (i0; i0 <= Math.max(pos, newPosition); i0++) {
  179. if (columns[i0].visible) {
  180. break;
  181. }
  182. }
  183. if (i0 > Math.max(pos, newPosition)) {
  184. //no visible column found, column did not visibly move
  185. return;
  186. }
  187. var originalColumn = columns[originalPosition];
  188. if (originalColumn.colDef.enableColumnMoving) {
  189. if (originalPosition > newPosition) {
  190. for (var i1 = originalPosition; i1 > newPosition; i1--) {
  191. columns[i1] = columns[i1 - 1];
  192. }
  193. }
  194. else if (newPosition > originalPosition) {
  195. for (var i2 = originalPosition; i2 < newPosition; i2++) {
  196. columns[i2] = columns[i2 + 1];
  197. }
  198. }
  199. columns[newPosition] = originalColumn;
  200. service.updateColumnCache(grid);
  201. grid.queueGridRefresh();
  202. $rootScope.$applyAsync(function () {
  203. grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
  204. grid.api.colMovable.raise.columnPositionChanged(originalColumn.colDef, originalPosition, newPosition);
  205. });
  206. }
  207. }
  208. };
  209. return service;
  210. }]);
  211. /**
  212. * @ngdoc directive
  213. * @name ui.grid.moveColumns.directive:uiGridMoveColumns
  214. * @element div
  215. * @restrict A
  216. * @description Adds column moving features to the ui-grid directive.
  217. * @example
  218. <example module="app">
  219. <file name="app.js">
  220. var app = angular.module('app', ['ui.grid', 'ui.grid.moveColumns']);
  221. app.controller('MainCtrl', ['$scope', function ($scope) {
  222. $scope.data = [
  223. { name: 'Bob', title: 'CEO', age: 45 },
  224. { name: 'Frank', title: 'Lowly Developer', age: 25 },
  225. { name: 'Jenny', title: 'Highly Developer', age: 35 }
  226. ];
  227. $scope.columnDefs = [
  228. {name: 'name'},
  229. {name: 'title'},
  230. {name: 'age'}
  231. ];
  232. }]);
  233. </file>
  234. <file name="main.css">
  235. .grid {
  236. width: 100%;
  237. height: 150px;
  238. }
  239. </file>
  240. <file name="index.html">
  241. <div ng-controller="MainCtrl">
  242. <div class="grid" ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-move-columns></div>
  243. </div>
  244. </file>
  245. </example>
  246. */
  247. module.directive('uiGridMoveColumns', ['uiGridMoveColumnService', function (uiGridMoveColumnService) {
  248. return {
  249. replace: true,
  250. priority: 0,
  251. require: '^uiGrid',
  252. scope: false,
  253. compile: function () {
  254. return {
  255. pre: function ($scope, $elm, $attrs, uiGridCtrl) {
  256. uiGridMoveColumnService.initializeGrid(uiGridCtrl.grid);
  257. },
  258. post: function ($scope, $elm, $attrs, uiGridCtrl) {
  259. }
  260. };
  261. }
  262. };
  263. }]);
  264. /**
  265. * @ngdoc directive
  266. * @name ui.grid.moveColumns.directive:uiGridHeaderCell
  267. * @element div
  268. * @restrict A
  269. *
  270. * @description Stacks on top of ui.grid.uiGridHeaderCell to provide capability to be able to move it to reposition column.
  271. *
  272. * On receiving mouseDown event headerCell is cloned, now as the mouse moves the cloned header cell also moved in the grid.
  273. * In case the moving cloned header cell reaches the left or right extreme of grid, grid scrolling is triggered (if horizontal scroll exists).
  274. * On mouseUp event column is repositioned at position where mouse is released and cloned header cell is removed.
  275. *
  276. * Events that invoke cloning of header cell:
  277. * - mousedown
  278. *
  279. * Events that invoke movement of cloned header cell:
  280. * - mousemove
  281. *
  282. * Events that invoke repositioning of column:
  283. * - mouseup
  284. */
  285. module.directive('uiGridHeaderCell', ['$q', 'gridUtil', 'uiGridMoveColumnService', '$document', '$log', 'uiGridConstants', 'ScrollEvent',
  286. function ($q, gridUtil, uiGridMoveColumnService, $document, $log, uiGridConstants, ScrollEvent) {
  287. return {
  288. priority: -10,
  289. require: '^uiGrid',
  290. compile: function () {
  291. return {
  292. post: function ($scope, $elm, $attrs, uiGridCtrl) {
  293. if ($scope.col.colDef.enableColumnMoving) {
  294. /*
  295. * Our general approach to column move is that we listen to a touchstart or mousedown
  296. * event over the column header. When we hear one, then we wait for a move of the same type
  297. * - if we are a touchstart then we listen for a touchmove, if we are a mousedown we listen for
  298. * a mousemove (i.e. a drag) before we decide that there's a move underway. If there's never a move,
  299. * and we instead get a mouseup or a touchend, then we just drop out again and do nothing.
  300. *
  301. */
  302. var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
  303. var gridLeft;
  304. var previousMouseX;
  305. var totalMouseMovement;
  306. var rightMoveLimit;
  307. var elmCloned = false;
  308. var movingElm;
  309. var reducedWidth;
  310. var moveOccurred = false;
  311. var downFn = function( event ){
  312. //Setting some variables required for calculations.
  313. gridLeft = $scope.grid.element[0].getBoundingClientRect().left;
  314. if ( $scope.grid.hasLeftContainer() ){
  315. gridLeft += $scope.grid.renderContainers.left.header[0].getBoundingClientRect().width;
  316. }
  317. previousMouseX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
  318. totalMouseMovement = 0;
  319. rightMoveLimit = gridLeft + $scope.grid.getViewportWidth();
  320. if ( event.type === 'mousedown' ){
  321. $document.on('mousemove', moveFn);
  322. $document.on('mouseup', upFn);
  323. } else if ( event.type === 'touchstart' ){
  324. $document.on('touchmove', moveFn);
  325. $document.on('touchend', upFn);
  326. }
  327. };
  328. var moveFn = function( event ) {
  329. var pageX = event.pageX || (event.originalEvent ? event.originalEvent.pageX : 0);
  330. var changeValue = pageX - previousMouseX;
  331. if ( changeValue === 0 ){ return; }
  332. //Disable text selection in Chrome during column move
  333. document.onselectstart = function() { return false; };
  334. moveOccurred = true;
  335. if (!elmCloned) {
  336. cloneElement();
  337. }
  338. else if (elmCloned) {
  339. moveElement(changeValue);
  340. previousMouseX = pageX;
  341. }
  342. };
  343. var upFn = function( event ){
  344. //Re-enable text selection after column move
  345. document.onselectstart = null;
  346. //Remove the cloned element on mouse up.
  347. if (movingElm) {
  348. movingElm.remove();
  349. elmCloned = false;
  350. }
  351. offAllEvents();
  352. onDownEvents();
  353. if (!moveOccurred){
  354. return;
  355. }
  356. var columns = $scope.grid.columns;
  357. var columnIndex = 0;
  358. for (var i = 0; i < columns.length; i++) {
  359. if (columns[i].colDef.name !== $scope.col.colDef.name) {
  360. columnIndex++;
  361. }
  362. else {
  363. break;
  364. }
  365. }
  366. var targetIndex;
  367. //Case where column should be moved to a position on its left
  368. if (totalMouseMovement < 0) {
  369. var totalColumnsLeftWidth = 0;
  370. var il;
  371. if ( $scope.grid.isRTL() ){
  372. for (il = columnIndex + 1; il < columns.length; il++) {
  373. if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
  374. totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
  375. if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
  376. uiGridMoveColumnService.redrawColumnAtPosition
  377. ($scope.grid, columnIndex, il - 1);
  378. break;
  379. }
  380. }
  381. }
  382. }
  383. else {
  384. for (il = columnIndex - 1; il >= 0; il--) {
  385. if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
  386. totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
  387. if (totalColumnsLeftWidth > Math.abs(totalMouseMovement)) {
  388. uiGridMoveColumnService.redrawColumnAtPosition
  389. ($scope.grid, columnIndex, il + 1);
  390. break;
  391. }
  392. }
  393. }
  394. }
  395. //Case where column should be moved to beginning (or end in RTL) of the grid.
  396. if (totalColumnsLeftWidth < Math.abs(totalMouseMovement)) {
  397. targetIndex = 0;
  398. if ( $scope.grid.isRTL() ){
  399. targetIndex = columns.length - 1;
  400. }
  401. uiGridMoveColumnService.redrawColumnAtPosition
  402. ($scope.grid, columnIndex, targetIndex);
  403. }
  404. }
  405. //Case where column should be moved to a position on its right
  406. else if (totalMouseMovement > 0) {
  407. var totalColumnsRightWidth = 0;
  408. var ir;
  409. if ( $scope.grid.isRTL() ){
  410. for (ir = columnIndex - 1; ir > 0; ir--) {
  411. if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
  412. totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
  413. if (totalColumnsRightWidth > totalMouseMovement) {
  414. uiGridMoveColumnService.redrawColumnAtPosition
  415. ($scope.grid, columnIndex, ir);
  416. break;
  417. }
  418. }
  419. }
  420. }
  421. else {
  422. for (ir = columnIndex + 1; ir < columns.length; ir++) {
  423. if (angular.isUndefined(columns[ir].colDef.visible) || columns[ir].colDef.visible === true) {
  424. totalColumnsRightWidth += columns[ir].drawnWidth || columns[ir].width || columns[ir].colDef.width;
  425. if (totalColumnsRightWidth > totalMouseMovement) {
  426. uiGridMoveColumnService.redrawColumnAtPosition
  427. ($scope.grid, columnIndex, ir - 1);
  428. break;
  429. }
  430. }
  431. }
  432. }
  433. //Case where column should be moved to end (or beginning in RTL) of the grid.
  434. if (totalColumnsRightWidth < totalMouseMovement) {
  435. targetIndex = columns.length - 1;
  436. if ( $scope.grid.isRTL() ){
  437. targetIndex = 0;
  438. }
  439. uiGridMoveColumnService.redrawColumnAtPosition
  440. ($scope.grid, columnIndex, targetIndex);
  441. }
  442. }
  443. };
  444. var onDownEvents = function(){
  445. $contentsElm.on('touchstart', downFn);
  446. $contentsElm.on('mousedown', downFn);
  447. };
  448. var offAllEvents = function() {
  449. $contentsElm.off('touchstart', downFn);
  450. $contentsElm.off('mousedown', downFn);
  451. $document.off('mousemove', moveFn);
  452. $document.off('touchmove', moveFn);
  453. $document.off('mouseup', upFn);
  454. $document.off('touchend', upFn);
  455. };
  456. onDownEvents();
  457. var cloneElement = function () {
  458. elmCloned = true;
  459. //Cloning header cell and appending to current header cell.
  460. movingElm = $elm.clone();
  461. $elm.parent().append(movingElm);
  462. //Left of cloned element should be aligned to original header cell.
  463. movingElm.addClass('movingColumn');
  464. var movingElementStyles = {};
  465. movingElementStyles.left = $elm[0].offsetLeft + 'px';
  466. var gridRight = $scope.grid.element[0].getBoundingClientRect().right;
  467. var elmRight = $elm[0].getBoundingClientRect().right;
  468. if (elmRight > gridRight) {
  469. reducedWidth = $scope.col.drawnWidth + (gridRight - elmRight);
  470. movingElementStyles.width = reducedWidth + 'px';
  471. }
  472. movingElm.css(movingElementStyles);
  473. };
  474. var moveElement = function (changeValue) {
  475. //Calculate total column width
  476. var columns = $scope.grid.columns;
  477. var totalColumnWidth = 0;
  478. for (var i = 0; i < columns.length; i++) {
  479. if (angular.isUndefined(columns[i].colDef.visible) || columns[i].colDef.visible === true) {
  480. totalColumnWidth += columns[i].drawnWidth || columns[i].width || columns[i].colDef.width;
  481. }
  482. }
  483. //Calculate new position of left of column
  484. var currentElmLeft = movingElm[0].getBoundingClientRect().left - 1;
  485. var currentElmRight = movingElm[0].getBoundingClientRect().right;
  486. var newElementLeft;
  487. newElementLeft = currentElmLeft - gridLeft + changeValue;
  488. newElementLeft = newElementLeft < rightMoveLimit ? newElementLeft : rightMoveLimit;
  489. //Update css of moving column to adjust to new left value or fire scroll in case column has reached edge of grid
  490. if ((currentElmLeft >= gridLeft || changeValue > 0) && (currentElmRight <= rightMoveLimit || changeValue < 0)) {
  491. movingElm.css({visibility: 'visible', 'left': (movingElm[0].offsetLeft +
  492. (newElementLeft < rightMoveLimit ? changeValue : (rightMoveLimit - currentElmLeft))) + 'px'});
  493. }
  494. else if (totalColumnWidth > Math.ceil(uiGridCtrl.grid.gridWidth)) {
  495. changeValue *= 8;
  496. var scrollEvent = new ScrollEvent($scope.col.grid, null, null, 'uiGridHeaderCell.moveElement');
  497. scrollEvent.x = {pixels: changeValue};
  498. scrollEvent.grid.scrollContainers('',scrollEvent);
  499. }
  500. //Calculate total width of columns on the left of the moving column and the mouse movement
  501. var totalColumnsLeftWidth = 0;
  502. for (var il = 0; il < columns.length; il++) {
  503. if (angular.isUndefined(columns[il].colDef.visible) || columns[il].colDef.visible === true) {
  504. if (columns[il].colDef.name !== $scope.col.colDef.name) {
  505. totalColumnsLeftWidth += columns[il].drawnWidth || columns[il].width || columns[il].colDef.width;
  506. }
  507. else {
  508. break;
  509. }
  510. }
  511. }
  512. if ($scope.newScrollLeft === undefined) {
  513. totalMouseMovement += changeValue;
  514. }
  515. else {
  516. totalMouseMovement = $scope.newScrollLeft + newElementLeft - totalColumnsLeftWidth;
  517. }
  518. //Increase width of moving column, in case the rightmost column was moved and its width was
  519. //decreased because of overflow
  520. if (reducedWidth < $scope.col.drawnWidth) {
  521. reducedWidth += Math.abs(changeValue);
  522. movingElm.css({'width': reducedWidth + 'px'});
  523. }
  524. };
  525. $scope.$on('$destroy', offAllEvents);
  526. }
  527. }
  528. };
  529. }
  530. };
  531. }]);
  532. })();