You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1164 lines
36 KiB

  1. wysihtml.commands.addTableCells = {
  2. exec: function(composer, command, value) {
  3. if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
  4. // switches start and end if start is bigger than end (reverse selection)
  5. var tableSelect = wysihtml.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end);
  6. if (value == 'before' || value == 'above') {
  7. wysihtml.dom.table.addCells(tableSelect.start, value);
  8. } else if (value == 'after' || value == 'below') {
  9. wysihtml.dom.table.addCells(tableSelect.end, value);
  10. }
  11. setTimeout(function() {
  12. composer.tableSelection.select(tableSelect.start, tableSelect.end);
  13. },0);
  14. }
  15. },
  16. state: function(composer, command) {
  17. return false;
  18. }
  19. };
  20. wysihtml.commands.createTable = {
  21. exec: function(composer, command, value) {
  22. var col, row, html;
  23. if (value && value.cols && value.rows && parseInt(value.cols, 10) > 0 && parseInt(value.rows, 10) > 0) {
  24. if (value.tableStyle) {
  25. html = '<table style="' + value.tableStyle + '">';
  26. } else {
  27. html = '<table>';
  28. }
  29. html += '<tbody>';
  30. for (row = 0; row < value.rows; row ++) {
  31. html += '<tr>';
  32. for (col = 0; col < value.cols; col ++) {
  33. html += '<td><br></td>';
  34. }
  35. html += '</tr>';
  36. }
  37. html += '</tbody></table>';
  38. composer.commands.exec('insertHTML', html);
  39. }
  40. },
  41. state: function(composer, command) {
  42. return false;
  43. }
  44. };
  45. wysihtml.commands.deleteTableCells = {
  46. exec: function(composer, command, value) {
  47. if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
  48. var tableSelect = wysihtml.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end),
  49. idx = wysihtml.dom.table.indexOf(tableSelect.start),
  50. selCell,
  51. table = composer.tableSelection.table;
  52. wysihtml.dom.table.removeCells(tableSelect.start, value);
  53. setTimeout(function() {
  54. // move selection to next or previous if not present
  55. selCell = wysihtml.dom.table.findCell(table, idx);
  56. if (!selCell) {
  57. if (value == 'row') {
  58. selCell = wysihtml.dom.table.findCell(table, {
  59. 'row': idx.row - 1,
  60. 'col': idx.col
  61. });
  62. }
  63. if (value == 'column') {
  64. selCell = wysihtml.dom.table.findCell(table, {
  65. 'row': idx.row,
  66. 'col': idx.col - 1
  67. });
  68. }
  69. }
  70. if (selCell) {
  71. composer.tableSelection.select(selCell, selCell);
  72. }
  73. }, 0);
  74. }
  75. },
  76. state: function(composer, command) {
  77. return false;
  78. }
  79. };
  80. wysihtml.commands.mergeTableCells = {
  81. exec: function(composer, command) {
  82. if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
  83. if (this.state(composer, command)) {
  84. wysihtml.dom.table.unmergeCell(composer.tableSelection.start);
  85. } else {
  86. wysihtml.dom.table.mergeCellsBetween(composer.tableSelection.start, composer.tableSelection.end);
  87. }
  88. }
  89. },
  90. state: function(composer, command) {
  91. if (composer.tableSelection) {
  92. var start = composer.tableSelection.start,
  93. end = composer.tableSelection.end;
  94. if (start && end && start == end &&
  95. ((
  96. wysihtml.dom.getAttribute(start, 'colspan') &&
  97. parseInt(wysihtml.dom.getAttribute(start, 'colspan'), 10) > 1
  98. ) || (
  99. wysihtml.dom.getAttribute(start, 'rowspan') &&
  100. parseInt(wysihtml.dom.getAttribute(start, 'rowspan'), 10) > 1
  101. ))
  102. ) {
  103. return [start];
  104. }
  105. }
  106. return false;
  107. }
  108. };
  109. (function() {
  110. var api = wysihtml.dom;
  111. var MapCell = function(cell) {
  112. this.el = cell;
  113. this.isColspan= false;
  114. this.isRowspan= false;
  115. this.firstCol= true;
  116. this.lastCol= true;
  117. this.firstRow= true;
  118. this.lastRow= true;
  119. this.isReal= true;
  120. this.spanCollection= [];
  121. this.modified = false;
  122. };
  123. var TableModifyerByCell = function (cell, table) {
  124. if (cell) {
  125. this.cell = cell;
  126. this.table = api.getParentElement(cell, { query: "table" });
  127. } else if (table) {
  128. this.table = table;
  129. this.cell = this.table.querySelectorAll('th, td')[0];
  130. }
  131. };
  132. function queryInList(list, query) {
  133. var ret = [],
  134. q;
  135. for (var e = 0, len = list.length; e < len; e++) {
  136. q = list[e].querySelectorAll(query);
  137. if (q) {
  138. for(var i = q.length; i--; ret.unshift(q[i]));
  139. }
  140. }
  141. return ret;
  142. }
  143. function removeElement(el) {
  144. el.parentNode.removeChild(el);
  145. }
  146. function insertAfter(referenceNode, newNode) {
  147. referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  148. }
  149. function nextNode(node, tag) {
  150. var element = node.nextSibling;
  151. while (element.nodeType !=1) {
  152. element = element.nextSibling;
  153. if (!tag || tag == element.tagName.toLowerCase()) {
  154. return element;
  155. }
  156. }
  157. return null;
  158. }
  159. TableModifyerByCell.prototype = {
  160. addSpannedCellToMap: function(cell, map, r, c, cspan, rspan) {
  161. var spanCollect = [],
  162. rmax = r + ((rspan) ? parseInt(rspan, 10) - 1 : 0),
  163. cmax = c + ((cspan) ? parseInt(cspan, 10) - 1 : 0);
  164. for (var rr = r; rr <= rmax; rr++) {
  165. if (typeof map[rr] == "undefined") { map[rr] = []; }
  166. for (var cc = c; cc <= cmax; cc++) {
  167. map[rr][cc] = new MapCell(cell);
  168. map[rr][cc].isColspan = (cspan && parseInt(cspan, 10) > 1);
  169. map[rr][cc].isRowspan = (rspan && parseInt(rspan, 10) > 1);
  170. map[rr][cc].firstCol = cc == c;
  171. map[rr][cc].lastCol = cc == cmax;
  172. map[rr][cc].firstRow = rr == r;
  173. map[rr][cc].lastRow = rr == rmax;
  174. map[rr][cc].isReal = cc == c && rr == r;
  175. map[rr][cc].spanCollection = spanCollect;
  176. spanCollect.push(map[rr][cc]);
  177. }
  178. }
  179. },
  180. setCellAsModified: function(cell) {
  181. cell.modified = true;
  182. if (cell.spanCollection.length > 0) {
  183. for (var s = 0, smax = cell.spanCollection.length; s < smax; s++) {
  184. cell.spanCollection[s].modified = true;
  185. }
  186. }
  187. },
  188. setTableMap: function() {
  189. var map = [];
  190. var tableRows = this.getTableRows(),
  191. ridx, row, cells, cidx, cell,
  192. c,
  193. cspan, rspan;
  194. for (ridx = 0; ridx < tableRows.length; ridx++) {
  195. row = tableRows[ridx];
  196. cells = this.getRowCells(row);
  197. c = 0;
  198. if (typeof map[ridx] == "undefined") { map[ridx] = []; }
  199. for (cidx = 0; cidx < cells.length; cidx++) {
  200. cell = cells[cidx];
  201. // If cell allready set means it is set by col or rowspan,
  202. // so increase cols index until free col is found
  203. while (typeof map[ridx][c] != "undefined") { c++; }
  204. cspan = api.getAttribute(cell, 'colspan');
  205. rspan = api.getAttribute(cell, 'rowspan');
  206. if (cspan || rspan) {
  207. this.addSpannedCellToMap(cell, map, ridx, c, cspan, rspan);
  208. c = c + ((cspan) ? parseInt(cspan, 10) : 1);
  209. } else {
  210. map[ridx][c] = new MapCell(cell);
  211. c++;
  212. }
  213. }
  214. }
  215. this.map = map;
  216. return map;
  217. },
  218. getRowCells: function(row) {
  219. var inlineTables = this.table.querySelectorAll('table'),
  220. inlineCells = (inlineTables) ? queryInList(inlineTables, 'th, td') : [],
  221. allCells = row.querySelectorAll('th, td'),
  222. tableCells = (inlineCells.length > 0) ? wysihtml.lang.array(allCells).without(inlineCells) : allCells;
  223. return tableCells;
  224. },
  225. getTableRows: function() {
  226. var inlineTables = this.table.querySelectorAll('table'),
  227. inlineRows = (inlineTables) ? queryInList(inlineTables, 'tr') : [],
  228. allRows = this.table.querySelectorAll('tr'),
  229. tableRows = (inlineRows.length > 0) ? wysihtml.lang.array(allRows).without(inlineRows) : allRows;
  230. return tableRows;
  231. },
  232. getMapIndex: function(cell) {
  233. var r_length = this.map.length,
  234. c_length = (this.map && this.map[0]) ? this.map[0].length : 0;
  235. for (var r_idx = 0;r_idx < r_length; r_idx++) {
  236. for (var c_idx = 0;c_idx < c_length; c_idx++) {
  237. if (this.map[r_idx][c_idx].el === cell) {
  238. return {'row': r_idx, 'col': c_idx};
  239. }
  240. }
  241. }
  242. return false;
  243. },
  244. getElementAtIndex: function(idx) {
  245. this.setTableMap();
  246. if (this.map[idx.row] && this.map[idx.row][idx.col] && this.map[idx.row][idx.col].el) {
  247. return this.map[idx.row][idx.col].el;
  248. }
  249. return null;
  250. },
  251. getMapElsTo: function(to_cell) {
  252. var els = [];
  253. this.setTableMap();
  254. this.idx_start = this.getMapIndex(this.cell);
  255. this.idx_end = this.getMapIndex(to_cell);
  256. // switch indexes if start is bigger than end
  257. if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
  258. var temp_idx = this.idx_start;
  259. this.idx_start = this.idx_end;
  260. this.idx_end = temp_idx;
  261. }
  262. if (this.idx_start.col > this.idx_end.col) {
  263. var temp_cidx = this.idx_start.col;
  264. this.idx_start.col = this.idx_end.col;
  265. this.idx_end.col = temp_cidx;
  266. }
  267. if (this.idx_start != null && this.idx_end != null) {
  268. for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
  269. for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
  270. els.push(this.map[row][col].el);
  271. }
  272. }
  273. }
  274. return els;
  275. },
  276. orderSelectionEnds: function(secondcell) {
  277. this.setTableMap();
  278. this.idx_start = this.getMapIndex(this.cell);
  279. this.idx_end = this.getMapIndex(secondcell);
  280. // switch indexes if start is bigger than end
  281. if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
  282. var temp_idx = this.idx_start;
  283. this.idx_start = this.idx_end;
  284. this.idx_end = temp_idx;
  285. }
  286. if (this.idx_start.col > this.idx_end.col) {
  287. var temp_cidx = this.idx_start.col;
  288. this.idx_start.col = this.idx_end.col;
  289. this.idx_end.col = temp_cidx;
  290. }
  291. return {
  292. "start": this.map[this.idx_start.row][this.idx_start.col].el,
  293. "end": this.map[this.idx_end.row][this.idx_end.col].el
  294. };
  295. },
  296. createCells: function(tag, nr, attrs) {
  297. var doc = this.table.ownerDocument,
  298. frag = doc.createDocumentFragment(),
  299. cell;
  300. for (var i = 0; i < nr; i++) {
  301. cell = doc.createElement(tag);
  302. if (attrs) {
  303. for (var attr in attrs) {
  304. if (attrs.hasOwnProperty(attr)) {
  305. cell.setAttribute(attr, attrs[attr]);
  306. }
  307. }
  308. }
  309. // add non breaking space
  310. cell.appendChild(document.createTextNode("\u00a0"));
  311. frag.appendChild(cell);
  312. }
  313. return frag;
  314. },
  315. // Returns next real cell (not part of spanned cell unless first) on row if selected index is not real. I no real cells -1 will be returned
  316. correctColIndexForUnreals: function(col, row) {
  317. var r = this.map[row],
  318. corrIdx = -1;
  319. for (var i = 0, max = col; i < col; i++) {
  320. if (r[i].isReal){
  321. corrIdx++;
  322. }
  323. }
  324. return corrIdx;
  325. },
  326. getLastNewCellOnRow: function(row, rowLimit) {
  327. var cells = this.getRowCells(row),
  328. cell, idx;
  329. for (var cidx = 0, cmax = cells.length; cidx < cmax; cidx++) {
  330. cell = cells[cidx];
  331. idx = this.getMapIndex(cell);
  332. if (idx === false || (typeof rowLimit != "undefined" && idx.row != rowLimit)) {
  333. return cell;
  334. }
  335. }
  336. return null;
  337. },
  338. removeEmptyTable: function() {
  339. var cells = this.table.querySelectorAll('td, th');
  340. if (!cells || cells.length == 0) {
  341. removeElement(this.table);
  342. return true;
  343. } else {
  344. return false;
  345. }
  346. },
  347. // Splits merged cell on row to unique cells
  348. splitRowToCells: function(cell) {
  349. if (cell.isColspan) {
  350. var colspan = parseInt(api.getAttribute(cell.el, 'colspan') || 1, 10),
  351. cType = cell.el.tagName.toLowerCase();
  352. if (colspan > 1) {
  353. var newCells = this.createCells(cType, colspan -1);
  354. insertAfter(cell.el, newCells);
  355. }
  356. cell.el.removeAttribute('colspan');
  357. }
  358. },
  359. getRealRowEl: function(force, idx) {
  360. var r = null,
  361. c = null;
  362. idx = idx || this.idx;
  363. for (var cidx = 0, cmax = this.map[idx.row].length; cidx < cmax; cidx++) {
  364. c = this.map[idx.row][cidx];
  365. if (c.isReal) {
  366. r = api.getParentElement(c.el, { query: "tr" });
  367. if (r) {
  368. return r;
  369. }
  370. }
  371. }
  372. if (r === null && force) {
  373. r = api.getParentElement(this.map[idx.row][idx.col].el, { query: "tr" }) || null;
  374. }
  375. return r;
  376. },
  377. injectRowAt: function(row, col, colspan, cType, c) {
  378. var r = this.getRealRowEl(false, {'row': row, 'col': col}),
  379. new_cells = this.createCells(cType, colspan);
  380. if (r) {
  381. var n_cidx = this.correctColIndexForUnreals(col, row);
  382. if (n_cidx >= 0) {
  383. insertAfter(this.getRowCells(r)[n_cidx], new_cells);
  384. } else {
  385. r.insertBefore(new_cells, r.firstChild);
  386. }
  387. } else {
  388. var rr = this.table.ownerDocument.createElement('tr');
  389. rr.appendChild(new_cells);
  390. insertAfter(api.getParentElement(c.el, { query: "tr" }), rr);
  391. }
  392. },
  393. canMerge: function(to) {
  394. this.to = to;
  395. this.setTableMap();
  396. this.idx_start = this.getMapIndex(this.cell);
  397. this.idx_end = this.getMapIndex(this.to);
  398. // switch indexes if start is bigger than end
  399. if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
  400. var temp_idx = this.idx_start;
  401. this.idx_start = this.idx_end;
  402. this.idx_end = temp_idx;
  403. }
  404. if (this.idx_start.col > this.idx_end.col) {
  405. var temp_cidx = this.idx_start.col;
  406. this.idx_start.col = this.idx_end.col;
  407. this.idx_end.col = temp_cidx;
  408. }
  409. for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
  410. for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
  411. if (this.map[row][col].isColspan || this.map[row][col].isRowspan) {
  412. return false;
  413. }
  414. }
  415. }
  416. return true;
  417. },
  418. decreaseCellSpan: function(cell, span) {
  419. var nr = parseInt(api.getAttribute(cell.el, span), 10) - 1;
  420. if (nr >= 1) {
  421. cell.el.setAttribute(span, nr);
  422. } else {
  423. cell.el.removeAttribute(span);
  424. if (span == 'colspan') {
  425. cell.isColspan = false;
  426. }
  427. if (span == 'rowspan') {
  428. cell.isRowspan = false;
  429. }
  430. cell.firstCol = true;
  431. cell.lastCol = true;
  432. cell.firstRow = true;
  433. cell.lastRow = true;
  434. cell.isReal = true;
  435. }
  436. },
  437. removeSurplusLines: function() {
  438. var row, cell, ridx, rmax, cidx, cmax, allRowspan;
  439. this.setTableMap();
  440. if (this.map) {
  441. ridx = 0;
  442. rmax = this.map.length;
  443. for (;ridx < rmax; ridx++) {
  444. row = this.map[ridx];
  445. allRowspan = true;
  446. cidx = 0;
  447. cmax = row.length;
  448. for (; cidx < cmax; cidx++) {
  449. cell = row[cidx];
  450. if (!(api.getAttribute(cell.el, "rowspan") && parseInt(api.getAttribute(cell.el, "rowspan"), 10) > 1 && cell.firstRow !== true)) {
  451. allRowspan = false;
  452. break;
  453. }
  454. }
  455. if (allRowspan) {
  456. cidx = 0;
  457. for (; cidx < cmax; cidx++) {
  458. this.decreaseCellSpan(row[cidx], 'rowspan');
  459. }
  460. }
  461. }
  462. // remove rows without cells
  463. var tableRows = this.getTableRows();
  464. ridx = 0;
  465. rmax = tableRows.length;
  466. for (;ridx < rmax; ridx++) {
  467. row = tableRows[ridx];
  468. if (row.childNodes.length == 0 && (/^\s*$/.test(row.textContent || row.innerText))) {
  469. removeElement(row);
  470. }
  471. }
  472. }
  473. },
  474. fillMissingCells: function() {
  475. var r_max = 0,
  476. c_max = 0,
  477. prevcell = null;
  478. this.setTableMap();
  479. if (this.map) {
  480. // find maximal dimensions of broken table
  481. r_max = this.map.length;
  482. for (var ridx = 0; ridx < r_max; ridx++) {
  483. if (this.map[ridx].length > c_max) { c_max = this.map[ridx].length; }
  484. }
  485. for (var row = 0; row < r_max; row++) {
  486. for (var col = 0; col < c_max; col++) {
  487. if (this.map[row] && !this.map[row][col]) {
  488. if (col > 0) {
  489. this.map[row][col] = new MapCell(this.createCells('td', 1));
  490. prevcell = this.map[row][col-1];
  491. if (prevcell && prevcell.el && prevcell.el.parent) { // if parent does not exist element is removed from dom
  492. insertAfter(this.map[row][col-1].el, this.map[row][col].el);
  493. }
  494. }
  495. }
  496. }
  497. }
  498. }
  499. },
  500. rectify: function() {
  501. if (!this.removeEmptyTable()) {
  502. this.removeSurplusLines();
  503. this.fillMissingCells();
  504. return true;
  505. } else {
  506. return false;
  507. }
  508. },
  509. unmerge: function() {
  510. if (this.rectify()) {
  511. this.setTableMap();
  512. this.idx = this.getMapIndex(this.cell);
  513. if (this.idx) {
  514. var thisCell = this.map[this.idx.row][this.idx.col],
  515. colspan = (api.getAttribute(thisCell.el, "colspan")) ? parseInt(api.getAttribute(thisCell.el, "colspan"), 10) : 1,
  516. cType = thisCell.el.tagName.toLowerCase();
  517. if (thisCell.isRowspan) {
  518. var rowspan = parseInt(api.getAttribute(thisCell.el, "rowspan"), 10);
  519. if (rowspan > 1) {
  520. for (var nr = 1, maxr = rowspan - 1; nr <= maxr; nr++){
  521. this.injectRowAt(this.idx.row + nr, this.idx.col, colspan, cType, thisCell);
  522. }
  523. }
  524. thisCell.el.removeAttribute('rowspan');
  525. }
  526. this.splitRowToCells(thisCell);
  527. }
  528. }
  529. },
  530. // merges cells from start cell (defined in creating obj) to "to" cell
  531. merge: function(to) {
  532. if (this.rectify()) {
  533. if (this.canMerge(to)) {
  534. var rowspan = this.idx_end.row - this.idx_start.row + 1,
  535. colspan = this.idx_end.col - this.idx_start.col + 1;
  536. for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
  537. for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
  538. if (row == this.idx_start.row && col == this.idx_start.col) {
  539. if (rowspan > 1) {
  540. this.map[row][col].el.setAttribute('rowspan', rowspan);
  541. }
  542. if (colspan > 1) {
  543. this.map[row][col].el.setAttribute('colspan', colspan);
  544. }
  545. } else {
  546. // transfer content
  547. if (!(/^\s*<br\/?>\s*$/.test(this.map[row][col].el.innerHTML.toLowerCase()))) {
  548. this.map[this.idx_start.row][this.idx_start.col].el.innerHTML += ' ' + this.map[row][col].el.innerHTML;
  549. }
  550. removeElement(this.map[row][col].el);
  551. }
  552. }
  553. }
  554. this.rectify();
  555. } else {
  556. if (window.console) {
  557. console.log('Do not know how to merge allready merged cells.');
  558. }
  559. }
  560. }
  561. },
  562. // Decreases rowspan of a cell if it is done on first cell of rowspan row (real cell)
  563. // Cell is moved to next row (if it is real)
  564. collapseCellToNextRow: function(cell) {
  565. var cellIdx = this.getMapIndex(cell.el),
  566. newRowIdx = cellIdx.row + 1,
  567. newIdx = {'row': newRowIdx, 'col': cellIdx.col};
  568. if (newRowIdx < this.map.length) {
  569. var row = this.getRealRowEl(false, newIdx);
  570. if (row !== null) {
  571. var n_cidx = this.correctColIndexForUnreals(newIdx.col, newIdx.row);
  572. if (n_cidx >= 0) {
  573. insertAfter(this.getRowCells(row)[n_cidx], cell.el);
  574. } else {
  575. var lastCell = this.getLastNewCellOnRow(row, newRowIdx);
  576. if (lastCell !== null) {
  577. insertAfter(lastCell, cell.el);
  578. } else {
  579. row.insertBefore(cell.el, row.firstChild);
  580. }
  581. }
  582. if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
  583. cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
  584. } else {
  585. cell.el.removeAttribute('rowspan');
  586. }
  587. }
  588. }
  589. },
  590. // Removes a cell when removing a row
  591. // If is rowspan cell then decreases the rowspan
  592. // and moves cell to next row if needed (is first cell of rowspan)
  593. removeRowCell: function(cell) {
  594. if (cell.isReal) {
  595. if (cell.isRowspan) {
  596. this.collapseCellToNextRow(cell);
  597. } else {
  598. removeElement(cell.el);
  599. }
  600. } else {
  601. if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
  602. cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
  603. } else {
  604. cell.el.removeAttribute('rowspan');
  605. }
  606. }
  607. },
  608. getRowElementsByCell: function() {
  609. var cells = [];
  610. this.setTableMap();
  611. this.idx = this.getMapIndex(this.cell);
  612. if (this.idx !== false) {
  613. var modRow = this.map[this.idx.row];
  614. for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
  615. if (modRow[cidx].isReal) {
  616. cells.push(modRow[cidx].el);
  617. }
  618. }
  619. }
  620. return cells;
  621. },
  622. getColumnElementsByCell: function() {
  623. var cells = [];
  624. this.setTableMap();
  625. this.idx = this.getMapIndex(this.cell);
  626. if (this.idx !== false) {
  627. for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
  628. if (this.map[ridx][this.idx.col] && this.map[ridx][this.idx.col].isReal) {
  629. cells.push(this.map[ridx][this.idx.col].el);
  630. }
  631. }
  632. }
  633. return cells;
  634. },
  635. // Removes the row of selected cell
  636. removeRow: function() {
  637. var oldRow = api.getParentElement(this.cell, { query: "tr" });
  638. if (oldRow) {
  639. this.setTableMap();
  640. this.idx = this.getMapIndex(this.cell);
  641. if (this.idx !== false) {
  642. var modRow = this.map[this.idx.row];
  643. for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
  644. if (!modRow[cidx].modified) {
  645. this.setCellAsModified(modRow[cidx]);
  646. this.removeRowCell(modRow[cidx]);
  647. }
  648. }
  649. }
  650. removeElement(oldRow);
  651. }
  652. },
  653. removeColCell: function(cell) {
  654. if (cell.isColspan) {
  655. if (parseInt(api.getAttribute(cell.el, 'colspan'), 10) > 2) {
  656. cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) - 1);
  657. } else {
  658. cell.el.removeAttribute('colspan');
  659. }
  660. } else if (cell.isReal) {
  661. removeElement(cell.el);
  662. }
  663. },
  664. removeColumn: function() {
  665. this.setTableMap();
  666. this.idx = this.getMapIndex(this.cell);
  667. if (this.idx !== false) {
  668. for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
  669. if (!this.map[ridx][this.idx.col].modified) {
  670. this.setCellAsModified(this.map[ridx][this.idx.col]);
  671. this.removeColCell(this.map[ridx][this.idx.col]);
  672. }
  673. }
  674. }
  675. },
  676. // removes row or column by selected cell element
  677. remove: function(what) {
  678. if (this.rectify()) {
  679. switch (what) {
  680. case 'row':
  681. this.removeRow();
  682. break;
  683. case 'column':
  684. this.removeColumn();
  685. break;
  686. }
  687. this.rectify();
  688. }
  689. },
  690. addRow: function(where) {
  691. var doc = this.table.ownerDocument;
  692. this.setTableMap();
  693. this.idx = this.getMapIndex(this.cell);
  694. if (where == "below" && api.getAttribute(this.cell, 'rowspan')) {
  695. this.idx.row = this.idx.row + parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1;
  696. }
  697. if (this.idx !== false) {
  698. var modRow = this.map[this.idx.row],
  699. newRow = doc.createElement('tr');
  700. for (var ridx = 0, rmax = modRow.length; ridx < rmax; ridx++) {
  701. if (!modRow[ridx].modified) {
  702. this.setCellAsModified(modRow[ridx]);
  703. this.addRowCell(modRow[ridx], newRow, where);
  704. }
  705. }
  706. switch (where) {
  707. case 'below':
  708. insertAfter(this.getRealRowEl(true), newRow);
  709. break;
  710. case 'above':
  711. var cr = api.getParentElement(this.map[this.idx.row][this.idx.col].el, { query: "tr" });
  712. if (cr) {
  713. cr.parentNode.insertBefore(newRow, cr);
  714. }
  715. break;
  716. }
  717. }
  718. },
  719. addRowCell: function(cell, row, where) {
  720. var colSpanAttr = (cell.isColspan) ? {"colspan" : api.getAttribute(cell.el, 'colspan')} : null;
  721. if (cell.isReal) {
  722. if (where != 'above' && cell.isRowspan) {
  723. cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el,'rowspan'), 10) + 1);
  724. } else {
  725. row.appendChild(this.createCells('td', 1, colSpanAttr));
  726. }
  727. } else {
  728. if (where != 'above' && cell.isRowspan && cell.lastRow) {
  729. row.appendChild(this.createCells('td', 1, colSpanAttr));
  730. } else if (c.isRowspan) {
  731. cell.el.attr('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) + 1);
  732. }
  733. }
  734. },
  735. add: function(where) {
  736. if (this.rectify()) {
  737. if (where == 'below' || where == 'above') {
  738. this.addRow(where);
  739. }
  740. if (where == 'before' || where == 'after') {
  741. this.addColumn(where);
  742. }
  743. }
  744. },
  745. addColCell: function (cell, ridx, where) {
  746. var doAdd,
  747. cType = cell.el.tagName.toLowerCase();
  748. // defines add cell vs expand cell conditions
  749. // true means add
  750. switch (where) {
  751. case "before":
  752. doAdd = (!cell.isColspan || cell.firstCol);
  753. break;
  754. case "after":
  755. doAdd = (!cell.isColspan || cell.lastCol || (cell.isColspan && cell.el == this.cell));
  756. break;
  757. }
  758. if (doAdd){
  759. // adds a cell before or after current cell element
  760. switch (where) {
  761. case "before":
  762. cell.el.parentNode.insertBefore(this.createCells(cType, 1), cell.el);
  763. break;
  764. case "after":
  765. insertAfter(cell.el, this.createCells(cType, 1));
  766. break;
  767. }
  768. // handles if cell has rowspan
  769. if (cell.isRowspan) {
  770. this.handleCellAddWithRowspan(cell, ridx+1, where);
  771. }
  772. } else {
  773. // expands cell
  774. cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) + 1);
  775. }
  776. },
  777. addColumn: function(where) {
  778. var row, modCell;
  779. this.setTableMap();
  780. this.idx = this.getMapIndex(this.cell);
  781. if (where == "after" && api.getAttribute(this.cell, 'colspan')) {
  782. this.idx.col = this.idx.col + parseInt(api.getAttribute(this.cell, 'colspan'), 10) - 1;
  783. }
  784. if (this.idx !== false) {
  785. for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++ ) {
  786. row = this.map[ridx];
  787. if (row[this.idx.col]) {
  788. modCell = row[this.idx.col];
  789. if (!modCell.modified) {
  790. this.setCellAsModified(modCell);
  791. this.addColCell(modCell, ridx , where);
  792. }
  793. }
  794. }
  795. }
  796. },
  797. handleCellAddWithRowspan: function (cell, ridx, where) {
  798. var addRowsNr = parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1,
  799. crow = api.getParentElement(cell.el, { query: "tr" }),
  800. cType = cell.el.tagName.toLowerCase(),
  801. cidx, temp_r_cells,
  802. doc = this.table.ownerDocument,
  803. nrow;
  804. for (var i = 0; i < addRowsNr; i++) {
  805. cidx = this.correctColIndexForUnreals(this.idx.col, (ridx + i));
  806. crow = nextNode(crow, 'tr');
  807. if (crow) {
  808. if (cidx > 0) {
  809. switch (where) {
  810. case "before":
  811. temp_r_cells = this.getRowCells(crow);
  812. if (cidx > 0 && this.map[ridx + i][this.idx.col].el != temp_r_cells[cidx] && cidx == temp_r_cells.length - 1) {
  813. insertAfter(temp_r_cells[cidx], this.createCells(cType, 1));
  814. } else {
  815. temp_r_cells[cidx].parentNode.insertBefore(this.createCells(cType, 1), temp_r_cells[cidx]);
  816. }
  817. break;
  818. case "after":
  819. insertAfter(this.getRowCells(crow)[cidx], this.createCells(cType, 1));
  820. break;
  821. }
  822. } else {
  823. crow.insertBefore(this.createCells(cType, 1), crow.firstChild);
  824. }
  825. } else {
  826. nrow = doc.createElement('tr');
  827. nrow.appendChild(this.createCells(cType, 1));
  828. this.table.appendChild(nrow);
  829. }
  830. }
  831. }
  832. };
  833. api.table = {
  834. getCellsBetween: function(cell1, cell2) {
  835. var c1 = new TableModifyerByCell(cell1);
  836. return c1.getMapElsTo(cell2);
  837. },
  838. addCells: function(cell, where) {
  839. var c = new TableModifyerByCell(cell);
  840. c.add(where);
  841. },
  842. removeCells: function(cell, what) {
  843. var c = new TableModifyerByCell(cell);
  844. c.remove(what);
  845. },
  846. mergeCellsBetween: function(cell1, cell2) {
  847. var c1 = new TableModifyerByCell(cell1);
  848. c1.merge(cell2);
  849. },
  850. unmergeCell: function(cell) {
  851. var c = new TableModifyerByCell(cell);
  852. c.unmerge();
  853. },
  854. orderSelectionEnds: function(cell, cell2) {
  855. var c = new TableModifyerByCell(cell);
  856. return c.orderSelectionEnds(cell2);
  857. },
  858. indexOf: function(cell) {
  859. var c = new TableModifyerByCell(cell);
  860. c.setTableMap();
  861. return c.getMapIndex(cell);
  862. },
  863. findCell: function(table, idx) {
  864. var c = new TableModifyerByCell(null, table);
  865. return c.getElementAtIndex(idx);
  866. },
  867. findRowByCell: function(cell) {
  868. var c = new TableModifyerByCell(cell);
  869. return c.getRowElementsByCell();
  870. },
  871. findColumnByCell: function(cell) {
  872. var c = new TableModifyerByCell(cell);
  873. return c.getColumnElementsByCell();
  874. },
  875. canMerge: function(cell1, cell2) {
  876. var c = new TableModifyerByCell(cell1);
  877. return c.canMerge(cell2);
  878. }
  879. };
  880. })();
  881. (function() {
  882. // Keep the old composer.observe function.
  883. var oldObserverFunction = wysihtml.views.Composer.prototype.observe;
  884. var extendedObserverFunction = function() {
  885. oldObserverFunction.call(this);
  886. // Bind the table user interaction tracking
  887. if (this.config.handleTables) {
  888. // If handleTables option is true, table handling functions are bound
  889. initTableHandling.call(this);
  890. }
  891. };
  892. // Table management.
  893. // If present enableObjectResizing and enableInlineTableEditing command
  894. // should be called with false to prevent native table handlers.
  895. var initTableHandling = function() {
  896. var hideHandlers = function() {
  897. this.win.removeEventListener('load', hideHandlers);
  898. this.doc.execCommand('enableObjectResizing', false, 'false');
  899. this.doc.execCommand('enableInlineTableEditing', false, 'false');
  900. }.bind(this),
  901. iframeInitiator = (function() {
  902. hideHandlers.call(this);
  903. actions.removeListeners(this.sandbox.getIframe(), ['focus', 'mouseup', 'mouseover'], iframeInitiator);
  904. }).bind(this);
  905. if (
  906. this.doc.execCommand &&
  907. wysihtml.browser.supportsCommand(this.doc, 'enableObjectResizing') &&
  908. wysihtml.browser.supportsCommand(this.doc, 'enableInlineTableEditing')
  909. ) {
  910. if (this.sandbox.getIframe) {
  911. actions.addListeners(this.sandbox.getIframe(), ['focus', 'mouseup', 'mouseover'], iframeInitiator);
  912. } else {
  913. this.win.addEventListener('load', hideHandlers);
  914. }
  915. }
  916. this.tableSelection = wysihtml.quirks.tableCellsSelection(this.element, this.parent);
  917. };
  918. // Cell selections handling
  919. var tableCellsSelection = function(editable, editor) {
  920. var init = function() {
  921. editable.addEventListener('mousedown', handleMouseDown);
  922. return select;
  923. };
  924. var handleMouseDown = function(event) {
  925. var target = wysihtml.dom.getParentElement(event.target, {query: 'td, th'}, false, editable);
  926. if (target) {
  927. handleSelectionMousedown(target);
  928. }
  929. };
  930. var handleSelectionMousedown = function(target) {
  931. select.start = target;
  932. select.end = target;
  933. select.cells = [target];
  934. select.table = dom.getParentElement(select.start, {query: 'table'}, false, editable);
  935. if (select.table) {
  936. removeCellSelections();
  937. dom.addClass(target, selectionClass);
  938. editable.addEventListener('mousemove', handleMouseMove);
  939. editable.addEventListener('mouseup', handleMouseUp);
  940. editor.fire('tableselectstart').fire('tableselectstart:composer');
  941. }
  942. };
  943. // remove all selection classes
  944. var removeCellSelections = function() {
  945. if (editable) {
  946. var selectedCells = editable.querySelectorAll('.' + selectionClass);
  947. if (selectedCells.length > 0) {
  948. for (var i = 0; i < selectedCells.length; i++) {
  949. dom.removeClass(selectedCells[i], selectionClass);
  950. }
  951. }
  952. }
  953. };
  954. var addSelections = function(cells) {
  955. for (var i = 0; i < cells.length; i++) {
  956. dom.addClass(cells[i], selectionClass);
  957. }
  958. };
  959. var handleMouseMove = function(event) {
  960. var curTable = null,
  961. cell = dom.getParentElement(event.target, {query: 'td, th'}, false, editable),
  962. oldEnd;
  963. if (cell && select.table && select.start) {
  964. curTable = dom.getParentElement(cell, {query: 'table'}, false, editable);
  965. if (curTable && curTable === select.table) {
  966. removeCellSelections();
  967. oldEnd = select.end;
  968. select.end = cell;
  969. select.cells = dom.table.getCellsBetween(select.start, cell);
  970. if (select.cells.length > 1) {
  971. editor.composer.selection.deselect();
  972. }
  973. addSelections(select.cells);
  974. if (select.end !== oldEnd) {
  975. editor.fire('tableselectchange').fire('tableselectchange:composer');
  976. }
  977. }
  978. }
  979. };
  980. var handleMouseUp = function(event) {
  981. editable.removeEventListener('mousemove', handleMouseMove);
  982. editable.removeEventListener('mouseup', handleMouseUp);
  983. editor.fire('tableselect').fire('tableselect:composer');
  984. setTimeout(function() {
  985. bindSideclick();
  986. }, 0);
  987. };
  988. var sideClickHandler = function(event) {
  989. editable.ownerDocument.removeEventListener('click', sideClickHandler);
  990. if (dom.getParentElement(event.target, {query: 'table'}, false, editable) != select.table) {
  991. removeCellSelections();
  992. select.table = null;
  993. select.start = null;
  994. select.end = null;
  995. editor.fire('tableunselect').fire('tableunselect:composer');
  996. }
  997. };
  998. var bindSideclick = function() {
  999. editable.ownerDocument.addEventListener('click', sideClickHandler);
  1000. };
  1001. var selectCells = function(start, end) {
  1002. select.start = start;
  1003. select.end = end;
  1004. select.table = dom.getParentElement(select.start, {query: 'table'}, false, editable);
  1005. selectedCells = dom.table.getCellsBetween(select.start, select.end);
  1006. addSelections(selectedCells);
  1007. bindSideclick();
  1008. editor.fire('tableselect').fire('tableselect:composer');
  1009. };
  1010. var dom = wysihtml.dom,
  1011. select = {
  1012. table: null,
  1013. start: null,
  1014. end: null,
  1015. cells: null,
  1016. select: selectCells
  1017. },
  1018. selectionClass = 'wysiwyg-tmp-selected-cell';
  1019. return init();
  1020. };
  1021. // Bind to wysihtml
  1022. wysihtml.Editor.prototype.defaults.handleTables = true;
  1023. wysihtml.quirks.tableCellsSelection = tableCellsSelection;
  1024. wysihtml.views.Composer.prototype.observe = extendedObserverFunction;
  1025. })();