4 * Copyright 2009, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
12 var each = tinymce.each;
14 // Checks if the selection/caret is at the start of the specified block element
15 function isAtStart(rng, par) {
16 var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
18 rng2.setStartBefore(par);
19 rng2.setEnd(rng.endContainer, rng.endOffset);
21 elm = doc.createElement('body');
22 elm.appendChild(rng2.cloneContents());
24 // Check for text characters of other elements that should be treated as content
25 return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
31 function TableGrid(table, dom, selection) {
32 var grid, startPos, endPos, selectedCell;
35 selectedCell = dom.getParent(selection.getStart(), 'th,td');
37 startPos = getPos(selectedCell);
38 endPos = findEndPos();
39 selectedCell = getCell(startPos.x, startPos.y);
42 function cloneNode(node, children) {
43 node = node.cloneNode(children);
44 node.removeAttribute('id');
49 function buildGrid() {
54 each(['thead', 'tbody', 'tfoot'], function(part) {
55 var rows = dom.select('> ' + part + ' tr', table);
57 each(rows, function(tr, y) {
60 each(dom.select('> td, > th', tr), function(td, x) {
61 var x2, y2, rowspan, colspan;
63 // Skip over existing cells produced by rowspan
69 // Get col/rowspan from cell
70 rowspan = getSpanVal(td, 'rowspan');
71 colspan = getSpanVal(td, 'colspan');
73 // Fill out rowspan/colspan right and down
74 for (y2 = y; y2 < y + rowspan; y2++) {
78 for (x2 = x; x2 < x + colspan; x2++) {
81 real : y2 == y && x2 == x,
91 startY += rows.length;
95 function getCell(x, y) {
103 function getSpanVal(td, name) {
104 return parseInt(td.getAttribute(name) || 1);
107 function setSpanVal(td, name, val) {
112 td.removeAttribute(name, 1);
114 td.setAttribute(name, val, 1);
118 function isCellSelected(cell) {
119 return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell);
122 function getSelectedRows() {
125 each(table.rows, function(row) {
126 each(row.cells, function(cell) {
127 if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
137 function deleteTable() {
138 var rng = dom.createRng();
140 rng.setStartAfter(table);
141 rng.setEndAfter(table);
143 selection.setRng(rng);
148 function cloneCell(cell) {
152 tinymce.walk(cell, function(node) {
155 if (node.nodeType == 3) {
156 each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
157 node = cloneNode(node, false);
160 formatNode = curNode = node;
162 curNode.appendChild(node);
167 // Add something to the inner node
169 curNode.innerHTML = tinymce.isIE ? ' ' : '<br data-mce-bogus="1" />';
175 cell = cloneNode(cell, false);
176 setSpanVal(cell, 'rowSpan', 1);
177 setSpanVal(cell, 'colSpan', 1);
180 cell.appendChild(formatNode);
183 cell.innerHTML = '<br data-mce-bogus="1" />';
190 var rng = dom.createRng();
193 each(dom.select('tr', table), function(tr) {
194 if (tr.cells.length == 0)
199 if (dom.select('tr', table).length == 0) {
200 rng.setStartAfter(table);
201 rng.setEndAfter(table);
202 selection.setRng(rng);
207 // Empty header/body/footer
208 each(dom.select('thead,tbody,tfoot', table), function(part) {
209 if (part.rows.length == 0)
213 // Restore selection to start position if it still exists
216 // Restore the selection to the closest table position
217 row = grid[Math.min(grid.length - 1, startPos.y)];
219 selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
220 selection.collapse(true);
224 function fillLeftDown(x, y, rows, cols) {
225 var tr, x2, r, c, cell;
227 tr = grid[y][x].elm.parentNode;
228 for (r = 1; r <= rows; r++) {
229 tr = dom.getNext(tr, 'tr');
232 // Loop left to find real cell
233 for (x2 = x; x2 >= 0; x2--) {
234 cell = grid[y + r][x2].elm;
236 if (cell.parentNode == tr) {
237 // Append clones after
238 for (c = 1; c <= cols; c++)
239 dom.insertAfter(cloneCell(cell), cell);
246 // Insert nodes before first cell
247 for (c = 1; c <= cols; c++)
248 tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
255 each(grid, function(row, y) {
256 each(row, function(cell, x) {
257 var colSpan, rowSpan, newCell, i;
259 if (isCellSelected(cell)) {
261 colSpan = getSpanVal(cell, 'colspan');
262 rowSpan = getSpanVal(cell, 'rowspan');
264 if (colSpan > 1 || rowSpan > 1) {
265 setSpanVal(cell, 'rowSpan', 1);
266 setSpanVal(cell, 'colSpan', 1);
268 // Insert cells right
269 for (i = 0; i < colSpan - 1; i++)
270 dom.insertAfter(cloneCell(cell), cell);
272 fillLeftDown(x, y, rowSpan - 1, colSpan);
279 function merge(cell, cols, rows) {
280 var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count;
282 // Use specified cell and cols/rows
287 endX = startX + (cols - 1);
288 endY = startY + (rows - 1);
297 // Find start/end cells
298 startCell = getCell(startX, startY);
299 endCell = getCell(endX, endY);
301 // Check if the cells exists and if they are of the same part for example tbody = tbody
302 if (startCell && endCell && startCell.part == endCell.part) {
303 // Split and rebuild grid
307 // Set row/col span to start cell
308 startCell = getCell(startX, startY).elm;
309 setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
310 setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
312 // Remove other cells and add it's contents to the start cell
313 for (y = startY; y <= endY; y++) {
314 for (x = startX; x <= endX; x++) {
315 if (!grid[y] || !grid[y][x])
318 cell = grid[y][x].elm;
320 if (cell != startCell) {
321 // Move children to startCell
322 children = tinymce.grep(cell.childNodes);
323 each(children, function(node) {
324 startCell.appendChild(node);
327 // Remove bogus nodes if there is children in the target cell
328 if (children.length) {
329 children = tinymce.grep(startCell.childNodes);
331 each(children, function(node) {
332 if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1)
333 startCell.removeChild(node);
343 // Remove empty rows etc and restore caret location
348 function insertRow(before) {
349 var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
351 // Find first/last row
352 each(grid, function(row, y) {
353 each(row, function(cell, x) {
354 if (isCellSelected(cell)) {
356 rowElm = cell.parentNode;
357 newRow = cloneNode(rowElm, false);
369 for (x = 0; x < grid[0].length; x++) {
370 // Cell not found could be because of an invalid table structure
374 cell = grid[posY][x].elm;
376 if (cell != lastCell) {
378 rowSpan = getSpanVal(cell, 'rowspan');
380 setSpanVal(cell, 'rowSpan', rowSpan + 1);
384 // Check if cell above can be expanded
385 if (posY > 0 && grid[posY - 1][x]) {
386 otherCell = grid[posY - 1][x].elm;
387 rowSpan = getSpanVal(otherCell, 'rowSpan');
389 setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
395 // Insert new cell into new row
396 newCell = cloneCell(cell);
397 setSpanVal(newCell, 'colSpan', cell.colSpan);
399 newRow.appendChild(newCell);
405 if (newRow.hasChildNodes()) {
407 dom.insertAfter(newRow, rowElm);
409 rowElm.parentNode.insertBefore(newRow, rowElm);
413 function insertCol(before) {
416 // Find first/last column
417 each(grid, function(row, y) {
418 each(row, function(cell, x) {
419 if (isCellSelected(cell)) {
431 each(grid, function(row, y) {
432 var cell, rowSpan, colSpan;
437 cell = row[posX].elm;
438 if (cell != lastCell) {
439 colSpan = getSpanVal(cell, 'colspan');
440 rowSpan = getSpanVal(cell, 'rowspan');
444 dom.insertAfter(cloneCell(cell), cell);
445 fillLeftDown(posX, y, rowSpan - 1, colSpan);
447 cell.parentNode.insertBefore(cloneCell(cell), cell);
448 fillLeftDown(posX, y, rowSpan - 1, colSpan);
451 setSpanVal(cell, 'colSpan', cell.colSpan + 1);
458 function deleteCols() {
461 // Get selected column indexes
462 each(grid, function(row, y) {
463 each(row, function(cell, x) {
464 if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
465 each(grid, function(row) {
466 var cell = row[x].elm, colSpan;
468 colSpan = getSpanVal(cell, 'colSpan');
471 setSpanVal(cell, 'colSpan', colSpan - 1);
484 function deleteRows() {
487 function deleteRow(tr) {
488 var nextTr, pos, lastCell;
490 nextTr = dom.getNext(tr, 'tr');
492 // Move down row spanned cells
493 each(tr.cells, function(cell) {
494 var rowSpan = getSpanVal(cell, 'rowSpan');
497 setSpanVal(cell, 'rowSpan', rowSpan - 1);
499 fillLeftDown(pos.x, pos.y, 1, 1);
504 pos = getPos(tr.cells[0]);
505 each(grid[pos.y], function(cell) {
510 if (cell != lastCell) {
511 rowSpan = getSpanVal(cell, 'rowSpan');
516 setSpanVal(cell, 'rowSpan', rowSpan - 1);
523 // Get selected rows and move selection out of scope
524 rows = getSelectedRows();
526 // Delete all selected rows
527 each(rows.reverse(), function(tr) {
535 var rows = getSelectedRows();
543 function copyRows() {
544 var rows = getSelectedRows();
546 each(rows, function(row, i) {
547 rows[i] = cloneNode(row, true);
553 function pasteRows(rows, before) {
554 var selectedRows = getSelectedRows(),
555 targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
556 targetCellCount = targetRow.cells.length;
558 // Calc target cell count
559 each(grid, function(row) {
563 each(row, function(cell, x) {
565 targetCellCount += cell.colspan;
567 if (cell.elm.parentNode == targetRow)
578 each(rows, function(row) {
579 var cellCount = row.cells.length, cell;
581 // Remove col/rowspans
582 for (i = 0; i < cellCount; i++) {
584 setSpanVal(cell, 'colSpan', 1);
585 setSpanVal(cell, 'rowSpan', 1);
589 for (i = cellCount; i < targetCellCount; i++)
590 row.appendChild(cloneCell(row.cells[cellCount - 1]));
593 for (i = targetCellCount; i < cellCount; i++)
594 dom.remove(row.cells[i]);
598 targetRow.parentNode.insertBefore(row, targetRow);
600 dom.insertAfter(row, targetRow);
604 function getPos(target) {
607 each(grid, function(row, y) {
608 each(row, function(cell, x) {
609 if (cell.elm == target) {
610 pos = {x : x, y : y};
621 function setStartCell(cell) {
622 startPos = getPos(cell);
625 function findEndPos() {
630 each(grid, function(row, y) {
631 each(row, function(cell, x) {
632 var colSpan, rowSpan;
634 if (isCellSelected(cell)) {
644 colSpan = cell.colspan - 1;
645 rowSpan = cell.rowspan - 1;
648 if (x + colSpan > maxX)
653 if (y + rowSpan > maxY)
661 return {x : maxX, y : maxY};
664 function setEndCell(cell) {
665 var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
667 endPos = getPos(cell);
669 if (startPos && endPos) {
670 // Get start/end positions
671 startX = Math.min(startPos.x, endPos.x);
672 startY = Math.min(startPos.y, endPos.y);
673 endX = Math.max(startPos.x, endPos.x);
674 endY = Math.max(startPos.y, endPos.y);
676 // Expand end positon to include spans
681 for (y = startY; y <= maxY; y++) {
682 cell = grid[y][startX];
685 if (startX - (cell.colspan - 1) < startX)
686 startX -= cell.colspan - 1;
691 for (x = startX; x <= maxX; x++) {
692 cell = grid[startY][x];
695 if (startY - (cell.rowspan - 1) < startY)
696 startY -= cell.rowspan - 1;
701 for (y = startY; y <= endY; y++) {
702 for (x = startX; x <= endX; x++) {
706 colSpan = cell.colspan - 1;
707 rowSpan = cell.rowspan - 1;
710 if (x + colSpan > maxX)
715 if (y + rowSpan > maxY)
722 // Remove current selection
723 dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
726 for (y = startY; y <= maxY; y++) {
727 for (x = startX; x <= maxX; x++) {
729 dom.addClass(grid[y][x].elm, 'mceSelected');
736 tinymce.extend(this, {
737 deleteTable : deleteTable,
740 insertRow : insertRow,
741 insertCol : insertCol,
742 deleteCols : deleteCols,
743 deleteRows : deleteRows,
746 pasteRows : pasteRows,
748 setStartCell : setStartCell,
749 setEndCell : setEndCell
753 tinymce.create('tinymce.plugins.TablePlugin', {
754 init : function(ed, url) {
755 var winMan, clipboardRows;
757 function createTableGrid(node) {
758 var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
761 return new TableGrid(tblElm, ed.dom, selection);
765 // Restore selection possibilities
766 ed.getBody().style.webkitUserSelect = '';
767 ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
772 ['table', 'table.desc', 'mceInsertTable', true],
773 ['delete_table', 'table.del', 'mceTableDelete'],
774 ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
775 ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
776 ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
777 ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
778 ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
779 ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
780 ['row_props', 'table.row_desc', 'mceTableRowProps', true],
781 ['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
782 ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
783 ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
785 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
788 // Select whole table is a table border is clicked
790 ed.onClick.add(function(ed, e) {
793 if (e.nodeName === 'TABLE') {
794 ed.selection.select(e);
800 ed.onPreProcess.add(function(ed, args) {
801 var nodes, i, node, dom = ed.dom, value;
803 nodes = dom.select('table', args.node);
807 dom.setAttrib(node, 'data-mce-style', '');
809 if ((value = dom.getAttrib(node, 'width'))) {
810 dom.setStyle(node, 'width', value);
811 dom.setAttrib(node, 'width', '');
814 if ((value = dom.getAttrib(node, 'height'))) {
815 dom.setStyle(node, 'height', value);
816 dom.setAttrib(node, 'height', '');
821 // Handle node change updates
822 ed.onNodeChange.add(function(ed, cm, n) {
825 n = ed.selection.getStart();
826 p = ed.dom.getParent(n, 'td,th,caption');
827 cm.setActive('table', n.nodeName === 'TABLE' || !!p);
829 // Disable table tools if we are in caption
830 if (p && p.nodeName === 'CAPTION')
833 cm.setDisabled('delete_table', !p);
834 cm.setDisabled('delete_col', !p);
835 cm.setDisabled('delete_table', !p);
836 cm.setDisabled('delete_row', !p);
837 cm.setDisabled('col_after', !p);
838 cm.setDisabled('col_before', !p);
839 cm.setDisabled('row_after', !p);
840 cm.setDisabled('row_before', !p);
841 cm.setDisabled('row_props', !p);
842 cm.setDisabled('cell_props', !p);
843 cm.setDisabled('split_cells', !p);
844 cm.setDisabled('merge_cells', !p);
847 ed.onInit.add(function(ed) {
848 var startTable, startCell, dom = ed.dom, tableGrid;
850 winMan = ed.windowManager;
852 // Add cell selection logic
853 ed.onMouseDown.add(function(ed, e) {
857 startCell = dom.getParent(e.target, 'td,th');
858 startTable = dom.getParent(startCell, 'table');
862 dom.bind(ed.getDoc(), 'mouseover', function(e) {
863 var sel, table, target = e.target;
865 if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
866 table = dom.getParent(target, 'table');
867 if (table == startTable) {
869 tableGrid = createTableGrid(table);
870 tableGrid.setStartCell(startCell);
872 ed.getBody().style.webkitUserSelect = 'none';
875 tableGrid.setEndCell(target);
878 // Remove current selection
879 sel = ed.selection.getSel();
882 if (sel.removeAllRanges)
883 sel.removeAllRanges();
887 // IE9 might throw errors here
894 ed.onMouseUp.add(function(ed, e) {
895 var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
897 // Move selection to startCell
900 ed.getBody().style.webkitUserSelect = '';
902 function setPoint(node, start) {
903 var walker = new tinymce.dom.TreeWalker(node, node);
907 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
909 rng.setStart(node, 0);
911 rng.setEnd(node, node.nodeValue.length);
917 if (node.nodeName == 'BR') {
919 rng.setStartBefore(node);
921 rng.setEndBefore(node);
925 } while (node = (start ? walker.next() : walker.prev()));
928 // Try to expand text selection as much as we can only Gecko supports cell selection
929 selectedCells = dom.select('td.mceSelected,th.mceSelected');
930 if (selectedCells.length > 0) {
931 rng = dom.createRng();
932 node = selectedCells[0];
933 endNode = selectedCells[selectedCells.length - 1];
936 walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
939 if (node.nodeName == 'TD' || node.nodeName == 'TH') {
940 if (!dom.hasClass(node, 'mceSelected'))
945 } while (node = walker.next());
953 startCell = tableGrid = startTable = null;
957 ed.onKeyUp.add(function(ed, e) {
962 if (ed && ed.plugins.contextmenu) {
963 ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
964 var sm, se = ed.selection, el = se.getNode() || ed.getBody();
966 if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
969 if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
970 m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
971 m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
975 if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
976 m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
980 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
981 m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
982 m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
986 sm = m.addMenu({title : 'table.cell'});
987 sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
988 sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
989 sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
992 sm = m.addMenu({title : 'table.row'});
993 sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
994 sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
995 sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
996 sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
998 sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
999 sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
1000 sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
1001 sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
1004 sm = m.addMenu({title : 'table.col'});
1005 sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
1006 sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
1007 sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
1009 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
1013 // Fixes an issue on Gecko where it's impossible to place the caret behind a table
1014 // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
1015 if (!tinymce.isIE) {
1016 function fixTableCaretPos() {
1019 // Skip empty text nodes form the end
1020 for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
1022 if (last && last.nodeName == 'TABLE')
1023 ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
1026 // Fixes an bug where it's impossible to place the caret before a table in Gecko
1027 // this fix solves it by detecting when the caret is at the beginning of such a table
1028 // and then manually moves the caret infront of the table
1029 if (tinymce.isGecko) {
1030 ed.onKeyDown.add(function(ed, e) {
1031 var rng, table, dom = ed.dom;
1033 // On gecko it's not possible to place the caret before a table
1034 if (e.keyCode == 37 || e.keyCode == 38) {
1035 rng = ed.selection.getRng();
1036 table = dom.getParent(rng.startContainer, 'table');
1038 if (table && ed.getBody().firstChild == table) {
1039 if (isAtStart(rng, table)) {
1040 rng = dom.createRng();
1042 rng.setStartBefore(table);
1043 rng.setEndBefore(table);
1045 ed.selection.setRng(rng);
1054 ed.onKeyUp.add(fixTableCaretPos);
1055 ed.onSetContent.add(fixTableCaretPos);
1056 ed.onVisualAid.add(fixTableCaretPos);
1058 ed.onPreProcess.add(function(ed, o) {
1059 var last = o.node.lastChild;
1061 if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
1062 ed.dom.remove(last);
1069 // Register action commands
1071 mceTableSplitCells : function(grid) {
1075 mceTableMergeCells : function(grid) {
1076 var rowSpan, colSpan, cell;
1078 cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
1080 rowSpan = cell.rowSpan;
1081 colSpan = cell.colSpan;
1084 if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
1086 url : url + '/merge_cells.htm',
1087 width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
1088 height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
1093 onaction : function(data) {
1094 grid.merge(cell, data.cols, data.rows);
1102 mceTableInsertRowBefore : function(grid) {
1103 grid.insertRow(true);
1106 mceTableInsertRowAfter : function(grid) {
1110 mceTableInsertColBefore : function(grid) {
1111 grid.insertCol(true);
1114 mceTableInsertColAfter : function(grid) {
1118 mceTableDeleteCol : function(grid) {
1122 mceTableDeleteRow : function(grid) {
1126 mceTableCutRow : function(grid) {
1127 clipboardRows = grid.cutRows();
1130 mceTableCopyRow : function(grid) {
1131 clipboardRows = grid.copyRows();
1134 mceTablePasteRowBefore : function(grid) {
1135 grid.pasteRows(clipboardRows, true);
1138 mceTablePasteRowAfter : function(grid) {
1139 grid.pasteRows(clipboardRows);
1142 mceTableDelete : function(grid) {
1145 }, function(func, name) {
1146 ed.addCommand(name, function() {
1147 var grid = createTableGrid();
1151 ed.execCommand('mceRepaint');
1157 // Register dialog commands
1159 mceInsertTable : function(val) {
1161 url : url + '/table.htm',
1162 width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
1163 height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
1167 action : val ? val.action : 0
1171 mceTableRowProps : function() {
1173 url : url + '/row.htm',
1174 width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
1175 height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
1182 mceTableCellProps : function() {
1184 url : url + '/cell.htm',
1185 width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
1186 height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
1192 }, function(func, name) {
1193 ed.addCommand(name, function(ui, val) {
1201 tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);