customBorders.js 14 KB

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