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