]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/plugins/table/editor_plugin_src.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / plugins / table / editor_plugin_src.js
1 /**
2  * editor_plugin_src.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         var each = tinymce.each;
13
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;
17
18                 rng2.setStartBefore(par);
19                 rng2.setEnd(rng.endContainer, rng.endOffset);
20
21                 elm = doc.createElement('body');
22                 elm.appendChild(rng2.cloneContents());
23
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;
26         };
27
28         /**
29          * Table Grid class.
30          */
31         function TableGrid(table, dom, selection) {
32                 var grid, startPos, endPos, selectedCell;
33
34                 buildGrid();
35                 selectedCell = dom.getParent(selection.getStart(), 'th,td');
36                 if (selectedCell) {
37                         startPos = getPos(selectedCell);
38                         endPos = findEndPos();
39                         selectedCell = getCell(startPos.x, startPos.y);
40                 }
41
42                 function cloneNode(node, children) {
43                         node = node.cloneNode(children);
44                         node.removeAttribute('id');
45
46                         return node;
47                 }
48
49                 function buildGrid() {
50                         var startY = 0;
51
52                         grid = [];
53
54                         each(['thead', 'tbody', 'tfoot'], function(part) {
55                                 var rows = dom.select('> ' + part + ' tr', table);
56
57                                 each(rows, function(tr, y) {
58                                         y += startY;
59
60                                         each(dom.select('> td, > th', tr), function(td, x) {
61                                                 var x2, y2, rowspan, colspan;
62
63                                                 // Skip over existing cells produced by rowspan
64                                                 if (grid[y]) {
65                                                         while (grid[y][x])
66                                                                 x++;
67                                                 }
68
69                                                 // Get col/rowspan from cell
70                                                 rowspan = getSpanVal(td, 'rowspan');
71                                                 colspan = getSpanVal(td, 'colspan');
72
73                                                 // Fill out rowspan/colspan right and down
74                                                 for (y2 = y; y2 < y + rowspan; y2++) {
75                                                         if (!grid[y2])
76                                                                 grid[y2] = [];
77
78                                                         for (x2 = x; x2 < x + colspan; x2++) {
79                                                                 grid[y2][x2] = {
80                                                                         part : part,
81                                                                         real : y2 == y && x2 == x,
82                                                                         elm : td,
83                                                                         rowspan : rowspan,
84                                                                         colspan : colspan
85                                                                 };
86                                                         }
87                                                 }
88                                         });
89                                 });
90
91                                 startY += rows.length;
92                         });
93                 };
94
95                 function getCell(x, y) {
96                         var row;
97
98                         row = grid[y];
99                         if (row)
100                                 return row[x];
101                 };
102
103                 function getSpanVal(td, name) {
104                         return parseInt(td.getAttribute(name) || 1);
105                 };
106
107                 function setSpanVal(td, name, val) {
108                         if (td) {
109                                 val = parseInt(val);
110
111                                 if (val === 1)
112                                         td.removeAttribute(name, 1);
113                                 else
114                                         td.setAttribute(name, val, 1);
115                         }
116                 }
117
118                 function isCellSelected(cell) {
119                         return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell);
120                 };
121
122                 function getSelectedRows() {
123                         var rows = [];
124
125                         each(table.rows, function(row) {
126                                 each(row.cells, function(cell) {
127                                         if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) {
128                                                 rows.push(row);
129                                                 return false;
130                                         }
131                                 });
132                         });
133
134                         return rows;
135                 };
136
137                 function deleteTable() {
138                         var rng = dom.createRng();
139
140                         rng.setStartAfter(table);
141                         rng.setEndAfter(table);
142
143                         selection.setRng(rng);
144
145                         dom.remove(table);
146                 };
147
148                 function cloneCell(cell) {
149                         var formatNode;
150
151                         // Clone formats
152                         tinymce.walk(cell, function(node) {
153                                 var curNode;
154
155                                 if (node.nodeType == 3) {
156                                         each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
157                                                 node = cloneNode(node, false);
158
159                                                 if (!formatNode)
160                                                         formatNode = curNode = node;
161                                                 else if (curNode)
162                                                         curNode.appendChild(node);
163
164                                                 curNode = node;
165                                         });
166
167                                         // Add something to the inner node
168                                         if (curNode)
169                                                 curNode.innerHTML = tinymce.isIE ? '&nbsp;' : '<br data-mce-bogus="1" />';
170
171                                         return false;
172                                 }
173                         }, 'childNodes');
174
175                         cell = cloneNode(cell, false);
176                         setSpanVal(cell, 'rowSpan', 1);
177                         setSpanVal(cell, 'colSpan', 1);
178
179                         if (formatNode) {
180                                 cell.appendChild(formatNode);
181                         } else {
182                                 if (!tinymce.isIE)
183                                         cell.innerHTML = '<br data-mce-bogus="1" />';
184                         }
185
186                         return cell;
187                 };
188
189                 function cleanup() {
190                         var rng = dom.createRng();
191
192                         // Empty rows
193                         each(dom.select('tr', table), function(tr) {
194                                 if (tr.cells.length == 0)
195                                         dom.remove(tr);
196                         });
197
198                         // Empty table
199                         if (dom.select('tr', table).length == 0) {
200                                 rng.setStartAfter(table);
201                                 rng.setEndAfter(table);
202                                 selection.setRng(rng);
203                                 dom.remove(table);
204                                 return;
205                         }
206
207                         // Empty header/body/footer
208                         each(dom.select('thead,tbody,tfoot', table), function(part) {
209                                 if (part.rows.length == 0)
210                                         dom.remove(part);
211                         });
212
213                         // Restore selection to start position if it still exists
214                         buildGrid();
215
216                         // Restore the selection to the closest table position
217                         row = grid[Math.min(grid.length - 1, startPos.y)];
218                         if (row) {
219                                 selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
220                                 selection.collapse(true);
221                         }
222                 };
223
224                 function fillLeftDown(x, y, rows, cols) {
225                         var tr, x2, r, c, cell;
226
227                         tr = grid[y][x].elm.parentNode;
228                         for (r = 1; r <= rows; r++) {
229                                 tr = dom.getNext(tr, 'tr');
230
231                                 if (tr) {
232                                         // Loop left to find real cell
233                                         for (x2 = x; x2 >= 0; x2--) {
234                                                 cell = grid[y + r][x2].elm;
235
236                                                 if (cell.parentNode == tr) {
237                                                         // Append clones after
238                                                         for (c = 1; c <= cols; c++)
239                                                                 dom.insertAfter(cloneCell(cell), cell);
240
241                                                         break;
242                                                 }
243                                         }
244
245                                         if (x2 == -1) {
246                                                 // Insert nodes before first cell
247                                                 for (c = 1; c <= cols; c++)
248                                                         tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
249                                         }
250                                 }
251                         }
252                 };
253
254                 function split() {
255                         each(grid, function(row, y) {
256                                 each(row, function(cell, x) {
257                                         var colSpan, rowSpan, newCell, i;
258
259                                         if (isCellSelected(cell)) {
260                                                 cell = cell.elm;
261                                                 colSpan = getSpanVal(cell, 'colspan');
262                                                 rowSpan = getSpanVal(cell, 'rowspan');
263
264                                                 if (colSpan > 1 || rowSpan > 1) {
265                                                         setSpanVal(cell, 'rowSpan', 1);
266                                                         setSpanVal(cell, 'colSpan', 1);
267
268                                                         // Insert cells right
269                                                         for (i = 0; i < colSpan - 1; i++)
270                                                                 dom.insertAfter(cloneCell(cell), cell);
271
272                                                         fillLeftDown(x, y, rowSpan - 1, colSpan);
273                                                 }
274                                         }
275                                 });
276                         });
277                 };
278
279                 function merge(cell, cols, rows) {
280                         var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count;
281
282                         // Use specified cell and cols/rows
283                         if (cell) {
284                                 pos = getPos(cell);
285                                 startX = pos.x;
286                                 startY = pos.y;
287                                 endX = startX + (cols - 1);
288                                 endY = startY + (rows - 1);
289                         } else {
290                                 // Use selection
291                                 startX = startPos.x;
292                                 startY = startPos.y;
293                                 endX = endPos.x;
294                                 endY = endPos.y;
295                         }
296
297                         // Find start/end cells
298                         startCell = getCell(startX, startY);
299                         endCell = getCell(endX, endY);
300
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
304                                 split();
305                                 buildGrid();
306
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);
311
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])
316                                                         continue;
317
318                                                 cell = grid[y][x].elm;
319
320                                                 if (cell != startCell) {
321                                                         // Move children to startCell
322                                                         children = tinymce.grep(cell.childNodes);
323                                                         each(children, function(node) {
324                                                                 startCell.appendChild(node);
325                                                         });
326
327                                                         // Remove bogus nodes if there is children in the target cell
328                                                         if (children.length) {
329                                                                 children = tinymce.grep(startCell.childNodes);
330                                                                 count = 0;
331                                                                 each(children, function(node) {
332                                                                         if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1)
333                                                                                 startCell.removeChild(node);
334                                                                 });
335                                                         }
336                                                         
337                                                         // Remove cell
338                                                         dom.remove(cell);
339                                                 }
340                                         }
341                                 }
342
343                                 // Remove empty rows etc and restore caret location
344                                 cleanup();
345                         }
346                 };
347
348                 function insertRow(before) {
349                         var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
350
351                         // Find first/last row
352                         each(grid, function(row, y) {
353                                 each(row, function(cell, x) {
354                                         if (isCellSelected(cell)) {
355                                                 cell = cell.elm;
356                                                 rowElm = cell.parentNode;
357                                                 newRow = cloneNode(rowElm, false);
358                                                 posY = y;
359
360                                                 if (before)
361                                                         return false;
362                                         }
363                                 });
364
365                                 if (before)
366                                         return !posY;
367                         });
368
369                         for (x = 0; x < grid[0].length; x++) {
370                                 // Cell not found could be because of an invalid table structure
371                                 if (!grid[posY][x])
372                                         continue;
373
374                                 cell = grid[posY][x].elm;
375
376                                 if (cell != lastCell) {
377                                         if (!before) {
378                                                 rowSpan = getSpanVal(cell, 'rowspan');
379                                                 if (rowSpan > 1) {
380                                                         setSpanVal(cell, 'rowSpan', rowSpan + 1);
381                                                         continue;
382                                                 }
383                                         } else {
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');
388                                                         if (rowSpan > 1) {
389                                                                 setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
390                                                                 continue;
391                                                         }
392                                                 }
393                                         }
394
395                                         // Insert new cell into new row
396                                         newCell = cloneCell(cell);
397                                         setSpanVal(newCell, 'colSpan', cell.colSpan);
398
399                                         newRow.appendChild(newCell);
400
401                                         lastCell = cell;
402                                 }
403                         }
404
405                         if (newRow.hasChildNodes()) {
406                                 if (!before)
407                                         dom.insertAfter(newRow, rowElm);
408                                 else
409                                         rowElm.parentNode.insertBefore(newRow, rowElm);
410                         }
411                 };
412
413                 function insertCol(before) {
414                         var posX, lastCell;
415
416                         // Find first/last column
417                         each(grid, function(row, y) {
418                                 each(row, function(cell, x) {
419                                         if (isCellSelected(cell)) {
420                                                 posX = x;
421
422                                                 if (before)
423                                                         return false;
424                                         }
425                                 });
426
427                                 if (before)
428                                         return !posX;
429                         });
430
431                         each(grid, function(row, y) {
432                                 var cell, rowSpan, colSpan;
433
434                                 if (!row[posX])
435                                         return;
436
437                                 cell = row[posX].elm;
438                                 if (cell != lastCell) {
439                                         colSpan = getSpanVal(cell, 'colspan');
440                                         rowSpan = getSpanVal(cell, 'rowspan');
441
442                                         if (colSpan == 1) {
443                                                 if (!before) {
444                                                         dom.insertAfter(cloneCell(cell), cell);
445                                                         fillLeftDown(posX, y, rowSpan - 1, colSpan);
446                                                 } else {
447                                                         cell.parentNode.insertBefore(cloneCell(cell), cell);
448                                                         fillLeftDown(posX, y, rowSpan - 1, colSpan);
449                                                 }
450                                         } else
451                                                 setSpanVal(cell, 'colSpan', cell.colSpan + 1);
452
453                                         lastCell = cell;
454                                 }
455                         });
456                 };
457
458                 function deleteCols() {
459                         var cols = [];
460
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;
467
468                                                         colSpan = getSpanVal(cell, 'colSpan');
469
470                                                         if (colSpan > 1)
471                                                                 setSpanVal(cell, 'colSpan', colSpan - 1);
472                                                         else
473                                                                 dom.remove(cell);
474                                                 });
475
476                                                 cols.push(x);
477                                         }
478                                 });
479                         });
480
481                         cleanup();
482                 };
483
484                 function deleteRows() {
485                         var rows;
486
487                         function deleteRow(tr) {
488                                 var nextTr, pos, lastCell;
489
490                                 nextTr = dom.getNext(tr, 'tr');
491
492                                 // Move down row spanned cells
493                                 each(tr.cells, function(cell) {
494                                         var rowSpan = getSpanVal(cell, 'rowSpan');
495
496                                         if (rowSpan > 1) {
497                                                 setSpanVal(cell, 'rowSpan', rowSpan - 1);
498                                                 pos = getPos(cell);
499                                                 fillLeftDown(pos.x, pos.y, 1, 1);
500                                         }
501                                 });
502
503                                 // Delete cells
504                                 pos = getPos(tr.cells[0]);
505                                 each(grid[pos.y], function(cell) {
506                                         var rowSpan;
507
508                                         cell = cell.elm;
509
510                                         if (cell != lastCell) {
511                                                 rowSpan = getSpanVal(cell, 'rowSpan');
512
513                                                 if (rowSpan <= 1)
514                                                         dom.remove(cell);
515                                                 else
516                                                         setSpanVal(cell, 'rowSpan', rowSpan - 1);
517
518                                                 lastCell = cell;
519                                         }
520                                 });
521                         };
522
523                         // Get selected rows and move selection out of scope
524                         rows = getSelectedRows();
525
526                         // Delete all selected rows
527                         each(rows.reverse(), function(tr) {
528                                 deleteRow(tr);
529                         });
530
531                         cleanup();
532                 };
533
534                 function cutRows() {
535                         var rows = getSelectedRows();
536
537                         dom.remove(rows);
538                         cleanup();
539
540                         return rows;
541                 };
542
543                 function copyRows() {
544                         var rows = getSelectedRows();
545
546                         each(rows, function(row, i) {
547                                 rows[i] = cloneNode(row, true);
548                         });
549
550                         return rows;
551                 };
552
553                 function pasteRows(rows, before) {
554                         var selectedRows = getSelectedRows(),
555                                 targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
556                                 targetCellCount = targetRow.cells.length;
557
558                         // Calc target cell count
559                         each(grid, function(row) {
560                                 var match;
561
562                                 targetCellCount = 0;
563                                 each(row, function(cell, x) {
564                                         if (cell.real)
565                                                 targetCellCount += cell.colspan;
566
567                                         if (cell.elm.parentNode == targetRow)
568                                                 match = 1;
569                                 });
570
571                                 if (match)
572                                         return false;
573                         });
574
575                         if (!before)
576                                 rows.reverse();
577
578                         each(rows, function(row) {
579                                 var cellCount = row.cells.length, cell;
580
581                                 // Remove col/rowspans
582                                 for (i = 0; i < cellCount; i++) {
583                                         cell = row.cells[i];
584                                         setSpanVal(cell, 'colSpan', 1);
585                                         setSpanVal(cell, 'rowSpan', 1);
586                                 }
587
588                                 // Needs more cells
589                                 for (i = cellCount; i < targetCellCount; i++)
590                                         row.appendChild(cloneCell(row.cells[cellCount - 1]));
591
592                                 // Needs less cells
593                                 for (i = targetCellCount; i < cellCount; i++)
594                                         dom.remove(row.cells[i]);
595
596                                 // Add before/after
597                                 if (before)
598                                         targetRow.parentNode.insertBefore(row, targetRow);
599                                 else
600                                         dom.insertAfter(row, targetRow);
601                         });
602                 };
603
604                 function getPos(target) {
605                         var pos;
606
607                         each(grid, function(row, y) {
608                                 each(row, function(cell, x) {
609                                         if (cell.elm == target) {
610                                                 pos = {x : x, y : y};
611                                                 return false;
612                                         }
613                                 });
614
615                                 return !pos;
616                         });
617
618                         return pos;
619                 };
620
621                 function setStartCell(cell) {
622                         startPos = getPos(cell);
623                 };
624
625                 function findEndPos() {
626                         var pos, maxX, maxY;
627
628                         maxX = maxY = 0;
629
630                         each(grid, function(row, y) {
631                                 each(row, function(cell, x) {
632                                         var colSpan, rowSpan;
633
634                                         if (isCellSelected(cell)) {
635                                                 cell = grid[y][x];
636
637                                                 if (x > maxX)
638                                                         maxX = x;
639
640                                                 if (y > maxY)
641                                                         maxY = y;
642
643                                                 if (cell.real) {
644                                                         colSpan = cell.colspan - 1;
645                                                         rowSpan = cell.rowspan - 1;
646
647                                                         if (colSpan) {
648                                                                 if (x + colSpan > maxX)
649                                                                         maxX = x + colSpan;
650                                                         }
651
652                                                         if (rowSpan) {
653                                                                 if (y + rowSpan > maxY)
654                                                                         maxY = y + rowSpan;
655                                                         }
656                                                 }
657                                         }
658                                 });
659                         });
660
661                         return {x : maxX, y : maxY};
662                 };
663
664                 function setEndCell(cell) {
665                         var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan;
666
667                         endPos = getPos(cell);
668
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);
675
676                                 // Expand end positon to include spans
677                                 maxX = endX;
678                                 maxY = endY;
679
680                                 // Expand startX
681                                 for (y = startY; y <= maxY; y++) {
682                                         cell = grid[y][startX];
683
684                                         if (!cell.real) {
685                                                 if (startX - (cell.colspan - 1) < startX)
686                                                         startX -= cell.colspan - 1;
687                                         }
688                                 }
689
690                                 // Expand startY
691                                 for (x = startX; x <= maxX; x++) {
692                                         cell = grid[startY][x];
693
694                                         if (!cell.real) {
695                                                 if (startY - (cell.rowspan - 1) < startY)
696                                                         startY -= cell.rowspan - 1;
697                                         }
698                                 }
699
700                                 // Find max X, Y
701                                 for (y = startY; y <= endY; y++) {
702                                         for (x = startX; x <= endX; x++) {
703                                                 cell = grid[y][x];
704
705                                                 if (cell.real) {
706                                                         colSpan = cell.colspan - 1;
707                                                         rowSpan = cell.rowspan - 1;
708
709                                                         if (colSpan) {
710                                                                 if (x + colSpan > maxX)
711                                                                         maxX = x + colSpan;
712                                                         }
713
714                                                         if (rowSpan) {
715                                                                 if (y + rowSpan > maxY)
716                                                                         maxY = y + rowSpan;
717                                                         }
718                                                 }
719                                         }
720                                 }
721
722                                 // Remove current selection
723                                 dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
724
725                                 // Add new selection
726                                 for (y = startY; y <= maxY; y++) {
727                                         for (x = startX; x <= maxX; x++) {
728                                                 if (grid[y][x])
729                                                         dom.addClass(grid[y][x].elm, 'mceSelected');
730                                         }
731                                 }
732                         }
733                 };
734
735                 // Expose to public
736                 tinymce.extend(this, {
737                         deleteTable : deleteTable,
738                         split : split,
739                         merge : merge,
740                         insertRow : insertRow,
741                         insertCol : insertCol,
742                         deleteCols : deleteCols,
743                         deleteRows : deleteRows,
744                         cutRows : cutRows,
745                         copyRows : copyRows,
746                         pasteRows : pasteRows,
747                         getPos : getPos,
748                         setStartCell : setStartCell,
749                         setEndCell : setEndCell
750                 });
751         };
752
753         tinymce.create('tinymce.plugins.TablePlugin', {
754                 init : function(ed, url) {
755                         var winMan, clipboardRows;
756
757                         function createTableGrid(node) {
758                                 var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table');
759
760                                 if (tblElm)
761                                         return new TableGrid(tblElm, ed.dom, selection);
762                         };
763
764                         function cleanup() {
765                                 // Restore selection possibilities
766                                 ed.getBody().style.webkitUserSelect = '';
767                                 ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected');
768                         };
769
770                         // Register buttons
771                         each([
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]
784                         ], function(c) {
785                                 ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
786                         });
787
788                         // Select whole table is a table border is clicked
789                         if (!tinymce.isIE) {
790                                 ed.onClick.add(function(ed, e) {
791                                         e = e.target;
792
793                                         if (e.nodeName === 'TABLE') {
794                                                 ed.selection.select(e);
795                                                 ed.nodeChanged();
796                                         }
797                                 });
798                         }
799
800                         ed.onPreProcess.add(function(ed, args) {
801                                 var nodes, i, node, dom = ed.dom, value;
802
803                                 nodes = dom.select('table', args.node);
804                                 i = nodes.length;
805                                 while (i--) {
806                                         node = nodes[i];
807                                         dom.setAttrib(node, 'data-mce-style', '');
808
809                                         if ((value = dom.getAttrib(node, 'width'))) {
810                                                 dom.setStyle(node, 'width', value);
811                                                 dom.setAttrib(node, 'width', '');
812                                         }
813
814                                         if ((value = dom.getAttrib(node, 'height'))) {
815                                                 dom.setStyle(node, 'height', value);
816                                                 dom.setAttrib(node, 'height', '');
817                                         }
818                                 }
819                         });
820
821                         // Handle node change updates
822                         ed.onNodeChange.add(function(ed, cm, n) {
823                                 var p;
824
825                                 n = ed.selection.getStart();
826                                 p = ed.dom.getParent(n, 'td,th,caption');
827                                 cm.setActive('table', n.nodeName === 'TABLE' || !!p);
828
829                                 // Disable table tools if we are in caption
830                                 if (p && p.nodeName === 'CAPTION')
831                                         p = 0;
832
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);
845                         });
846
847                         ed.onInit.add(function(ed) {
848                                 var startTable, startCell, dom = ed.dom, tableGrid;
849
850                                 winMan = ed.windowManager;
851
852                                 // Add cell selection logic
853                                 ed.onMouseDown.add(function(ed, e) {
854                                         if (e.button != 2) {
855                                                 cleanup();
856
857                                                 startCell = dom.getParent(e.target, 'td,th');
858                                                 startTable = dom.getParent(startCell, 'table');
859                                         }
860                                 });
861
862                                 dom.bind(ed.getDoc(), 'mouseover', function(e) {
863                                         var sel, table, target = e.target;
864
865                                         if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
866                                                 table = dom.getParent(target, 'table');
867                                                 if (table == startTable) {
868                                                         if (!tableGrid) {
869                                                                 tableGrid = createTableGrid(table);
870                                                                 tableGrid.setStartCell(startCell);
871
872                                                                 ed.getBody().style.webkitUserSelect = 'none';
873                                                         }
874
875                                                         tableGrid.setEndCell(target);
876                                                 }
877
878                                                 // Remove current selection
879                                                 sel = ed.selection.getSel();
880
881                                                 try {
882                                                         if (sel.removeAllRanges)
883                                                                 sel.removeAllRanges();
884                                                         else
885                                                                 sel.empty();
886                                                 } catch (ex) {
887                                                         // IE9 might throw errors here
888                                                 }
889
890                                                 e.preventDefault();
891                                         }
892                                 });
893
894                                 ed.onMouseUp.add(function(ed, e) {
895                                         var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode;
896
897                                         // Move selection to startCell
898                                         if (startCell) {
899                                                 if (tableGrid)
900                                                         ed.getBody().style.webkitUserSelect = '';
901
902                                                 function setPoint(node, start) {
903                                                         var walker = new tinymce.dom.TreeWalker(node, node);
904
905                                                         do {
906                                                                 // Text node
907                                                                 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
908                                                                         if (start)
909                                                                                 rng.setStart(node, 0);
910                                                                         else
911                                                                                 rng.setEnd(node, node.nodeValue.length);
912
913                                                                         return;
914                                                                 }
915
916                                                                 // BR element
917                                                                 if (node.nodeName == 'BR') {
918                                                                         if (start)
919                                                                                 rng.setStartBefore(node);
920                                                                         else
921                                                                                 rng.setEndBefore(node);
922
923                                                                         return;
924                                                                 }
925                                                         } while (node = (start ? walker.next() : walker.prev()));
926                                                 };
927
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];
934
935                                                         setPoint(node, 1);
936                                                         walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
937
938                                                         do {
939                                                                 if (node.nodeName == 'TD' || node.nodeName == 'TH') {
940                                                                         if (!dom.hasClass(node, 'mceSelected'))
941                                                                                 break;
942
943                                                                         lastNode = node;
944                                                                 }
945                                                         } while (node = walker.next());
946
947                                                         setPoint(lastNode);
948
949                                                         sel.setRng(rng);
950                                                 }
951
952                                                 ed.nodeChanged();
953                                                 startCell = tableGrid = startTable = null;
954                                         }
955                                 });
956
957                                 ed.onKeyUp.add(function(ed, e) {
958                                         cleanup();
959                                 });
960
961                                 // Add context menu
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();
965
966                                                 if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) {
967                                                         m.removeAll();
968
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'});
972                                                                 m.addSeparator();
973                                                         }
974
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});
977                                                                 m.addSeparator();
978                                                         }
979
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'});
983                                                         m.addSeparator();
984
985                                                         // Cell menu
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'});
990
991                                                         // Row menu
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'});
997                                                         sm.addSeparator();
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);
1002
1003                                                         // Column menu
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'});
1008                                                 } else
1009                                                         m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'});
1010                                         });
1011                                 }
1012
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() {
1017                                                 var last;
1018
1019                                                 // Skip empty text nodes form the end
1020                                                 for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ;
1021
1022                                                 if (last && last.nodeName == 'TABLE')
1023                                                         ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
1024                                         };
1025
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;
1032
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');
1037
1038                                                                 if (table && ed.getBody().firstChild == table) {
1039                                                                         if (isAtStart(rng, table)) {
1040                                                                                 rng = dom.createRng();
1041
1042                                                                                 rng.setStartBefore(table);
1043                                                                                 rng.setEndBefore(table);
1044
1045                                                                                 ed.selection.setRng(rng);
1046
1047                                                                                 e.preventDefault();
1048                                                                         }
1049                                                                 }
1050                                                         }
1051                                                 });
1052                                         }
1053
1054                                         ed.onKeyUp.add(fixTableCaretPos);
1055                                         ed.onSetContent.add(fixTableCaretPos);
1056                                         ed.onVisualAid.add(fixTableCaretPos);
1057
1058                                         ed.onPreProcess.add(function(ed, o) {
1059                                                 var last = o.node.lastChild;
1060
1061                                                 if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
1062                                                         ed.dom.remove(last);
1063                                         });
1064
1065                                         fixTableCaretPos();
1066                                 }
1067                         });
1068
1069                         // Register action commands
1070                         each({
1071                                 mceTableSplitCells : function(grid) {
1072                                         grid.split();
1073                                 },
1074
1075                                 mceTableMergeCells : function(grid) {
1076                                         var rowSpan, colSpan, cell;
1077
1078                                         cell = ed.dom.getParent(ed.selection.getNode(), 'th,td');
1079                                         if (cell) {
1080                                                 rowSpan = cell.rowSpan;
1081                                                 colSpan = cell.colSpan;
1082                                         }
1083
1084                                         if (!ed.dom.select('td.mceSelected,th.mceSelected').length) {
1085                                                 winMan.open({
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)),
1089                                                         inline : 1
1090                                                 }, {
1091                                                         rows : rowSpan,
1092                                                         cols : colSpan,
1093                                                         onaction : function(data) {
1094                                                                 grid.merge(cell, data.cols, data.rows);
1095                                                         },
1096                                                         plugin_url : url
1097                                                 });
1098                                         } else
1099                                                 grid.merge();
1100                                 },
1101
1102                                 mceTableInsertRowBefore : function(grid) {
1103                                         grid.insertRow(true);
1104                                 },
1105
1106                                 mceTableInsertRowAfter : function(grid) {
1107                                         grid.insertRow();
1108                                 },
1109
1110                                 mceTableInsertColBefore : function(grid) {
1111                                         grid.insertCol(true);
1112                                 },
1113
1114                                 mceTableInsertColAfter : function(grid) {
1115                                         grid.insertCol();
1116                                 },
1117
1118                                 mceTableDeleteCol : function(grid) {
1119                                         grid.deleteCols();
1120                                 },
1121
1122                                 mceTableDeleteRow : function(grid) {
1123                                         grid.deleteRows();
1124                                 },
1125
1126                                 mceTableCutRow : function(grid) {
1127                                         clipboardRows = grid.cutRows();
1128                                 },
1129
1130                                 mceTableCopyRow : function(grid) {
1131                                         clipboardRows = grid.copyRows();
1132                                 },
1133
1134                                 mceTablePasteRowBefore : function(grid) {
1135                                         grid.pasteRows(clipboardRows, true);
1136                                 },
1137
1138                                 mceTablePasteRowAfter : function(grid) {
1139                                         grid.pasteRows(clipboardRows);
1140                                 },
1141
1142                                 mceTableDelete : function(grid) {
1143                                         grid.deleteTable();
1144                                 }
1145                         }, function(func, name) {
1146                                 ed.addCommand(name, function() {
1147                                         var grid = createTableGrid();
1148
1149                                         if (grid) {
1150                                                 func(grid);
1151                                                 ed.execCommand('mceRepaint');
1152                                                 cleanup();
1153                                         }
1154                                 });
1155                         });
1156
1157                         // Register dialog commands
1158                         each({
1159                                 mceInsertTable : function(val) {
1160                                         winMan.open({
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)),
1164                                                 inline : 1
1165                                         }, {
1166                                                 plugin_url : url,
1167                                                 action : val ? val.action : 0
1168                                         });
1169                                 },
1170
1171                                 mceTableRowProps : function() {
1172                                         winMan.open({
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)),
1176                                                 inline : 1
1177                                         }, {
1178                                                 plugin_url : url
1179                                         });
1180                                 },
1181
1182                                 mceTableCellProps : function() {
1183                                         winMan.open({
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)),
1187                                                 inline : 1
1188                                         }, {
1189                                                 plugin_url : url
1190                                         });
1191                                 }
1192                         }, function(func, name) {
1193                                 ed.addCommand(name, function(ui, val) {
1194                                         func(val);
1195                                 });
1196                         });
1197                 }
1198         });
1199
1200         // Register plugin
1201         tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
1202 })(tinymce);