customBorders.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. 'use strict';
  2. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
  3. var _pluginHooks = require('./../../pluginHooks');
  4. var _pluginHooks2 = _interopRequireDefault(_pluginHooks);
  5. var _plugins = require('./../../plugins');
  6. var _object = require('./../../helpers/object');
  7. var _src = require('./../../3rdparty/walkontable/src');
  8. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  9. function CustomBorders() {}
  10. /** *
  11. * Current instance (table where borders should be placed)
  12. */
  13. var instance;
  14. /**
  15. * This plugin enables an option to apply custom borders through the context menu (configurable with context menu key `borders`).
  16. *
  17. * To initialize Handsontable with predefined custom borders, provide cell coordinates and border styles in a form of an array.
  18. *
  19. * See [Custom Borders](http://docs.handsontable.com/demo-custom-borders.html) demo for more examples.
  20. *
  21. * @example
  22. * ```js
  23. * ...
  24. * customBorders: [
  25. * {range: {
  26. * from: {row: 1, col: 1},
  27. * to: {row: 3, col: 4}},
  28. * left: {},
  29. * right: {},
  30. * top: {},
  31. * bottom: {}
  32. * }
  33. * ],
  34. * ...
  35. *
  36. * // or
  37. * ...
  38. * customBorders: [
  39. * {row: 2, col: 2, left: {width: 2, color: 'red'},
  40. * right: {width: 1, color: 'green'}, top: '', bottom: ''}
  41. * ],
  42. * ...
  43. * ```
  44. * @private
  45. * @class CustomBorders
  46. * @plugin CustomBorders
  47. */
  48. /** *
  49. * Check if plugin should be enabled.
  50. */
  51. var checkEnable = function checkEnable(customBorders) {
  52. if (typeof customBorders === 'boolean') {
  53. if (customBorders === true) {
  54. return true;
  55. }
  56. }
  57. if ((typeof customBorders === 'undefined' ? 'undefined' : _typeof(customBorders)) === 'object') {
  58. if (customBorders.length > 0) {
  59. return true;
  60. }
  61. }
  62. return false;
  63. };
  64. /** *
  65. * Initialize plugin.
  66. */
  67. var init = function init() {
  68. if (checkEnable(this.getSettings().customBorders)) {
  69. if (!this.customBorders) {
  70. instance = this;
  71. this.customBorders = new CustomBorders();
  72. }
  73. }
  74. };
  75. /** *
  76. * Get index of border from the settings.
  77. *
  78. * @param {String} className
  79. * @returns {Number}
  80. */
  81. var getSettingIndex = function getSettingIndex(className) {
  82. for (var i = 0; i < instance.view.wt.selections.length; i++) {
  83. if (instance.view.wt.selections[i].settings.className == className) {
  84. return i;
  85. }
  86. }
  87. return -1;
  88. };
  89. /** *
  90. * Insert WalkontableSelection instance into Walkontable settings.
  91. *
  92. * @param border
  93. */
  94. var insertBorderIntoSettings = function insertBorderIntoSettings(border) {
  95. var coordinates = {
  96. row: border.row,
  97. col: border.col
  98. };
  99. var selection = new _src.Selection(border, new _src.CellRange(coordinates, coordinates, coordinates));
  100. var index = getSettingIndex(border.className);
  101. if (index >= 0) {
  102. instance.view.wt.selections[index] = selection;
  103. } else {
  104. instance.view.wt.selections.push(selection);
  105. }
  106. };
  107. /** *
  108. * Prepare borders from setting (single cell).
  109. *
  110. * @param {Number} row Row index.
  111. * @param {Number} col Column index.
  112. * @param borderObj
  113. */
  114. var prepareBorderFromCustomAdded = function prepareBorderFromCustomAdded(row, col, borderObj) {
  115. var border = createEmptyBorders(row, col);
  116. border = extendDefaultBorder(border, borderObj);
  117. this.setCellMeta(row, col, 'borders', border);
  118. insertBorderIntoSettings(border);
  119. };
  120. /** *
  121. * Prepare borders from setting (object).
  122. *
  123. * @param {Object} rowObj
  124. */
  125. var prepareBorderFromCustomAddedRange = function prepareBorderFromCustomAddedRange(rowObj) {
  126. var range = rowObj.range;
  127. for (var row = range.from.row; row <= range.to.row; row++) {
  128. for (var col = range.from.col; col <= range.to.col; col++) {
  129. var border = createEmptyBorders(row, col);
  130. var add = 0;
  131. if (row == range.from.row) {
  132. add++;
  133. if ((0, _object.hasOwnProperty)(rowObj, 'top')) {
  134. border.top = rowObj.top;
  135. }
  136. }
  137. if (row == range.to.row) {
  138. add++;
  139. if ((0, _object.hasOwnProperty)(rowObj, 'bottom')) {
  140. border.bottom = rowObj.bottom;
  141. }
  142. }
  143. if (col == range.from.col) {
  144. add++;
  145. if ((0, _object.hasOwnProperty)(rowObj, 'left')) {
  146. border.left = rowObj.left;
  147. }
  148. }
  149. if (col == range.to.col) {
  150. add++;
  151. if ((0, _object.hasOwnProperty)(rowObj, 'right')) {
  152. border.right = rowObj.right;
  153. }
  154. }
  155. if (add > 0) {
  156. this.setCellMeta(row, col, 'borders', border);
  157. insertBorderIntoSettings(border);
  158. }
  159. }
  160. }
  161. };
  162. /** *
  163. * Create separated class name for borders for each cell.
  164. *
  165. * @param {Number} row Row index.
  166. * @param {Number} col Column index.
  167. * @returns {String}
  168. */
  169. var createClassName = function createClassName(row, col) {
  170. return 'border_row' + row + 'col' + col;
  171. };
  172. /** *
  173. * Create default single border for each position (top/right/bottom/left).
  174. *
  175. * @returns {Object} `{{width: number, color: string}}`
  176. */
  177. var createDefaultCustomBorder = function createDefaultCustomBorder() {
  178. return {
  179. width: 1,
  180. color: '#000'
  181. };
  182. };
  183. /** *
  184. * Create default object for empty border.
  185. *
  186. * @returns {Object} `{{hide: boolean}}`
  187. */
  188. var createSingleEmptyBorder = function createSingleEmptyBorder() {
  189. return {
  190. hide: true
  191. };
  192. };
  193. /** *
  194. * Create default Handsontable border object.
  195. *
  196. * @returns {Object} `{{width: number, color: string, cornerVisible: boolean}}`
  197. */
  198. var createDefaultHtBorder = function createDefaultHtBorder() {
  199. return {
  200. width: 1,
  201. color: '#000',
  202. cornerVisible: false
  203. };
  204. };
  205. /** *
  206. * Prepare empty border for each cell with all custom borders hidden.
  207. *
  208. * @param {Number} row Row index.
  209. * @param {Number} col Column index.
  210. * @returns {Object} `{{className: *, border: *, row: *, col: *, top: {hide: boolean}, right: {hide: boolean}, bottom: {hide: boolean}, left: {hide: boolean}}}`
  211. */
  212. var createEmptyBorders = function createEmptyBorders(row, col) {
  213. return {
  214. className: createClassName(row, col),
  215. border: createDefaultHtBorder(),
  216. row: row,
  217. col: col,
  218. top: createSingleEmptyBorder(),
  219. right: createSingleEmptyBorder(),
  220. bottom: createSingleEmptyBorder(),
  221. left: createSingleEmptyBorder()
  222. };
  223. };
  224. var extendDefaultBorder = function extendDefaultBorder(defaultBorder, customBorder) {
  225. if ((0, _object.hasOwnProperty)(customBorder, 'border')) {
  226. defaultBorder.border = customBorder.border;
  227. }
  228. if ((0, _object.hasOwnProperty)(customBorder, 'top')) {
  229. defaultBorder.top = customBorder.top;
  230. }
  231. if ((0, _object.hasOwnProperty)(customBorder, 'right')) {
  232. defaultBorder.right = customBorder.right;
  233. }
  234. if ((0, _object.hasOwnProperty)(customBorder, 'bottom')) {
  235. defaultBorder.bottom = customBorder.bottom;
  236. }
  237. if ((0, _object.hasOwnProperty)(customBorder, 'left')) {
  238. defaultBorder.left = customBorder.left;
  239. }
  240. return defaultBorder;
  241. };
  242. /**
  243. * Remove borders divs from DOM.
  244. *
  245. * @param borderClassName
  246. */
  247. var removeBordersFromDom = function removeBordersFromDom(borderClassName) {
  248. var borders = document.querySelectorAll('.' + borderClassName);
  249. for (var i = 0; i < borders.length; i++) {
  250. if (borders[i]) {
  251. if (borders[i].nodeName != 'TD') {
  252. var parent = borders[i].parentNode;
  253. if (parent.parentNode) {
  254. parent.parentNode.removeChild(parent);
  255. }
  256. }
  257. }
  258. }
  259. };
  260. /** *
  261. * Remove border (triggered from context menu).
  262. *
  263. * @param {Number} row Row index.
  264. * @param {Number} col Column index.
  265. */
  266. var removeAllBorders = function removeAllBorders(row, col) {
  267. var borderClassName = createClassName(row, col);
  268. removeBordersFromDom(borderClassName);
  269. this.removeCellMeta(row, col, 'borders');
  270. };
  271. /** *
  272. * Set borders for each cell re. to border position
  273. *
  274. * @param row
  275. * @param col
  276. * @param place
  277. * @param remove
  278. */
  279. var setBorder = function setBorder(row, col, place, remove) {
  280. var bordersMeta = this.getCellMeta(row, col).borders;
  281. if (!bordersMeta || bordersMeta.border == undefined) {
  282. bordersMeta = createEmptyBorders(row, col);
  283. }
  284. if (remove) {
  285. bordersMeta[place] = createSingleEmptyBorder();
  286. } else {
  287. bordersMeta[place] = createDefaultCustomBorder();
  288. }
  289. this.setCellMeta(row, col, 'borders', bordersMeta);
  290. var borderClassName = createClassName(row, col);
  291. removeBordersFromDom(borderClassName);
  292. insertBorderIntoSettings(bordersMeta);
  293. this.render();
  294. };
  295. /** *
  296. * Prepare borders based on cell and border position
  297. *
  298. * @param range
  299. * @param place
  300. * @param remove
  301. */
  302. var prepareBorder = function prepareBorder(range, place, remove) {
  303. if (range.from.row == range.to.row && range.from.col == range.to.col) {
  304. if (place == 'noBorders') {
  305. removeAllBorders.call(this, range.from.row, range.from.col);
  306. } else {
  307. setBorder.call(this, range.from.row, range.from.col, place, remove);
  308. }
  309. } else {
  310. switch (place) {
  311. case 'noBorders':
  312. for (var column = range.from.col; column <= range.to.col; column++) {
  313. for (var row = range.from.row; row <= range.to.row; row++) {
  314. removeAllBorders.call(this, row, column);
  315. }
  316. }
  317. break;
  318. case 'top':
  319. for (var topCol = range.from.col; topCol <= range.to.col; topCol++) {
  320. setBorder.call(this, range.from.row, topCol, place, remove);
  321. }
  322. break;
  323. case 'right':
  324. for (var rowRight = range.from.row; rowRight <= range.to.row; rowRight++) {
  325. setBorder.call(this, rowRight, range.to.col, place);
  326. }
  327. break;
  328. case 'bottom':
  329. for (var bottomCol = range.from.col; bottomCol <= range.to.col; bottomCol++) {
  330. setBorder.call(this, range.to.row, bottomCol, place);
  331. }
  332. break;
  333. case 'left':
  334. for (var rowLeft = range.from.row; rowLeft <= range.to.row; rowLeft++) {
  335. setBorder.call(this, rowLeft, range.from.col, place);
  336. }
  337. break;
  338. default:
  339. break;
  340. }
  341. }
  342. };
  343. /** *
  344. * Check if selection has border by className
  345. *
  346. * @param hot
  347. * @param direction
  348. */
  349. var checkSelectionBorders = function checkSelectionBorders(hot, direction) {
  350. var atLeastOneHasBorder = false;
  351. hot.getSelectedRange().forAll(function (r, c) {
  352. var metaBorders = hot.getCellMeta(r, c).borders;
  353. if (metaBorders) {
  354. if (direction) {
  355. if (!(0, _object.hasOwnProperty)(metaBorders[direction], 'hide')) {
  356. atLeastOneHasBorder = true;
  357. return false; // breaks forAll
  358. }
  359. } else {
  360. atLeastOneHasBorder = true;
  361. return false; // breaks forAll
  362. }
  363. }
  364. });
  365. return atLeastOneHasBorder;
  366. };
  367. /** *
  368. * Mark label in contextMenu as selected
  369. *
  370. * @param label
  371. * @returns {string}
  372. */
  373. var markSelected = function markSelected(label) {
  374. return '<span class="selected">' + String.fromCharCode(10003) + '</span>' + label; // workaround for https://github.com/handsontable/handsontable/issues/1946
  375. };
  376. /** *
  377. * Add border options to context menu
  378. *
  379. * @param defaultOptions
  380. */
  381. var addBordersOptionsToContextMenu = function addBordersOptionsToContextMenu(defaultOptions) {
  382. if (!this.getSettings().customBorders) {
  383. return;
  384. }
  385. defaultOptions.items.push({
  386. name: '---------'
  387. });
  388. defaultOptions.items.push({
  389. key: 'borders',
  390. name: 'Borders',
  391. disabled: function disabled() {
  392. return this.selection.selectedHeader.corner;
  393. },
  394. submenu: {
  395. items: [{
  396. key: 'borders:top',
  397. name: function name() {
  398. var label = 'Top';
  399. var hasBorder = checkSelectionBorders(this, 'top');
  400. if (hasBorder) {
  401. label = markSelected(label);
  402. }
  403. return label;
  404. },
  405. callback: function callback() {
  406. var hasBorder = checkSelectionBorders(this, 'top');
  407. prepareBorder.call(this, this.getSelectedRange(), 'top', hasBorder);
  408. }
  409. }, {
  410. key: 'borders:right',
  411. name: function name() {
  412. var label = 'Right';
  413. var hasBorder = checkSelectionBorders(this, 'right');
  414. if (hasBorder) {
  415. label = markSelected(label);
  416. }
  417. return label;
  418. },
  419. callback: function callback() {
  420. var hasBorder = checkSelectionBorders(this, 'right');
  421. prepareBorder.call(this, this.getSelectedRange(), 'right', hasBorder);
  422. }
  423. }, {
  424. key: 'borders:bottom',
  425. name: function name() {
  426. var label = 'Bottom';
  427. var hasBorder = checkSelectionBorders(this, 'bottom');
  428. if (hasBorder) {
  429. label = markSelected(label);
  430. }
  431. return label;
  432. },
  433. callback: function callback() {
  434. var hasBorder = checkSelectionBorders(this, 'bottom');
  435. prepareBorder.call(this, this.getSelectedRange(), 'bottom', hasBorder);
  436. }
  437. }, {
  438. key: 'borders:left',
  439. name: function name() {
  440. var label = 'Left';
  441. var hasBorder = checkSelectionBorders(this, 'left');
  442. if (hasBorder) {
  443. label = markSelected(label);
  444. }
  445. return label;
  446. },
  447. callback: function callback() {
  448. var hasBorder = checkSelectionBorders(this, 'left');
  449. prepareBorder.call(this, this.getSelectedRange(), 'left', hasBorder);
  450. }
  451. }, {
  452. key: 'borders:no_borders',
  453. name: 'Remove border(s)',
  454. callback: function callback() {
  455. prepareBorder.call(this, this.getSelectedRange(), 'noBorders');
  456. },
  457. disabled: function disabled() {
  458. return !checkSelectionBorders(this);
  459. }
  460. }]
  461. }
  462. });
  463. };
  464. _pluginHooks2.default.getSingleton().add('beforeInit', init);
  465. _pluginHooks2.default.getSingleton().add('afterContextMenuDefaultOptions', addBordersOptionsToContextMenu);
  466. _pluginHooks2.default.getSingleton().add('afterInit', function () {
  467. var customBorders = this.getSettings().customBorders;
  468. if (customBorders) {
  469. for (var i = 0; i < customBorders.length; i++) {
  470. if (customBorders[i].range) {
  471. prepareBorderFromCustomAddedRange.call(this, customBorders[i]);
  472. } else {
  473. prepareBorderFromCustomAdded.call(this, customBorders[i].row, customBorders[i].col, customBorders[i]);
  474. }
  475. }
  476. this.render();
  477. this.view.wt.draw(true);
  478. }
  479. });