comparison media/js/tiny_mce/plugins/table/editor_plugin_src.js @ 183:149c3567fec1

Updated to TinyMCE version 3.3.2. This is for #57.
author Brian Neal <bgneal@gmail.com>
date Sun, 28 Mar 2010 21:47:48 +0000
parents a5b4c5ce0658
children 237710206167
comparison
equal deleted inserted replaced
182:5c889b587416 183:149c3567fec1
1 /** 1 /**
2 * $Id: editor_plugin_src.js 953 2008-11-04 10:16:50Z spocke $ 2 * editor_plugin_src.js
3 * 3 *
4 * @author Moxiecode 4 * Copyright 2009, Moxiecode Systems AB
5 * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. 5 * Released under LGPL License.
6 *
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
6 */ 9 */
7 10
8 (function() { 11 (function(tinymce) {
9 var each = tinymce.each; 12 var each = tinymce.each;
13
14 /**
15 * Table Grid class.
16 */
17 function TableGrid(table, dom, selection) {
18 var grid, startPos, endPos, selectedCell;
19
20 buildGrid();
21 selectedCell = dom.getParent(selection.getStart(), 'th,td');
22 if (selectedCell) {
23 startPos = getPos(selectedCell);
24 endPos = findEndPos();
25 selectedCell = getCell(startPos.x, startPos.y);
26 }
27
28 function buildGrid() {
29 var startY = 0;
30
31 grid = [];
32
33 each(['thead', 'tbody', 'tfoot'], function(part) {
34 var rows = dom.select(part + ' tr', table);
35
36 each(rows, function(tr, y) {
37 y += startY;
38
39 each(dom.select('td,th', tr), function(td, x) {
40 var x2, y2, rowspan, colspan;
41
42 // Skip over existing cells produced by rowspan
43 if (grid[y]) {
44 while (grid[y][x])
45 x++;
46 }
47
48 // Get col/rowspan from cell
49 rowspan = getSpanVal(td, 'rowspan');
50 colspan = getSpanVal(td, 'colspan');
51
52 // Fill out rowspan/colspan right and down
53 for (y2 = y; y2 < y + rowspan; y2++) {
54 if (!grid[y2])
55 grid[y2] = [];
56
57 for (x2 = x; x2 < x + colspan; x2++) {
58 grid[y2][x2] = {
59 part : part,
60 real : y2 == y && x2 == x,
61 elm : td,
62 rowspan : rowspan,
63 colspan : colspan
64 };
65 }
66 }
67 });
68 });
69
70 startY += rows.length;
71 });
72 };
73
74 function getCell(x, y) {
75 var row;
76
77 row = grid[y];
78 if (row)
79 return row[x];
80 };
81
82 function getSpanVal(td, name) {
83 return parseInt(td.getAttribute(name) || 1);
84 };
85
86 function isCellSelected(cell) {
87 return dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell;
88 };
89
90 function getSelectedRows() {
91 var rows = [];
92
93 each(table.rows, function(row) {
94 each(row.cells, function(cell) {
95 if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
96 rows.push(row);
97 return false;
98 }
99 });
100 });
101
102 return rows;
103 };
104
105 function deleteTable() {
106 var rng = dom.createRng();
107
108 rng.setStartAfter(table);
109 rng.setEndAfter(table);
110
111 selection.setRng(rng);
112
113 dom.remove(table);
114 };
115
116 function cloneCell(cell) {
117 var formatNode;
118
119 // Clone formats
120 tinymce.walk(cell, function(node) {
121 var curNode;
122
123 if (node.nodeType == 3) {
124 each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
125 node = node.cloneNode(false);
126
127 if (!formatNode)
128 formatNode = curNode = node;
129 else if (curNode)
130 curNode.appendChild(node);
131
132 curNode = node;
133 });
134
135 // Add something to the inner node
136 if (curNode)
137 curNode.innerHTML = tinymce.isIE ? '&nbsp;' : '<br _mce_bogus="1" />';
138
139 return false;
140 }
141 }, 'childNodes');
142
143 cell = cell.cloneNode(false);
144 cell.rowSpan = cell.colSpan = 1;
145
146 if (formatNode) {
147 cell.appendChild(formatNode);
148 } else {
149 if (!tinymce.isIE)
150 cell.innerHTML = '<br _mce_bogus="1" />';
151 }
152
153 return cell;
154 };
155
156 function cleanup() {
157 var rng = dom.createRng();
158
159 // Empty rows
160 each(dom.select('tr', table), function(tr) {
161 if (tr.cells.length == 0)
162 dom.remove(tr);
163 });
164
165 // Empty table
166 if (dom.select('tr', table).length == 0) {
167 rng.setStartAfter(table);
168 rng.setEndAfter(table);
169 selection.setRng(rng);
170 dom.remove(table);
171 return;
172 }
173
174 // Empty header/body/footer
175 each(dom.select('thead,tbody,tfoot', table), function(part) {
176 if (part.rows.length == 0)
177 dom.remove(part);
178 });
179
180 // Restore selection to start position if it still exists
181 buildGrid();
182
183 // Restore the selection to the closest table position
184 row = grid[Math.min(grid.length - 1, startPos.y)];
185 if (row) {
186 selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
187 selection.collapse(true);
188 }
189 };
190
191 function fillLeftDown(x, y, rows, cols) {
192 var tr, x2, r, c, cell;
193
194 tr = grid[y][x].elm.parentNode;
195 for (r = 1; r <= rows; r++) {
196 tr = dom.getNext(tr, 'tr');
197
198 if (tr) {
199 // Loop left to find real cell
200 for (x2 = x; x2 >= 0; x2--) {
201 cell = grid[y + r][x2].elm;
202
203 if (cell.parentNode == tr) {
204 // Append clones after
205 for (c = 1; c <= cols; c++)
206 dom.insertAfter(cloneCell(cell), cell);
207
208 break;
209 }
210 }
211
212 if (x2 == -1) {
213 // Insert nodes before first cell
214 for (c = 1; c <= cols; c++)
215 tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
216 }
217 }
218 }
219 };
220
221 function split() {
222 each(grid, function(row, y) {
223 each(row, function(cell, x) {
224 var colSpan, rowSpan, newCell, i;
225
226 if (isCellSelected(cell)) {
227 cell = cell.elm;
228 colSpan = getSpanVal(cell, 'colspan');
229 rowSpan = getSpanVal(cell, 'rowspan');
230
231 if (colSpan > 1 || rowSpan > 1) {
232 cell.colSpan = cell.rowSpan = 1;
233
234 // Insert cells right
235 for (i = 0; i < colSpan - 1; i++)
236 dom.insertAfter(cloneCell(cell), cell);
237
238 fillLeftDown(x, y, rowSpan - 1, colSpan);
239 }
240 }
241 });
242 });
243 };
244
245 function merge(cell, cols, rows) {
246 var startX, startY, endX, endY, x, y, startCell, endCell, cell, children;
247
248 // Use specified cell and cols/rows
249 if (cell) {
250 pos = getPos(cell);
251 startX = pos.x;
252 startY = pos.y;
253 endX = startX + (cols - 1);
254 endY = startY + (rows - 1);
255 } else {
256 // Use selection
257 startX = startPos.x;
258 startY = startPos.y;
259 endX = endPos.x;
260 endY = endPos.y;
261 }
262
263 // Find start/end cells
264 startCell = getCell(startX, startY);
265 endCell = getCell(endX, endY);
266
267 // Check if the cells exists and if they are of the same part for example tbody = tbody
268 if (startCell && endCell && startCell.part == endCell.part) {
269 // Split and rebuild grid
270 split();
271 buildGrid();
272
273 // Set row/col span to start cell
274 startCell = getCell(startX, startY).elm;
275 startCell.colSpan = (endX - startX) + 1;
276 startCell.rowSpan = (endY - startY) + 1;
277
278 // Remove other cells and add it's contents to the start cell
279 for (y = startY; y <= endY; y++) {
280 for (x = startX; x <= endX; x++) {
281 cell = grid[y][x].elm;
282
283 if (cell != startCell) {
284 // Move children to startCell
285 children = tinymce.grep(cell.childNodes);
286 each(children, function(node, i) {
287 // Jump over last BR element
288 if (node.nodeName != 'BR' || i != children.length - 1)
289 startCell.appendChild(node);
290 });
291
292 // Remove cell
293 dom.remove(cell);
294 }
295 }
296 }
297
298 // Remove empty rows etc and restore caret location
299 cleanup();
300 }
301 };
302
303 function insertRow(before) {
304 var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell;
305
306 // Find first/last row
307 each(grid, function(row, y) {
308 each(row, function(cell, x) {
309 if (isCellSelected(cell)) {
310 cell = cell.elm;
311 rowElm = cell.parentNode;
312 newRow = rowElm.cloneNode(false);
313 posY = y;
314
315 if (before)
316 return false;
317 }
318 });
319
320 if (before)
321 return !posY;
322 });
323
324 for (x = 0; x < grid[0].length; x++) {
325 cell = grid[posY][x].elm;
326
327 if (cell != lastCell) {
328 if (!before) {
329 rowSpan = getSpanVal(cell, 'rowspan');
330 if (rowSpan > 1) {
331 cell.rowSpan = rowSpan + 1;
332 continue;
333 }
334 } else {
335 // Check if cell above can be expanded
336 if (posY > 0 && grid[posY - 1][x]) {
337 otherCell = grid[posY - 1][x].elm;
338 rowSpan = getSpanVal(otherCell, 'rowspan');
339 if (rowSpan > 1) {
340 otherCell.rowSpan = rowSpan + 1;
341 continue;
342 }
343 }
344 }
345
346 // Insert new cell into new row
347 newCell = cloneCell(cell)
348 newCell.colSpan = cell.colSpan;
349 newRow.appendChild(newCell);
350
351 lastCell = cell;
352 }
353 }
354
355 if (newRow.hasChildNodes()) {
356 if (!before)
357 dom.insertAfter(newRow, rowElm);
358 else
359 rowElm.parentNode.insertBefore(newRow, rowElm);
360 }
361 };
362
363 function insertCol(before) {
364 var posX, lastCell;
365
366 // Find first/last column
367 each(grid, function(row, y) {
368 each(row, function(cell, x) {
369 if (isCellSelected(cell)) {
370 posX = x;
371
372 if (before)
373 return false;
374 }
375 });
376
377 if (before)
378 return !posX;
379 });
380
381 each(grid, function(row, y) {
382 var cell = row[posX].elm, rowSpan, colSpan;
383
384 if (cell != lastCell) {
385 colSpan = getSpanVal(cell, 'colspan');
386 rowSpan = getSpanVal(cell, 'rowspan');
387
388 if (colSpan == 1) {
389 if (!before) {
390 dom.insertAfter(cloneCell(cell), cell);
391 fillLeftDown(posX, y, rowSpan - 1, colSpan);
392 } else {
393 cell.parentNode.insertBefore(cloneCell(cell), cell);
394 fillLeftDown(posX, y, rowSpan - 1, colSpan);
395 }
396 } else
397 cell.colSpan++;
398
399 lastCell = cell;
400 }
401 });
402 };
403
404 function deleteCols() {
405 var cols = [];
406
407 // Get selected column indexes
408 each(grid, function(row, y) {
409 each(row, function(cell, x) {
410 if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) {
411 each(grid, function(row) {
412 var cell = row[x].elm, colSpan;
413
414 colSpan = getSpanVal(cell, 'colspan');
415
416 if (colSpan > 1)
417 cell.colSpan = colSpan - 1;
418 else
419 dom.remove(cell);
420 });
421
422 cols.push(x);
423 }
424 });
425 });
426
427 cleanup();
428 };
429
430 function deleteRows() {
431 var rows;
432
433 function deleteRow(tr) {
434 var nextTr, pos, lastCell;
435
436 nextTr = dom.getNext(tr, 'tr');
437
438 // Move down row spanned cells
439 each(tr.cells, function(cell) {
440 var rowSpan = getSpanVal(cell, 'rowspan');
441
442 if (rowSpan > 1) {
443 cell.rowSpan = rowSpan - 1;
444 pos = getPos(cell);
445 fillLeftDown(pos.x, pos.y, 1, 1);
446 }
447 });
448
449 // Delete cells
450 pos = getPos(tr.cells[0]);
451 each(grid[pos.y], function(cell) {
452 var rowSpan;
453
454 cell = cell.elm;
455
456 if (cell != lastCell) {
457 rowSpan = getSpanVal(cell, 'rowspan');
458
459 if (rowSpan <= 1)
460 dom.remove(cell);
461 else
462 cell.rowSpan = rowSpan - 1;
463
464 lastCell = cell;
465 }
466 });
467 };
468
469 // Get selected rows and move selection out of scope
470 rows = getSelectedRows();
471
472 // Delete all selected rows
473 each(rows.reverse(), function(tr) {
474 deleteRow(tr);
475 });
476
477 cleanup();
478 };
479
480 function cutRows() {
481 var rows = getSelectedRows();
482
483 dom.remove(rows);
484 cleanup();
485
486 return rows;
487 };
488
489 function copyRows() {
490 var rows = getSelectedRows();
491
492 each(rows, function(row, i) {
493 rows[i] = row.cloneNode(true);
494 });
495
496 return rows;
497 };
498
499 function pasteRows(rows, before) {
500 var selectedRows = getSelectedRows(),
501 targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
502 targetCellCount = targetRow.cells.length;
503
504 // Calc target cell count
505 each(grid, function(row) {
506 var match;
507
508 targetCellCount = 0;
509 each(row, function(cell, x) {
510 if (cell.real)
511 targetCellCount += cell.colspan;
512
513 if (cell.elm.parentNode == targetRow)
514 match = 1;
515 });
516
517 if (match)
518 return false;
519 });
520
521 if (!before)
522 rows.reverse();
523
524 each(rows, function(row) {
525 var cellCount = row.cells.length, cell;
526
527 // Remove col/rowspans
528 for (i = 0; i < cellCount; i++) {
529 cell = row.cells[i];
530 cell.colSpan = cell.rowSpan = 1;
531 }
532
533 // Needs more cells
534 for (i = cellCount; i < targetCellCount; i++)
535 row.appendChild(cloneCell(row.cells[cellCount - 1]));
536
537 // Needs less cells
538 for (i = targetCellCount; i < cellCount; i++)
539 dom.remove(row.cells[i]);
540
541 // Add before/after
542 if (before)
543 targetRow.parentNode.insertBefore(row, targetRow);
544 else
545 dom.insertAfter(row, targetRow);
546 });
547 };
548
549 function getPos(target) {
550 var pos;
551
552 each(grid, function(row, y) {
553 each(row, function(cell, x) {
554 if (cell.elm == target) {
555 pos = {x : x, y : y};
556 return false;
557 }
558 });
559
560 return !pos;
561 });
562
563 return pos;
564 };
565
566 function setStartCell(cell) {
567 startPos = getPos(cell);
568 };
569
570 function findEndPos() {
571 var pos, maxX, maxY;
572
573 maxX = maxY = 0;
574
575 each(grid, function(row, y) {
576 each(row, function(cell, x) {
577 var colSpan, rowSpan;
578
579 if (isCellSelected(cell)) {
580 cell = grid[y][x];
581
582 if (x > maxX)
583 maxX = x;
584
585 if (y > maxY)
586 maxY = y;
587
588 if (cell.real) {
589 colSpan = cell.colspan - 1;
590 rowSpan = cell.rowspan - 1;
591
592 if (colSpan) {
593 if (x + colSpan > maxX)
594 maxX = x + colSpan;
595 }
596
597 if (rowSpan) {
598 if (y + rowSpan > maxY)
599 maxY = y + rowSpan;
600 }
601 }
602 }
603 });
604 });
605
606 return {x : maxX, y : maxY};
607 };
608
609 function setEndCell(cell) {
610 var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
611
612 endPos = getPos(cell);
613
614 if (startPos && endPos) {
615 // Get start/end positions
616 startX = Math.min(startPos.x, endPos.x);
617 startY = Math.min(startPos.y, endPos.y);
618 endX = Math.max(startPos.x, endPos.x);
619 endY = Math.max(startPos.y, endPos.y);
620
621 // Expand end positon to include spans
622 maxX = endX;
623 maxY = endY;
624
625 // Expand startX
626 for (y = startY; y <= maxY; y++) {
627 cell = grid[y][startX];
628
629 if (!cell.real) {
630 if (startX - (cell.colspan - 1) < startX)
631 startX -= cell.colspan - 1;
632 }
633 }
634
635 // Expand startY
636 for (x = startX; x <= maxX; x++) {
637 cell = grid[startY][x];
638
639 if (!cell.real) {
640 if (startY - (cell.rowspan - 1) < startY)
641 startY -= cell.rowspan - 1;
642 }
643 }
644
645 // Find max X, Y
646 for (y = startY; y <= endY; y++) {
647 for (x = startX; x <= endX; x++) {
648 cell = grid[y][x];
649
650 if (cell.real) {
651 colSpan = cell.colspan - 1;
652 rowSpan = cell.rowspan - 1;
653
654 if (colSpan) {
655 if (x + colSpan > maxX)
656 maxX = x + colSpan;
657 }
658
659 if (rowSpan) {
660 if (y + rowSpan > maxY)
661 maxY = y + rowSpan;
662 }
663 }
664 }
665 }
666
667 // Remove current selection
668 dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
669
670 // Add new selection
671 for (y = startY; y <= maxY; y++) {
672 for (x = startX; x <= maxX; x++)
673 dom.addClass(grid[y][x].elm, 'mceSelected');
674 }
675 }
676 };
677
678 // Expose to public
679 tinymce.extend(this, {
680 deleteTable : deleteTable,
681 split : split,
682 merge : merge,
683 insertRow : insertRow,
684 insertCol : insertCol,
685 deleteCols : deleteCols,
686 deleteRows : deleteRows,
687 cutRows : cutRows,
688 copyRows : copyRows,
689 pasteRows : pasteRows,
690 getPos : getPos,
691 setStartCell : setStartCell,
692 setEndCell : setEndCell
693 });
694 };
10 695
11 tinymce.create('tinymce.plugins.TablePlugin', { 696 tinymce.create('tinymce.plugins.TablePlugin', {
12 init : function(ed, url) { 697 init : function(ed, url) {
13 var t = this; 698 var winMan, clipboardRows;
14 699
15 t.editor = ed; 700 function createTableGrid(node) {
16 t.url = url; 701 var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
702
703 if (tblElm)
704 return new TableGrid(tblElm, ed.dom, selection);
705 };
706
707 function cleanup() {
708 // Restore selection possibilities
709 ed.getBody().style.webkitUserSelect = '';
710 ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
711 };
17 712
18 // Register buttons 713 // Register buttons
19 each([ 714 each([
20 ['table', 'table.desc', 'mceInsertTable', true], 715 ['table', 'table.desc', 'mceInsertTable', true],
21 ['delete_table', 'table.del', 'mceTableDelete'], 716 ['delete_table', 'table.del', 'mceTableDelete'],
31 ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true] 726 ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
32 ], function(c) { 727 ], function(c) {
33 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]}); 728 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
34 }); 729 });
35 730
36 if (ed.getParam('inline_styles')) {
37 // Force move of attribs to styles in strict mode
38 ed.onPreProcess.add(function(ed, o) {
39 var dom = ed.dom;
40
41 each(dom.select('table', o.node), function(n) {
42 var v;
43
44 if (v = dom.getAttrib(n, 'width')) {
45 dom.setStyle(n, 'width', v);
46 dom.setAttrib(n, 'width');
47 }
48
49 if (v = dom.getAttrib(n, 'height')) {
50 dom.setStyle(n, 'height', v);
51 dom.setAttrib(n, 'height');
52 }
53 });
54 });
55 }
56
57 ed.onInit.add(function() {
58 if (ed && ed.plugins.contextmenu) {
59 ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
60 var sm, se = ed.selection, el = se.getNode() || ed.getBody();
61
62 if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th')) {
63 m.removeAll();
64
65 if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
66 m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
67 m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
68 m.addSeparator();
69 }
70
71 if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
72 m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
73 m.addSeparator();
74 }
75
76 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true, value : {action : 'insert'}});
77 m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable', ui : true});
78 m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete', ui : true});
79 m.addSeparator();
80
81 // Cell menu
82 sm = m.addMenu({title : 'table.cell'});
83 sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps', ui : true});
84 sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells', ui : true});
85 sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells', ui : true});
86
87 // Row menu
88 sm = m.addMenu({title : 'table.row'});
89 sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps', ui : true});
90 sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
91 sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
92 sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
93 sm.addSeparator();
94 sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
95 sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
96 sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'});
97 sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'});
98
99 // Column menu
100 sm = m.addMenu({title : 'table.col'});
101 sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
102 sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
103 sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
104 } else
105 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true});
106 });
107 }
108 });
109
110 // Add undo level when new rows are created using the tab key
111 ed.onKeyDown.add(function(ed, e) {
112 if (e.keyCode == 9 && ed.dom.getParent(ed.selection.getNode(), 'TABLE')) {
113 if (!tinymce.isGecko && !tinymce.isOpera) {
114 tinyMCE.execInstanceCommand(ed.editorId, "mceTableMoveToNextRow", true);
115 return tinymce.dom.Event.cancel(e);
116 }
117
118 ed.undoManager.add();
119 }
120 });
121
122 // Select whole table is a table border is clicked 731 // Select whole table is a table border is clicked
123 if (!tinymce.isIE) { 732 if (!tinymce.isIE) {
124 if (ed.getParam('table_selection', true)) { 733 ed.onClick.add(function(ed, e) {
125 ed.onClick.add(function(ed, e) { 734 e = e.target;
126 e = e.target; 735
127 736 if (e.nodeName === 'TABLE')
128 if (e.nodeName === 'TABLE') 737 ed.selection.select(e);
129 ed.selection.select(e); 738 });
130 });
131 }
132 } 739 }
133 740
741 // Handle node change updates
134 ed.onNodeChange.add(function(ed, cm, n) { 742 ed.onNodeChange.add(function(ed, cm, n) {
135 var p = ed.dom.getParent(n, 'td,th,caption'); 743 var p;
136 744
745 n = ed.selection.getStart();
746 p = ed.dom.getParent(n, 'td,th,caption');
137 cm.setActive('table', n.nodeName === 'TABLE' || !!p); 747 cm.setActive('table', n.nodeName === 'TABLE' || !!p);
748
749 // Disable table tools if we are in caption
138 if (p && p.nodeName === 'CAPTION') 750 if (p && p.nodeName === 'CAPTION')
139 p = null; 751 p = 0;
140 752
141 cm.setDisabled('delete_table', !p); 753 cm.setDisabled('delete_table', !p);
142 cm.setDisabled('delete_col', !p); 754 cm.setDisabled('delete_col', !p);
143 cm.setDisabled('delete_table', !p); 755 cm.setDisabled('delete_table', !p);
144 cm.setDisabled('delete_row', !p); 756 cm.setDisabled('delete_row', !p);
146 cm.setDisabled('col_before', !p); 758 cm.setDisabled('col_before', !p);
147 cm.setDisabled('row_after', !p); 759 cm.setDisabled('row_after', !p);
148 cm.setDisabled('row_before', !p); 760 cm.setDisabled('row_before', !p);
149 cm.setDisabled('row_props', !p); 761 cm.setDisabled('row_props', !p);
150 cm.setDisabled('cell_props', !p); 762 cm.setDisabled('cell_props', !p);
151 cm.setDisabled('split_cells', !p || (parseInt(ed.dom.getAttrib(p, 'colspan', '1')) < 2 && parseInt(ed.dom.getAttrib(p, 'rowspan', '1')) < 2)); 763 cm.setDisabled('split_cells', !p);
152 cm.setDisabled('merge_cells', !p); 764 cm.setDisabled('merge_cells', !p);
153 }); 765 });
154 766
155 // Padd empty table cells 767 ed.onInit.add(function(ed) {
156 if (!tinymce.isIE) { 768 var startTable, startCell, dom = ed.dom, tableGrid;
157 ed.onBeforeSetContent.add(function(ed, o) { 769
158 if (o.initial) 770 winMan = ed.windowManager;
159 o.content = o.content.replace(/<(td|th)([^>]+|)>\s*<\/(td|th)>/g, tinymce.isOpera ? '<$1$2>&nbsp;</$1>' : '<$1$2><br mce_bogus="1" /></$1>'); 771
160 }); 772 // Add cell selection logic
161 } 773 ed.onMouseDown.add(function(ed, e) {
162 }, 774 if (e.button != 2) {
163 775 cleanup();
164 execCommand : function(cmd, ui, val) { 776
165 var ed = this.editor, b; 777 startCell = dom.getParent(e.target, 'td,th');
166 778 startTable = dom.getParent(startCell, 'table');
167 // Is table command 779 }
168 switch (cmd) { 780 });
169 case "mceTableMoveToNextRow": 781
170 case "mceInsertTable": 782 dom.bind(ed.getDoc(), 'mouseover', function(e) {
171 case "mceTableRowProps": 783 var sel, table, target = e.target;
172 case "mceTableCellProps": 784
173 case "mceTableSplitCells": 785 if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
174 case "mceTableMergeCells": 786 table = dom.getParent(target, 'table');
175 case "mceTableInsertRowBefore": 787 if (table == startTable) {
176 case "mceTableInsertRowAfter": 788 if (!tableGrid) {
177 case "mceTableDeleteRow": 789 tableGrid = createTableGrid(table);
178 case "mceTableInsertColBefore": 790 tableGrid.setStartCell(startCell);
179 case "mceTableInsertColAfter": 791
180 case "mceTableDeleteCol": 792 ed.getBody().style.webkitUserSelect = 'none';
181 case "mceTableCutRow": 793 }
182 case "mceTableCopyRow": 794
183 case "mceTablePasteRowBefore": 795 tableGrid.setEndCell(target);
184 case "mceTablePasteRowAfter":
185 case "mceTableDelete":
186 ed.execCommand('mceBeginUndoLevel');
187 this._doExecCommand(cmd, ui, val);
188 ed.execCommand('mceEndUndoLevel');
189
190 return true;
191 }
192
193 // Pass to next handler in chain
194 return false;
195 },
196
197 getInfo : function() {
198 return {
199 longname : 'Tables',
200 author : 'Moxiecode Systems AB',
201 authorurl : 'http://tinymce.moxiecode.com',
202 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/table',
203 version : tinymce.majorVersion + "." + tinymce.minorVersion
204 };
205 },
206
207 // Private plugin internal methods
208
209 /**
210 * Executes the table commands.
211 */
212 _doExecCommand : function(command, user_interface, value) {
213 var inst = this.editor, ed = inst, url = this.url;
214 var focusElm = inst.selection.getNode();
215 var trElm = inst.dom.getParent(focusElm, "tr");
216 var tdElm = inst.dom.getParent(focusElm, "td,th");
217 var tableElm = inst.dom.getParent(focusElm, "table");
218 var doc = inst.contentWindow.document;
219 var tableBorder = tableElm ? tableElm.getAttribute("border") : "";
220
221 // Get first TD if no TD found
222 if (trElm && tdElm == null)
223 tdElm = trElm.cells[0];
224
225 function inArray(ar, v) {
226 for (var i=0; i<ar.length; i++) {
227 // Is array
228 if (ar[i].length > 0 && inArray(ar[i], v))
229 return true;
230
231 // Found value
232 if (ar[i] == v)
233 return true;
234 }
235
236 return false;
237 }
238
239 function select(dx, dy) {
240 var td;
241
242 grid = getTableGrid(tableElm);
243 dx = dx || 0;
244 dy = dy || 0;
245 dx = Math.max(cpos.cellindex + dx, 0);
246 dy = Math.max(cpos.rowindex + dy, 0);
247
248 // Recalculate grid and select
249 inst.execCommand('mceRepaint');
250 td = getCell(grid, dy, dx);
251
252 if (td) {
253 inst.selection.select(td.firstChild || td);
254 inst.selection.collapse(1);
255 }
256 };
257
258 function makeTD() {
259 var newTD = doc.createElement("td");
260
261 if (!tinymce.isIE)
262 newTD.innerHTML = '<br mce_bogus="1"/>';
263 }
264
265 function getColRowSpan(td) {
266 var colspan = inst.dom.getAttrib(td, "colspan");
267 var rowspan = inst.dom.getAttrib(td, "rowspan");
268
269 colspan = colspan == "" ? 1 : parseInt(colspan);
270 rowspan = rowspan == "" ? 1 : parseInt(rowspan);
271
272 return {colspan : colspan, rowspan : rowspan};
273 }
274
275 function getCellPos(grid, td) {
276 var x, y;
277
278 for (y=0; y<grid.length; y++) {
279 for (x=0; x<grid[y].length; x++) {
280 if (grid[y][x] == td)
281 return {cellindex : x, rowindex : y};
282 }
283 }
284
285 return null;
286 }
287
288 function getCell(grid, row, col) {
289 if (grid[row] && grid[row][col])
290 return grid[row][col];
291
292 return null;
293 }
294
295 function getNextCell(table, cell) {
296 var cells = [], x = 0, i, j, cell, nextCell;
297
298 for (i = 0; i < table.rows.length; i++)
299 for (j = 0; j < table.rows[i].cells.length; j++, x++)
300 cells[x] = table.rows[i].cells[j];
301
302 for (i = 0; i < cells.length; i++)
303 if (cells[i] == cell)
304 if (nextCell = cells[i+1])
305 return nextCell;
306 }
307
308 function getTableGrid(table) {
309 var grid = [], rows = table.rows, x, y, td, sd, xstart, x2, y2;
310
311 for (y=0; y<rows.length; y++) {
312 for (x=0; x<rows[y].cells.length; x++) {
313 td = rows[y].cells[x];
314 sd = getColRowSpan(td);
315
316 // All ready filled
317 for (xstart = x; grid[y] && grid[y][xstart]; xstart++) ;
318
319 // Fill box
320 for (y2=y; y2<y+sd['rowspan']; y2++) {
321 if (!grid[y2])
322 grid[y2] = [];
323
324 for (x2=xstart; x2<xstart+sd['colspan']; x2++)
325 grid[y2][x2] = td;
326 } 796 }
327 } 797
328 } 798 // Remove current selection
329 799 sel = ed.selection.getSel();
330 return grid; 800
331 } 801 if (sel.removeAllRanges)
332 802 sel.removeAllRanges();
333 function trimRow(table, tr, td, new_tr) { 803 else
334 var grid = getTableGrid(table), cpos = getCellPos(grid, td); 804 sel.empty();
335 var cells, lastElm; 805
336 806 e.preventDefault();
337 // Time to crop away some 807 }
338 if (new_tr.cells.length != tr.childNodes.length) { 808 });
339 cells = tr.childNodes; 809
340 lastElm = null; 810 ed.onMouseUp.add(function(ed, e) {
341 811 var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
342 for (var x=0; td = getCell(grid, cpos.rowindex, x); x++) { 812
343 var remove = true; 813 // Move selection to startCell
344 var sd = getColRowSpan(td); 814 if (startCell) {
345 815 if (tableGrid)
346 // Remove due to rowspan 816 ed.getBody().style.webkitUserSelect = '';
347 if (inArray(cells, td)) { 817
348 new_tr.childNodes[x]._delete = true; 818 function setPoint(node, start) {
349 } else if ((lastElm == null || td != lastElm) && sd.colspan > 1) { // Remove due to colspan 819 var walker = new tinymce.dom.TreeWalker(node, node);
350 for (var i=x; i<x+td.colSpan; i++) 820
351 new_tr.childNodes[i]._delete = true; 821 do {
822 // Text node
823 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
824 if (start)
825 rng.setStart(node, 0);
826 else
827 rng.setEnd(node, node.nodeValue.length);
828
829 return;
830 }
831
832 // BR element
833 if (node.nodeName == 'BR') {
834 if (start)
835 rng.setStartBefore(node);
836 else
837 rng.setEndBefore(node);
838
839 return;
840 }
841 } while (node = (start ? walker.next() : walker.prev()));
842 };
843
844 // Try to expand text selection as much as we can only Gecko supports cell selection
845 selectedCells = dom.select('td.mceSelected,th.mceSelected');
846 if (selectedCells.length > 0) {
847 rng = dom.createRng();
848 node = selectedCells[0];
849 endNode = selectedCells[selectedCells.length - 1];
850
851 setPoint(node, 1);
852 walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
853
854 do {
855 if (node.nodeName == 'TD' || node.nodeName == 'TH') {
856 if (!dom.hasClass(node, 'mceSelected'))
857 break;
858
859 lastNode = node;
860 }
861 } while (node = walker.next());
862
863 setPoint(lastNode);
864
865 sel.setRng(rng);
352 } 866 }
353 867
354 if ((lastElm == null || td != lastElm) && sd.rowspan > 1) 868 ed.nodeChanged();
355 td.rowSpan = sd.rowspan + 1; 869 startCell = tableGrid = startTable = null;
356 870 }
357 lastElm = td; 871 });
358 } 872
359 873 ed.onKeyUp.add(function(ed, e) {
360 deleteMarked(tableElm); 874 cleanup();
361 } 875 });
362 } 876
363 877 // Add context menu
364 function prevElm(node, name) { 878 if (ed && ed.plugins.contextmenu) {
365 while ((node = node.previousSibling) != null) { 879 ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
366 if (node.nodeName == name) 880 var sm, se = ed.selection, el = se.getNode() || ed.getBody();
367 return node; 881
368 } 882 if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th')) {
369 883 m.removeAll();
370 return null; 884
371 } 885 if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
372 886 m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
373 function nextElm(node, names) { 887 m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
374 var namesAr = names.split(','); 888 m.addSeparator();
375
376 while ((node = node.nextSibling) != null) {
377 for (var i=0; i<namesAr.length; i++) {
378 if (node.nodeName.toLowerCase() == namesAr[i].toLowerCase() )
379 return node;
380 }
381 }
382
383 return null;
384 }
385
386 function deleteMarked(tbl) {
387 if (tbl.rows == 0)
388 return;
389
390 var tr = tbl.rows[0];
391 do {
392 var next = nextElm(tr, "TR");
393
394 // Delete row
395 if (tr._delete) {
396 tr.parentNode.removeChild(tr);
397 continue;
398 }
399
400 // Delete cells
401 var td = tr.cells[0];
402 if (td.cells > 1) {
403 do {
404 var nexttd = nextElm(td, "TD,TH");
405
406 if (td._delete)
407 td.parentNode.removeChild(td);
408 } while ((td = nexttd) != null);
409 }
410 } while ((tr = next) != null);
411 }
412
413 function addRows(td_elm, tr_elm, rowspan) {
414 // Add rows
415 td_elm.rowSpan = 1;
416 var trNext = nextElm(tr_elm, "TR");
417 for (var i=1; i<rowspan && trNext; i++) {
418 var newTD = doc.createElement("td");
419
420 if (!tinymce.isIE)
421 newTD.innerHTML = '<br mce_bogus="1"/>';
422
423 if (tinymce.isIE)
424 trNext.insertBefore(newTD, trNext.cells(td_elm.cellIndex));
425 else
426 trNext.insertBefore(newTD, trNext.cells[td_elm.cellIndex]);
427
428 trNext = nextElm(trNext, "TR");
429 }
430 }
431
432 function copyRow(doc, table, tr) {
433 var grid = getTableGrid(table);
434 var newTR = tr.cloneNode(false);
435 var cpos = getCellPos(grid, tr.cells[0]);
436 var lastCell = null;
437 var tableBorder = inst.dom.getAttrib(table, "border");
438 var tdElm = null;
439
440 for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
441 var newTD = null;
442
443 if (lastCell != tdElm) {
444 for (var i=0; i<tr.cells.length; i++) {
445 if (tdElm == tr.cells[i]) {
446 newTD = tdElm.cloneNode(true);
447 break;
448 } 889 }
449 } 890
450 } 891 if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
451 892 m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
452 if (newTD == null) { 893 m.addSeparator();
453 newTD = doc.createElement("td");
454
455 if (!tinymce.isIE)
456 newTD.innerHTML = '<br mce_bogus="1"/>';
457 }
458
459 // Reset col/row span
460 newTD.colSpan = 1;
461 newTD.rowSpan = 1;
462
463 newTR.appendChild(newTD);
464
465 lastCell = tdElm;
466 }
467
468 return newTR;
469 }
470
471 // ---- Commands -----
472
473 // Handle commands
474 switch (command) {
475 case "mceTableMoveToNextRow":
476 var nextCell = getNextCell(tableElm, tdElm);
477
478 if (!nextCell) {
479 inst.execCommand("mceTableInsertRowAfter", tdElm);
480 nextCell = getNextCell(tableElm, tdElm);
481 }
482
483 inst.selection.select(nextCell);
484 inst.selection.collapse(true);
485
486 return true;
487
488 case "mceTableRowProps":
489 if (trElm == null)
490 return true;
491
492 if (user_interface) {
493 inst.windowManager.open({
494 url : url + '/row.htm',
495 width : 400 + parseInt(inst.getLang('table.rowprops_delta_width', 0)),
496 height : 295 + parseInt(inst.getLang('table.rowprops_delta_height', 0)),
497 inline : 1
498 }, {
499 plugin_url : url
500 });
501 }
502
503 return true;
504
505 case "mceTableCellProps":
506 if (tdElm == null)
507 return true;
508
509 if (user_interface) {
510 inst.windowManager.open({
511 url : url + '/cell.htm',
512 width : 400 + parseInt(inst.getLang('table.cellprops_delta_width', 0)),
513 height : 295 + parseInt(inst.getLang('table.cellprops_delta_height', 0)),
514 inline : 1
515 }, {
516 plugin_url : url
517 });
518 }
519
520 return true;
521
522 case "mceInsertTable":
523 if (user_interface) {
524 inst.windowManager.open({
525 url : url + '/table.htm',
526 width : 400 + parseInt(inst.getLang('table.table_delta_width', 0)),
527 height : 320 + parseInt(inst.getLang('table.table_delta_height', 0)),
528 inline : 1
529 }, {
530 plugin_url : url,
531 action : value ? value.action : 0
532 });
533 }
534
535 return true;
536
537 case "mceTableDelete":
538 var table = inst.dom.getParent(inst.selection.getNode(), "table");
539 if (table) {
540 table.parentNode.removeChild(table);
541 inst.execCommand('mceRepaint');
542 }
543 return true;
544
545 case "mceTableSplitCells":
546 case "mceTableMergeCells":
547 case "mceTableInsertRowBefore":
548 case "mceTableInsertRowAfter":
549 case "mceTableDeleteRow":
550 case "mceTableInsertColBefore":
551 case "mceTableInsertColAfter":
552 case "mceTableDeleteCol":
553 case "mceTableCutRow":
554 case "mceTableCopyRow":
555 case "mceTablePasteRowBefore":
556 case "mceTablePasteRowAfter":
557 // No table just return (invalid command)
558 if (!tableElm)
559 return true;
560
561 // Table has a tbody use that reference
562 // Changed logic by ApTest 2005.07.12 (www.aptest.com)
563 // Now lookk at the focused element and take its parentNode. That will be a tbody or a table.
564 if (trElm && tableElm != trElm.parentNode)
565 tableElm = trElm.parentNode;
566
567 if (tableElm && trElm) {
568 switch (command) {
569 case "mceTableCutRow":
570 if (!trElm || !tdElm)
571 return true;
572
573 inst.tableRowClipboard = copyRow(doc, tableElm, trElm);
574 inst.execCommand("mceTableDeleteRow");
575 break;
576
577 case "mceTableCopyRow":
578 if (!trElm || !tdElm)
579 return true;
580
581 inst.tableRowClipboard = copyRow(doc, tableElm, trElm);
582 break;
583
584 case "mceTablePasteRowBefore":
585 if (!trElm || !tdElm)
586 return true;
587
588 var newTR = inst.tableRowClipboard.cloneNode(true);
589
590 var prevTR = prevElm(trElm, "TR");
591 if (prevTR != null)
592 trimRow(tableElm, prevTR, prevTR.cells[0], newTR);
593
594 trElm.parentNode.insertBefore(newTR, trElm);
595 break;
596
597 case "mceTablePasteRowAfter":
598 if (!trElm || !tdElm)
599 return true;
600
601 var nextTR = nextElm(trElm, "TR");
602 var newTR = inst.tableRowClipboard.cloneNode(true);
603
604 trimRow(tableElm, trElm, tdElm, newTR);
605
606 if (nextTR == null)
607 trElm.parentNode.appendChild(newTR);
608 else
609 nextTR.parentNode.insertBefore(newTR, nextTR);
610
611 break;
612
613 case "mceTableInsertRowBefore":
614 if (!trElm || !tdElm)
615 return true;
616
617 var grid = getTableGrid(tableElm);
618 var cpos = getCellPos(grid, tdElm);
619 var newTR = doc.createElement("tr");
620 var lastTDElm = null;
621
622 cpos.rowindex--;
623 if (cpos.rowindex < 0)
624 cpos.rowindex = 0;
625
626 // Create cells
627 for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
628 if (tdElm != lastTDElm) {
629 var sd = getColRowSpan(tdElm);
630
631 if (sd['rowspan'] == 1) {
632 var newTD = doc.createElement("td");
633
634 if (!tinymce.isIE)
635 newTD.innerHTML = '<br mce_bogus="1"/>';
636
637 newTD.colSpan = tdElm.colSpan;
638
639 newTR.appendChild(newTD);
640 } else
641 tdElm.rowSpan = sd['rowspan'] + 1;
642
643 lastTDElm = tdElm;
644 }
645 }
646
647 trElm.parentNode.insertBefore(newTR, trElm);
648 select(0, 1);
649 break;
650
651 case "mceTableInsertRowAfter":
652 if (!trElm || !tdElm)
653 return true;
654
655 var grid = getTableGrid(tableElm);
656 var cpos = getCellPos(grid, tdElm);
657 var newTR = doc.createElement("tr");
658 var lastTDElm = null;
659
660 // Create cells
661 for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
662 if (tdElm != lastTDElm) {
663 var sd = getColRowSpan(tdElm);
664
665 if (sd['rowspan'] == 1) {
666 var newTD = doc.createElement("td");
667
668 if (!tinymce.isIE)
669 newTD.innerHTML = '<br mce_bogus="1"/>';
670
671 newTD.colSpan = tdElm.colSpan;
672
673 newTR.appendChild(newTD);
674 } else
675 tdElm.rowSpan = sd['rowspan'] + 1;
676
677 lastTDElm = tdElm;
678 }
679 }
680
681 if (newTR.hasChildNodes()) {
682 var nextTR = nextElm(trElm, "TR");
683 if (nextTR)
684 nextTR.parentNode.insertBefore(newTR, nextTR);
685 else
686 tableElm.appendChild(newTR);
687 }
688
689 select(0, 1);
690 break;
691
692 case "mceTableDeleteRow":
693 if (!trElm || !tdElm)
694 return true;
695
696 var grid = getTableGrid(tableElm);
697 var cpos = getCellPos(grid, tdElm);
698
699 // Only one row, remove whole table
700 if (grid.length == 1 && tableElm.nodeName == 'TBODY') {
701 inst.dom.remove(inst.dom.getParent(tableElm, "table"));
702 return true;
703 }
704
705 // Move down row spanned cells
706 var cells = trElm.cells;
707 var nextTR = nextElm(trElm, "TR");
708 for (var x=0; x<cells.length; x++) {
709 if (cells[x].rowSpan > 1) {
710 var newTD = cells[x].cloneNode(true);
711 var sd = getColRowSpan(cells[x]);
712
713 newTD.rowSpan = sd.rowspan - 1;
714
715 var nextTD = nextTR.cells[x];
716
717 if (nextTD == null)
718 nextTR.appendChild(newTD);
719 else
720 nextTR.insertBefore(newTD, nextTD);
721 }
722 }
723
724 // Delete cells
725 var lastTDElm = null;
726 for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
727 if (tdElm != lastTDElm) {
728 var sd = getColRowSpan(tdElm);
729
730 if (sd.rowspan > 1) {
731 tdElm.rowSpan = sd.rowspan - 1;
732 } else {
733 trElm = tdElm.parentNode;
734
735 if (trElm.parentNode)
736 trElm._delete = true;
737 }
738
739 lastTDElm = tdElm;
740 }
741 }
742
743 deleteMarked(tableElm);
744
745 select(0, -1);
746 break;
747
748 case "mceTableInsertColBefore":
749 if (!trElm || !tdElm)
750 return true;
751
752 var grid = getTableGrid(inst.dom.getParent(tableElm, "table"));
753 var cpos = getCellPos(grid, tdElm);
754 var lastTDElm = null;
755
756 for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
757 if (tdElm != lastTDElm) {
758 var sd = getColRowSpan(tdElm);
759
760 if (sd['colspan'] == 1) {
761 var newTD = doc.createElement(tdElm.nodeName);
762
763 if (!tinymce.isIE)
764 newTD.innerHTML = '<br mce_bogus="1"/>';
765
766 newTD.rowSpan = tdElm.rowSpan;
767
768 tdElm.parentNode.insertBefore(newTD, tdElm);
769 } else
770 tdElm.colSpan++;
771
772 lastTDElm = tdElm;
773 }
774 }
775
776 select();
777 break;
778
779 case "mceTableInsertColAfter":
780 if (!trElm || !tdElm)
781 return true;
782
783 var grid = getTableGrid(inst.dom.getParent(tableElm, "table"));
784 var cpos = getCellPos(grid, tdElm);
785 var lastTDElm = null;
786
787 for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
788 if (tdElm != lastTDElm) {
789 var sd = getColRowSpan(tdElm);
790
791 if (sd['colspan'] == 1) {
792 var newTD = doc.createElement(tdElm.nodeName);
793
794 if (!tinymce.isIE)
795 newTD.innerHTML = '<br mce_bogus="1"/>';
796
797 newTD.rowSpan = tdElm.rowSpan;
798
799 var nextTD = nextElm(tdElm, "TD,TH");
800 if (nextTD == null)
801 tdElm.parentNode.appendChild(newTD);
802 else
803 nextTD.parentNode.insertBefore(newTD, nextTD);
804 } else
805 tdElm.colSpan++;
806
807 lastTDElm = tdElm;
808 }
809 }
810
811 select(1);
812 break;
813
814 case "mceTableDeleteCol":
815 if (!trElm || !tdElm)
816 return true;
817
818 var grid = getTableGrid(tableElm);
819 var cpos = getCellPos(grid, tdElm);
820 var lastTDElm = null;
821
822 // Only one col, remove whole table
823 if ((grid.length > 1 && grid[0].length <= 1) && tableElm.nodeName == 'TBODY') {
824 inst.dom.remove(inst.dom.getParent(tableElm, "table"));
825 return true;
826 }
827
828 // Delete cells
829 for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
830 if (tdElm != lastTDElm) {
831 var sd = getColRowSpan(tdElm);
832
833 if (sd['colspan'] > 1)
834 tdElm.colSpan = sd['colspan'] - 1;
835 else {
836 if (tdElm.parentNode)
837 tdElm.parentNode.removeChild(tdElm);
838 }
839
840 lastTDElm = tdElm;
841 }
842 }
843
844 select(-1);
845 break;
846
847 case "mceTableSplitCells":
848 if (!trElm || !tdElm)
849 return true;
850
851 var spandata = getColRowSpan(tdElm);
852
853 var colspan = spandata["colspan"];
854 var rowspan = spandata["rowspan"];
855
856 // Needs splitting
857 if (colspan > 1 || rowspan > 1) {
858 // Generate cols
859 tdElm.colSpan = 1;
860 for (var i=1; i<colspan; i++) {
861 var newTD = doc.createElement("td");
862
863 if (!tinymce.isIE)
864 newTD.innerHTML = '<br mce_bogus="1"/>';
865
866 trElm.insertBefore(newTD, nextElm(tdElm, "TD,TH"));
867
868 if (rowspan > 1)
869 addRows(newTD, trElm, rowspan);
870 }
871
872 addRows(tdElm, trElm, rowspan);
873 } 894 }
874 895
875 // Apply visual aids 896 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}});
876 tableElm = inst.dom.getParent(inst.selection.getNode(), "table"); 897 m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'});
877 break; 898 m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'});
878 899 m.addSeparator();
879 case "mceTableMergeCells": 900
880 var rows = []; 901 // Cell menu
881 var sel = inst.selection.getSel(); 902 sm = m.addMenu({title : 'table.cell'});
882 var grid = getTableGrid(tableElm); 903 sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'});
883 904 sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'});
884 if (tinymce.isIE || sel.rangeCount == 1) { 905 sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'});
885 if (user_interface) { 906
886 // Setup template 907 // Row menu
887 var sp = getColRowSpan(tdElm); 908 sm = m.addMenu({title : 'table.row'});
888 909 sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'});
889 inst.windowManager.open({ 910 sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
890 url : url + '/merge_cells.htm', 911 sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
891 width : 240 + parseInt(inst.getLang('table.merge_cells_delta_width', 0)), 912 sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
892 height : 110 + parseInt(inst.getLang('table.merge_cells_delta_height', 0)), 913 sm.addSeparator();
893 inline : 1 914 sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
894 }, { 915 sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
895 action : "update", 916 sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows);
896 numcols : sp.colspan, 917 sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows);
897 numrows : sp.rowspan, 918
898 plugin_url : url 919 // Column menu
899 }); 920 sm = m.addMenu({title : 'table.col'});
900 921 sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
901 return true; 922 sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
902 } else { 923 sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
903 var numRows = parseInt(value['numrows']); 924 } else
904 var numCols = parseInt(value['numcols']); 925 m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
905 var cpos = getCellPos(grid, tdElm); 926 });
906 927 }
907 if (("" + numRows) == "NaN") 928
908 numRows = 1; 929 // Fixes an issue on Gecko where it's impossible to place the caret behind a table
909 930 // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
910 if (("" + numCols) == "NaN") 931 if (!tinymce.isIE) {
911 numCols = 1; 932 function fixTableCaretPos() {
912 933 var last;
913 // Get rows and cells 934
914 var tRows = tableElm.rows; 935 // Skip empty text nodes form the end
915 for (var y=cpos.rowindex; y<grid.length; y++) { 936 for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
916 var rowCells = []; 937
917 938 if (last && last.nodeName == 'TABLE')
918 for (var x=cpos.cellindex; x<grid[y].length; x++) { 939 ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
919 var td = getCell(grid, y, x); 940 };
920 941
921 if (td && !inArray(rows, td) && !inArray(rowCells, td)) { 942 // Fixes an bug where it's impossible to place the caret before a table in Gecko
922 var cp = getCellPos(grid, td); 943 // this fix solves it by detecting when the caret is at the beginning of such a table
923 944 // and then manually moves the caret infront of the table
924 // Within range 945 if (tinymce.isGecko) {
925 if (cp.cellindex < cpos.cellindex+numCols && cp.rowindex < cpos.rowindex+numRows) 946 ed.onKeyDown.add(function(ed, e) {
926 rowCells[rowCells.length] = td; 947 var rng, table, dom = ed.dom;
927 } 948
928 } 949 // On gecko it's not possible to place the caret before a table
929 950 if (e.keyCode == 37 || e.keyCode == 38) {
930 if (rowCells.length > 0) 951 rng = ed.selection.getRng();
931 rows[rows.length] = rowCells; 952 table = dom.getParent(rng.startContainer, 'table');
932 953
933 var td = getCell(grid, cpos.rowindex, cpos.cellindex); 954 if (table && ed.getBody().firstChild == table) {
934 each(ed.dom.select('br', td), function(e, i) { 955 if (isAtStart(rng, table)) {
935 if (i > 0 && ed.dom.getAttrib('mce_bogus')) 956 rng = dom.createRng();
936 ed.dom.remove(e); 957
937 }); 958 rng.setStartBefore(table);
938 } 959 rng.setEndBefore(table);
939 960
940 //return true; 961 ed.selection.setRng(rng);
941 } 962
942 } else { 963 e.preventDefault();
943 var cells = [];
944 var sel = inst.selection.getSel();
945 var lastTR = null;
946 var curRow = null;
947 var x1 = -1, y1 = -1, x2, y2;
948
949 // Only one cell selected, whats the point?
950 if (sel.rangeCount < 2)
951 return true;
952
953 // Get all selected cells
954 for (var i=0; i<sel.rangeCount; i++) {
955 var rng = sel.getRangeAt(i);
956 var tdElm = rng.startContainer.childNodes[rng.startOffset];
957
958 if (!tdElm)
959 break;
960
961 if (tdElm.nodeName == "TD" || tdElm.nodeName == "TH")
962 cells[cells.length] = tdElm;
963 }
964
965 // Get rows and cells
966 var tRows = tableElm.rows;
967 for (var y=0; y<tRows.length; y++) {
968 var rowCells = [];
969
970 for (var x=0; x<tRows[y].cells.length; x++) {
971 var td = tRows[y].cells[x];
972
973 for (var i=0; i<cells.length; i++) {
974 if (td == cells[i]) {
975 rowCells[rowCells.length] = td;
976 }
977 }
978 }
979
980 if (rowCells.length > 0)
981 rows[rows.length] = rowCells;
982 }
983
984 // Find selected cells in grid and box
985 var curRow = [];
986 var lastTR = null;
987 for (var y=0; y<grid.length; y++) {
988 for (var x=0; x<grid[y].length; x++) {
989 grid[y][x]._selected = false;
990
991 for (var i=0; i<cells.length; i++) {
992 if (grid[y][x] == cells[i]) {
993 // Get start pos
994 if (x1 == -1) {
995 x1 = x;
996 y1 = y;
997 }
998
999 // Get end pos
1000 x2 = x;
1001 y2 = y;
1002
1003 grid[y][x]._selected = true;
1004 }
1005 }
1006 }
1007 }
1008
1009 // Is there gaps, if so deny
1010 for (var y=y1; y<=y2; y++) {
1011 for (var x=x1; x<=x2; x++) {
1012 if (!grid[y][x]._selected) {
1013 alert("Invalid selection for merge.");
1014 return true;
1015 }
1016 } 964 }
1017 } 965 }
1018 } 966 }
1019 967 });
1020 // Validate selection and get total rowspan and colspan 968 }
1021 var rowSpan = 1, colSpan = 1; 969
1022 970 ed.onKeyUp.add(fixTableCaretPos);
1023 // Validate horizontal and get total colspan 971 ed.onSetContent.add(fixTableCaretPos);
1024 var lastRowSpan = -1; 972 ed.onVisualAid.add(fixTableCaretPos);
1025 for (var y=0; y<rows.length; y++) { 973
1026 var rowColSpan = 0; 974 ed.onPreProcess.add(function(ed, o) {
1027 975 var last = o.node.lastChild;
1028 for (var x=0; x<rows[y].length; x++) { 976
1029 var sd = getColRowSpan(rows[y][x]); 977 if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
1030 978 ed.dom.remove(last);
1031 rowColSpan += sd['colspan']; 979 });
1032 980
1033 if (lastRowSpan != -1 && sd['rowspan'] != lastRowSpan) { 981 fixTableCaretPos();
1034 alert("Invalid selection for merge."); 982 }
1035 return true; 983 });
1036 } 984
1037 985 // Register action commands
1038 lastRowSpan = sd['rowspan']; 986 each({
1039 } 987 mceTableSplitCells : function(grid) {
1040 988 grid.split();
1041 if (rowColSpan > colSpan) 989 },
1042 colSpan = rowColSpan; 990
1043 991 mceTableMergeCells : function(grid) {
1044 lastRowSpan = -1; 992 var rowSpan, colSpan, cell;
1045 } 993
1046 994 cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
1047 // Validate vertical and get total rowspan 995 if (cell) {
1048 var lastColSpan = -1; 996 rowSpan = cell.rowSpan;
1049 for (var x=0; x<rows[0].length; x++) { 997 colSpan = cell.colSpan;
1050 var colRowSpan = 0; 998 }
1051 999
1052 for (var y=0; y<rows.length; y++) { 1000 if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
1053 var sd = getColRowSpan(rows[y][x]); 1001 winMan.open({
1054 1002 url : url + '/merge_cells.htm',
1055 colRowSpan += sd['rowspan']; 1003 width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)),
1056 1004 height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)),
1057 if (lastColSpan != -1 && sd['colspan'] != lastColSpan) { 1005 inline : 1
1058 alert("Invalid selection for merge."); 1006 }, {
1059 return true; 1007 rows : rowSpan,
1060 } 1008 cols : colSpan,
1061 1009 onaction : function(data) {
1062 lastColSpan = sd['colspan']; 1010 grid.merge(cell, data.cols, data.rows);
1063 } 1011 },
1064 1012 plugin_url : url
1065 if (colRowSpan > rowSpan) 1013 });
1066 rowSpan = colRowSpan; 1014 } else
1067 1015 grid.merge();
1068 lastColSpan = -1; 1016 },
1069 } 1017
1070 1018 mceTableInsertRowBefore : function(grid) {
1071 // Setup td 1019 grid.insertRow(true);
1072 tdElm = rows[0][0]; 1020 },
1073 tdElm.rowSpan = rowSpan; 1021
1074 tdElm.colSpan = colSpan; 1022 mceTableInsertRowAfter : function(grid) {
1075 1023 grid.insertRow();
1076 // Merge cells 1024 },
1077 for (var y=0; y<rows.length; y++) { 1025
1078 for (var x=0; x<rows[y].length; x++) { 1026 mceTableInsertColBefore : function(grid) {
1079 var html = rows[y][x].innerHTML; 1027 grid.insertCol(true);
1080 var chk = html.replace(/[ \t\r\n]/g, ""); 1028 },
1081 1029
1082 if (chk != "<br/>" && chk != "<br>" && chk != '<br mce_bogus="1"/>' && (x+y > 0)) 1030 mceTableInsertColAfter : function(grid) {
1083 tdElm.innerHTML += html; 1031 grid.insertCol();
1084 1032 },
1085 // Not current cell 1033
1086 if (rows[y][x] != tdElm && !rows[y][x]._deleted) { 1034 mceTableDeleteCol : function(grid) {
1087 var cpos = getCellPos(grid, rows[y][x]); 1035 grid.deleteCols();
1088 var tr = rows[y][x].parentNode; 1036 },
1089 1037
1090 tr.removeChild(rows[y][x]); 1038 mceTableDeleteRow : function(grid) {
1091 rows[y][x]._deleted = true; 1039 grid.deleteRows();
1092 1040 },
1093 // Empty TR, remove it 1041
1094 if (!tr.hasChildNodes()) { 1042 mceTableCutRow : function(grid) {
1095 tr.parentNode.removeChild(tr); 1043 clipboardRows = grid.cutRows();
1096 1044 },
1097 var lastCell = null; 1045
1098 for (var x=0; cellElm = getCell(grid, cpos.rowindex, x); x++) { 1046 mceTableCopyRow : function(grid) {
1099 if (cellElm != lastCell && cellElm.rowSpan > 1) 1047 clipboardRows = grid.copyRows();
1100 cellElm.rowSpan--; 1048 },
1101 1049
1102 lastCell = cellElm; 1050 mceTablePasteRowBefore : function(grid) {
1103 } 1051 grid.pasteRows(clipboardRows, true);
1104 1052 },
1105 if (tdElm.rowSpan > 1) 1053
1106 tdElm.rowSpan--; 1054 mceTablePasteRowAfter : function(grid) {
1107 } 1055 grid.pasteRows(clipboardRows);
1108 } 1056 },
1109 } 1057
1110 } 1058 mceTableDelete : function(grid) {
1111 1059 grid.deleteTable();
1112 // Remove all but one bogus br 1060 }
1113 each(ed.dom.select('br', tdElm), function(e, i) { 1061 }, function(func, name) {
1114 if (i > 0 && ed.dom.getAttrib(e, 'mce_bogus')) 1062 ed.addCommand(name, function() {
1115 ed.dom.remove(e); 1063 var grid = createTableGrid();
1116 }); 1064
1117 1065 if (grid) {
1118 break; 1066 func(grid);
1119 } 1067 ed.execCommand('mceRepaint');
1120 1068 cleanup();
1121 tableElm = inst.dom.getParent(inst.selection.getNode(), "table"); 1069 }
1122 inst.addVisual(tableElm); 1070 });
1123 inst.nodeChanged(); 1071 });
1124 } 1072
1125 1073 // Register dialog commands
1126 return true; 1074 each({
1127 } 1075 mceInsertTable : function(val) {
1128 1076 winMan.open({
1129 // Pass to next handler in chain 1077 url : url + '/table.htm',
1130 return false; 1078 width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)),
1079 height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)),
1080 inline : 1
1081 }, {
1082 plugin_url : url,
1083 action : val ? val.action : 0
1084 });
1085 },
1086
1087 mceTableRowProps : function() {
1088 winMan.open({
1089 url : url + '/row.htm',
1090 width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)),
1091 height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)),
1092 inline : 1
1093 }, {
1094 plugin_url : url
1095 });
1096 },
1097
1098 mceTableCellProps : function() {
1099 winMan.open({
1100 url : url + '/cell.htm',
1101 width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)),
1102 height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)),
1103 inline : 1
1104 }, {
1105 plugin_url : url
1106 });
1107 }
1108 }, function(func, name) {
1109 ed.addCommand(name, function(ui, val) {
1110 func(val);
1111 });
1112 });
1131 } 1113 }
1132 }); 1114 });
1133 1115
1134 // Register plugin 1116 // Register plugin
1135 tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin); 1117 tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
1136 })(); 1118 })(tinymce);