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