|
1 /** |
|
2 * TableGrid.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * This class creates a grid out of a table element. This |
|
13 * makes it a whole lot easier to handle complex tables with |
|
14 * col/row spans. |
|
15 * |
|
16 * @class tinymce.tableplugin.TableGrid |
|
17 * @private |
|
18 */ |
|
19 define("tinymce/tableplugin/TableGrid", [ |
|
20 "tinymce/util/Tools", |
|
21 "tinymce/Env" |
|
22 ], function(Tools, Env) { |
|
23 var each = Tools.each; |
|
24 |
|
25 function getSpanVal(td, name) { |
|
26 return parseInt(td.getAttribute(name) || 1, 10); |
|
27 } |
|
28 |
|
29 return function(editor, table) { |
|
30 var grid, gridWidth, startPos, endPos, selectedCell, selection = editor.selection, dom = selection.dom; |
|
31 |
|
32 function buildGrid() { |
|
33 var startY = 0; |
|
34 |
|
35 grid = []; |
|
36 gridWidth = 0; |
|
37 |
|
38 each(['thead', 'tbody', 'tfoot'], function(part) { |
|
39 var rows = dom.select('> ' + part + ' tr', table); |
|
40 |
|
41 each(rows, function(tr, y) { |
|
42 y += startY; |
|
43 |
|
44 each(dom.select('> td, > th', tr), function(td, x) { |
|
45 var x2, y2, rowspan, colspan; |
|
46 |
|
47 // Skip over existing cells produced by rowspan |
|
48 if (grid[y]) { |
|
49 while (grid[y][x]) { |
|
50 x++; |
|
51 } |
|
52 } |
|
53 |
|
54 // Get col/rowspan from cell |
|
55 rowspan = getSpanVal(td, 'rowspan'); |
|
56 colspan = getSpanVal(td, 'colspan'); |
|
57 |
|
58 // Fill out rowspan/colspan right and down |
|
59 for (y2 = y; y2 < y + rowspan; y2++) { |
|
60 if (!grid[y2]) { |
|
61 grid[y2] = []; |
|
62 } |
|
63 |
|
64 for (x2 = x; x2 < x + colspan; x2++) { |
|
65 grid[y2][x2] = { |
|
66 part: part, |
|
67 real: y2 == y && x2 == x, |
|
68 elm: td, |
|
69 rowspan: rowspan, |
|
70 colspan: colspan |
|
71 }; |
|
72 } |
|
73 } |
|
74 |
|
75 gridWidth = Math.max(gridWidth, x + 1); |
|
76 }); |
|
77 }); |
|
78 |
|
79 startY += rows.length; |
|
80 }); |
|
81 } |
|
82 |
|
83 function cloneNode(node, children) { |
|
84 node = node.cloneNode(children); |
|
85 node.removeAttribute('id'); |
|
86 |
|
87 return node; |
|
88 } |
|
89 |
|
90 function getCell(x, y) { |
|
91 var row; |
|
92 |
|
93 row = grid[y]; |
|
94 if (row) { |
|
95 return row[x]; |
|
96 } |
|
97 } |
|
98 |
|
99 function setSpanVal(td, name, val) { |
|
100 if (td) { |
|
101 val = parseInt(val, 10); |
|
102 |
|
103 if (val === 1) { |
|
104 td.removeAttribute(name, 1); |
|
105 } else { |
|
106 td.setAttribute(name, val, 1); |
|
107 } |
|
108 } |
|
109 } |
|
110 |
|
111 function isCellSelected(cell) { |
|
112 return cell && (dom.hasClass(cell.elm, 'mce-item-selected') || cell == selectedCell); |
|
113 } |
|
114 |
|
115 function getSelectedRows() { |
|
116 var rows = []; |
|
117 |
|
118 each(table.rows, function(row) { |
|
119 each(row.cells, function(cell) { |
|
120 if (dom.hasClass(cell, 'mce-item-selected') || (selectedCell && cell == selectedCell.elm)) { |
|
121 rows.push(row); |
|
122 return false; |
|
123 } |
|
124 }); |
|
125 }); |
|
126 |
|
127 return rows; |
|
128 } |
|
129 |
|
130 function deleteTable() { |
|
131 var rng = dom.createRng(); |
|
132 |
|
133 rng.setStartAfter(table); |
|
134 rng.setEndAfter(table); |
|
135 |
|
136 selection.setRng(rng); |
|
137 |
|
138 dom.remove(table); |
|
139 } |
|
140 |
|
141 function cloneCell(cell) { |
|
142 var formatNode, cloneFormats = {}; |
|
143 |
|
144 if (editor.settings.table_clone_elements !== false) { |
|
145 cloneFormats = Tools.makeMap( |
|
146 (editor.settings.table_clone_elements || 'strong em b i span font h1 h2 h3 h4 h5 h6 p div').toUpperCase(), |
|
147 /[ ,]/ |
|
148 ); |
|
149 } |
|
150 |
|
151 // Clone formats |
|
152 Tools.walk(cell, function(node) { |
|
153 var curNode; |
|
154 |
|
155 if (node.nodeType == 3) { |
|
156 each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) { |
|
157 if (!cloneFormats[node.nodeName]) { |
|
158 return; |
|
159 } |
|
160 |
|
161 node = cloneNode(node, false); |
|
162 |
|
163 if (!formatNode) { |
|
164 formatNode = curNode = node; |
|
165 } else if (curNode) { |
|
166 curNode.appendChild(node); |
|
167 } |
|
168 |
|
169 curNode = node; |
|
170 }); |
|
171 |
|
172 // Add something to the inner node |
|
173 if (curNode) { |
|
174 curNode.innerHTML = Env.ie ? ' ' : '<br data-mce-bogus="1" />'; |
|
175 } |
|
176 |
|
177 return false; |
|
178 } |
|
179 }, 'childNodes'); |
|
180 |
|
181 cell = cloneNode(cell, false); |
|
182 setSpanVal(cell, 'rowSpan', 1); |
|
183 setSpanVal(cell, 'colSpan', 1); |
|
184 |
|
185 if (formatNode) { |
|
186 cell.appendChild(formatNode); |
|
187 } else { |
|
188 if (!Env.ie || Env.ie > 10) { |
|
189 cell.innerHTML = '<br data-mce-bogus="1" />'; |
|
190 } |
|
191 } |
|
192 |
|
193 return cell; |
|
194 } |
|
195 |
|
196 function cleanup() { |
|
197 var rng = dom.createRng(), row; |
|
198 |
|
199 // Empty rows |
|
200 each(dom.select('tr', table), function(tr) { |
|
201 if (tr.cells.length === 0) { |
|
202 dom.remove(tr); |
|
203 } |
|
204 }); |
|
205 |
|
206 // Empty table |
|
207 if (dom.select('tr', table).length === 0) { |
|
208 rng.setStartBefore(table); |
|
209 rng.setEndBefore(table); |
|
210 selection.setRng(rng); |
|
211 dom.remove(table); |
|
212 return; |
|
213 } |
|
214 |
|
215 // Empty header/body/footer |
|
216 each(dom.select('thead,tbody,tfoot', table), function(part) { |
|
217 if (part.rows.length === 0) { |
|
218 dom.remove(part); |
|
219 } |
|
220 }); |
|
221 |
|
222 // Restore selection to start position if it still exists |
|
223 buildGrid(); |
|
224 |
|
225 // If we have a valid startPos object |
|
226 if (startPos) { |
|
227 // Restore the selection to the closest table position |
|
228 row = grid[Math.min(grid.length - 1, startPos.y)]; |
|
229 if (row) { |
|
230 selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); |
|
231 selection.collapse(true); |
|
232 } |
|
233 } |
|
234 } |
|
235 |
|
236 function fillLeftDown(x, y, rows, cols) { |
|
237 var tr, x2, r, c, cell; |
|
238 |
|
239 tr = grid[y][x].elm.parentNode; |
|
240 for (r = 1; r <= rows; r++) { |
|
241 tr = dom.getNext(tr, 'tr'); |
|
242 |
|
243 if (tr) { |
|
244 // Loop left to find real cell |
|
245 for (x2 = x; x2 >= 0; x2--) { |
|
246 cell = grid[y + r][x2].elm; |
|
247 |
|
248 if (cell.parentNode == tr) { |
|
249 // Append clones after |
|
250 for (c = 1; c <= cols; c++) { |
|
251 dom.insertAfter(cloneCell(cell), cell); |
|
252 } |
|
253 |
|
254 break; |
|
255 } |
|
256 } |
|
257 |
|
258 if (x2 == -1) { |
|
259 // Insert nodes before first cell |
|
260 for (c = 1; c <= cols; c++) { |
|
261 tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); |
|
262 } |
|
263 } |
|
264 } |
|
265 } |
|
266 } |
|
267 |
|
268 function split() { |
|
269 each(grid, function(row, y) { |
|
270 each(row, function(cell, x) { |
|
271 var colSpan, rowSpan, i; |
|
272 |
|
273 if (isCellSelected(cell)) { |
|
274 cell = cell.elm; |
|
275 colSpan = getSpanVal(cell, 'colspan'); |
|
276 rowSpan = getSpanVal(cell, 'rowspan'); |
|
277 |
|
278 if (colSpan > 1 || rowSpan > 1) { |
|
279 setSpanVal(cell, 'rowSpan', 1); |
|
280 setSpanVal(cell, 'colSpan', 1); |
|
281 |
|
282 // Insert cells right |
|
283 for (i = 0; i < colSpan - 1; i++) { |
|
284 dom.insertAfter(cloneCell(cell), cell); |
|
285 } |
|
286 |
|
287 fillLeftDown(x, y, rowSpan - 1, colSpan); |
|
288 } |
|
289 } |
|
290 }); |
|
291 }); |
|
292 } |
|
293 |
|
294 function merge(cell, cols, rows) { |
|
295 var pos, startX, startY, endX, endY, x, y, startCell, endCell, children, count; |
|
296 |
|
297 // Use specified cell and cols/rows |
|
298 if (cell) { |
|
299 pos = getPos(cell); |
|
300 startX = pos.x; |
|
301 startY = pos.y; |
|
302 endX = startX + (cols - 1); |
|
303 endY = startY + (rows - 1); |
|
304 } else { |
|
305 startPos = endPos = null; |
|
306 |
|
307 // Calculate start/end pos by checking for selected cells in grid works better with context menu |
|
308 each(grid, function(row, y) { |
|
309 each(row, function(cell, x) { |
|
310 if (isCellSelected(cell)) { |
|
311 if (!startPos) { |
|
312 startPos = {x: x, y: y}; |
|
313 } |
|
314 |
|
315 endPos = {x: x, y: y}; |
|
316 } |
|
317 }); |
|
318 }); |
|
319 |
|
320 // Use selection, but make sure startPos is valid before accessing |
|
321 if (startPos) { |
|
322 startX = startPos.x; |
|
323 startY = startPos.y; |
|
324 endX = endPos.x; |
|
325 endY = endPos.y; |
|
326 } |
|
327 } |
|
328 |
|
329 // Find start/end cells |
|
330 startCell = getCell(startX, startY); |
|
331 endCell = getCell(endX, endY); |
|
332 |
|
333 // Check if the cells exists and if they are of the same part for example tbody = tbody |
|
334 if (startCell && endCell && startCell.part == endCell.part) { |
|
335 // Split and rebuild grid |
|
336 split(); |
|
337 buildGrid(); |
|
338 |
|
339 // Set row/col span to start cell |
|
340 startCell = getCell(startX, startY).elm; |
|
341 setSpanVal(startCell, 'colSpan', (endX - startX) + 1); |
|
342 setSpanVal(startCell, 'rowSpan', (endY - startY) + 1); |
|
343 |
|
344 // Remove other cells and add it's contents to the start cell |
|
345 for (y = startY; y <= endY; y++) { |
|
346 for (x = startX; x <= endX; x++) { |
|
347 if (!grid[y] || !grid[y][x]) { |
|
348 continue; |
|
349 } |
|
350 |
|
351 cell = grid[y][x].elm; |
|
352 |
|
353 /*jshint loopfunc:true */ |
|
354 /*eslint no-loop-func:0 */ |
|
355 if (cell != startCell) { |
|
356 // Move children to startCell |
|
357 children = Tools.grep(cell.childNodes); |
|
358 each(children, function(node) { |
|
359 startCell.appendChild(node); |
|
360 }); |
|
361 |
|
362 // Remove bogus nodes if there is children in the target cell |
|
363 if (children.length) { |
|
364 children = Tools.grep(startCell.childNodes); |
|
365 count = 0; |
|
366 each(children, function(node) { |
|
367 if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1) { |
|
368 startCell.removeChild(node); |
|
369 } |
|
370 }); |
|
371 } |
|
372 |
|
373 dom.remove(cell); |
|
374 } |
|
375 } |
|
376 } |
|
377 |
|
378 // Remove empty rows etc and restore caret location |
|
379 cleanup(); |
|
380 } |
|
381 } |
|
382 |
|
383 function insertRow(before) { |
|
384 var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan; |
|
385 |
|
386 // Find first/last row |
|
387 each(grid, function(row, y) { |
|
388 each(row, function(cell) { |
|
389 if (isCellSelected(cell)) { |
|
390 cell = cell.elm; |
|
391 rowElm = cell.parentNode; |
|
392 newRow = cloneNode(rowElm, false); |
|
393 posY = y; |
|
394 |
|
395 if (before) { |
|
396 return false; |
|
397 } |
|
398 } |
|
399 }); |
|
400 |
|
401 if (before) { |
|
402 return !posY; |
|
403 } |
|
404 }); |
|
405 |
|
406 // If posY is undefined there is nothing for us to do here...just return to avoid crashing below |
|
407 if (posY === undefined) { |
|
408 return; |
|
409 } |
|
410 |
|
411 for (x = 0; x < grid[0].length; x++) { |
|
412 // Cell not found could be because of an invalid table structure |
|
413 if (!grid[posY][x]) { |
|
414 continue; |
|
415 } |
|
416 |
|
417 cell = grid[posY][x].elm; |
|
418 |
|
419 if (cell != lastCell) { |
|
420 if (!before) { |
|
421 rowSpan = getSpanVal(cell, 'rowspan'); |
|
422 if (rowSpan > 1) { |
|
423 setSpanVal(cell, 'rowSpan', rowSpan + 1); |
|
424 continue; |
|
425 } |
|
426 } else { |
|
427 // Check if cell above can be expanded |
|
428 if (posY > 0 && grid[posY - 1][x]) { |
|
429 otherCell = grid[posY - 1][x].elm; |
|
430 rowSpan = getSpanVal(otherCell, 'rowSpan'); |
|
431 if (rowSpan > 1) { |
|
432 setSpanVal(otherCell, 'rowSpan', rowSpan + 1); |
|
433 continue; |
|
434 } |
|
435 } |
|
436 } |
|
437 |
|
438 // Insert new cell into new row |
|
439 newCell = cloneCell(cell); |
|
440 setSpanVal(newCell, 'colSpan', cell.colSpan); |
|
441 |
|
442 newRow.appendChild(newCell); |
|
443 |
|
444 lastCell = cell; |
|
445 } |
|
446 } |
|
447 |
|
448 if (newRow.hasChildNodes()) { |
|
449 if (!before) { |
|
450 dom.insertAfter(newRow, rowElm); |
|
451 } else { |
|
452 rowElm.parentNode.insertBefore(newRow, rowElm); |
|
453 } |
|
454 } |
|
455 } |
|
456 |
|
457 function insertCol(before) { |
|
458 var posX, lastCell; |
|
459 |
|
460 // Find first/last column |
|
461 each(grid, function(row) { |
|
462 each(row, function(cell, x) { |
|
463 if (isCellSelected(cell)) { |
|
464 posX = x; |
|
465 |
|
466 if (before) { |
|
467 return false; |
|
468 } |
|
469 } |
|
470 }); |
|
471 |
|
472 if (before) { |
|
473 return !posX; |
|
474 } |
|
475 }); |
|
476 |
|
477 each(grid, function(row, y) { |
|
478 var cell, rowSpan, colSpan; |
|
479 |
|
480 if (!row[posX]) { |
|
481 return; |
|
482 } |
|
483 |
|
484 cell = row[posX].elm; |
|
485 if (cell != lastCell) { |
|
486 colSpan = getSpanVal(cell, 'colspan'); |
|
487 rowSpan = getSpanVal(cell, 'rowspan'); |
|
488 |
|
489 if (colSpan == 1) { |
|
490 if (!before) { |
|
491 dom.insertAfter(cloneCell(cell), cell); |
|
492 fillLeftDown(posX, y, rowSpan - 1, colSpan); |
|
493 } else { |
|
494 cell.parentNode.insertBefore(cloneCell(cell), cell); |
|
495 fillLeftDown(posX, y, rowSpan - 1, colSpan); |
|
496 } |
|
497 } else { |
|
498 setSpanVal(cell, 'colSpan', cell.colSpan + 1); |
|
499 } |
|
500 |
|
501 lastCell = cell; |
|
502 } |
|
503 }); |
|
504 } |
|
505 |
|
506 function deleteCols() { |
|
507 var cols = []; |
|
508 |
|
509 // Get selected column indexes |
|
510 each(grid, function(row) { |
|
511 each(row, function(cell, x) { |
|
512 if (isCellSelected(cell) && Tools.inArray(cols, x) === -1) { |
|
513 each(grid, function(row) { |
|
514 var cell = row[x].elm, colSpan; |
|
515 |
|
516 colSpan = getSpanVal(cell, 'colSpan'); |
|
517 |
|
518 if (colSpan > 1) { |
|
519 setSpanVal(cell, 'colSpan', colSpan - 1); |
|
520 } else { |
|
521 dom.remove(cell); |
|
522 } |
|
523 }); |
|
524 |
|
525 cols.push(x); |
|
526 } |
|
527 }); |
|
528 }); |
|
529 |
|
530 cleanup(); |
|
531 } |
|
532 |
|
533 function deleteRows() { |
|
534 var rows; |
|
535 |
|
536 function deleteRow(tr) { |
|
537 var pos, lastCell; |
|
538 |
|
539 // Move down row spanned cells |
|
540 each(tr.cells, function(cell) { |
|
541 var rowSpan = getSpanVal(cell, 'rowSpan'); |
|
542 |
|
543 if (rowSpan > 1) { |
|
544 setSpanVal(cell, 'rowSpan', rowSpan - 1); |
|
545 pos = getPos(cell); |
|
546 fillLeftDown(pos.x, pos.y, 1, 1); |
|
547 } |
|
548 }); |
|
549 |
|
550 // Delete cells |
|
551 pos = getPos(tr.cells[0]); |
|
552 each(grid[pos.y], function(cell) { |
|
553 var rowSpan; |
|
554 |
|
555 cell = cell.elm; |
|
556 |
|
557 if (cell != lastCell) { |
|
558 rowSpan = getSpanVal(cell, 'rowSpan'); |
|
559 |
|
560 if (rowSpan <= 1) { |
|
561 dom.remove(cell); |
|
562 } else { |
|
563 setSpanVal(cell, 'rowSpan', rowSpan - 1); |
|
564 } |
|
565 |
|
566 lastCell = cell; |
|
567 } |
|
568 }); |
|
569 } |
|
570 |
|
571 // Get selected rows and move selection out of scope |
|
572 rows = getSelectedRows(); |
|
573 |
|
574 // Delete all selected rows |
|
575 each(rows.reverse(), function(tr) { |
|
576 deleteRow(tr); |
|
577 }); |
|
578 |
|
579 cleanup(); |
|
580 } |
|
581 |
|
582 function cutRows() { |
|
583 var rows = getSelectedRows(); |
|
584 |
|
585 dom.remove(rows); |
|
586 cleanup(); |
|
587 |
|
588 return rows; |
|
589 } |
|
590 |
|
591 function copyRows() { |
|
592 var rows = getSelectedRows(); |
|
593 |
|
594 each(rows, function(row, i) { |
|
595 rows[i] = cloneNode(row, true); |
|
596 }); |
|
597 |
|
598 return rows; |
|
599 } |
|
600 |
|
601 function pasteRows(rows, before) { |
|
602 var selectedRows = getSelectedRows(), |
|
603 targetRow = selectedRows[before ? 0 : selectedRows.length - 1], |
|
604 targetCellCount = targetRow.cells.length; |
|
605 |
|
606 // Nothing to paste |
|
607 if (!rows) { |
|
608 return; |
|
609 } |
|
610 |
|
611 // Calc target cell count |
|
612 each(grid, function(row) { |
|
613 var match; |
|
614 |
|
615 targetCellCount = 0; |
|
616 each(row, function(cell) { |
|
617 if (cell.real) { |
|
618 targetCellCount += cell.colspan; |
|
619 } |
|
620 |
|
621 if (cell.elm.parentNode == targetRow) { |
|
622 match = 1; |
|
623 } |
|
624 }); |
|
625 |
|
626 if (match) { |
|
627 return false; |
|
628 } |
|
629 }); |
|
630 |
|
631 if (!before) { |
|
632 rows.reverse(); |
|
633 } |
|
634 |
|
635 each(rows, function(row) { |
|
636 var i, cellCount = row.cells.length, cell; |
|
637 |
|
638 // Remove col/rowspans |
|
639 for (i = 0; i < cellCount; i++) { |
|
640 cell = row.cells[i]; |
|
641 setSpanVal(cell, 'colSpan', 1); |
|
642 setSpanVal(cell, 'rowSpan', 1); |
|
643 } |
|
644 |
|
645 // Needs more cells |
|
646 for (i = cellCount; i < targetCellCount; i++) { |
|
647 row.appendChild(cloneCell(row.cells[cellCount - 1])); |
|
648 } |
|
649 |
|
650 // Needs less cells |
|
651 for (i = targetCellCount; i < cellCount; i++) { |
|
652 dom.remove(row.cells[i]); |
|
653 } |
|
654 |
|
655 // Add before/after |
|
656 if (before) { |
|
657 targetRow.parentNode.insertBefore(row, targetRow); |
|
658 } else { |
|
659 dom.insertAfter(row, targetRow); |
|
660 } |
|
661 }); |
|
662 |
|
663 // Remove current selection |
|
664 dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected'); |
|
665 } |
|
666 |
|
667 function getPos(target) { |
|
668 var pos; |
|
669 |
|
670 each(grid, function(row, y) { |
|
671 each(row, function(cell, x) { |
|
672 if (cell.elm == target) { |
|
673 pos = {x: x, y: y}; |
|
674 return false; |
|
675 } |
|
676 }); |
|
677 |
|
678 return !pos; |
|
679 }); |
|
680 |
|
681 return pos; |
|
682 } |
|
683 |
|
684 function setStartCell(cell) { |
|
685 startPos = getPos(cell); |
|
686 } |
|
687 |
|
688 function findEndPos() { |
|
689 var maxX, maxY; |
|
690 |
|
691 maxX = maxY = 0; |
|
692 |
|
693 each(grid, function(row, y) { |
|
694 each(row, function(cell, x) { |
|
695 var colSpan, rowSpan; |
|
696 |
|
697 if (isCellSelected(cell)) { |
|
698 cell = grid[y][x]; |
|
699 |
|
700 if (x > maxX) { |
|
701 maxX = x; |
|
702 } |
|
703 |
|
704 if (y > maxY) { |
|
705 maxY = y; |
|
706 } |
|
707 |
|
708 if (cell.real) { |
|
709 colSpan = cell.colspan - 1; |
|
710 rowSpan = cell.rowspan - 1; |
|
711 |
|
712 if (colSpan) { |
|
713 if (x + colSpan > maxX) { |
|
714 maxX = x + colSpan; |
|
715 } |
|
716 } |
|
717 |
|
718 if (rowSpan) { |
|
719 if (y + rowSpan > maxY) { |
|
720 maxY = y + rowSpan; |
|
721 } |
|
722 } |
|
723 } |
|
724 } |
|
725 }); |
|
726 }); |
|
727 |
|
728 return {x: maxX, y: maxY}; |
|
729 } |
|
730 |
|
731 function setEndCell(cell) { |
|
732 var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan, x, y; |
|
733 |
|
734 endPos = getPos(cell); |
|
735 |
|
736 if (startPos && endPos) { |
|
737 // Get start/end positions |
|
738 startX = Math.min(startPos.x, endPos.x); |
|
739 startY = Math.min(startPos.y, endPos.y); |
|
740 endX = Math.max(startPos.x, endPos.x); |
|
741 endY = Math.max(startPos.y, endPos.y); |
|
742 |
|
743 // Expand end positon to include spans |
|
744 maxX = endX; |
|
745 maxY = endY; |
|
746 |
|
747 // Expand startX |
|
748 for (y = startY; y <= maxY; y++) { |
|
749 cell = grid[y][startX]; |
|
750 |
|
751 if (!cell.real) { |
|
752 if (startX - (cell.colspan - 1) < startX) { |
|
753 startX -= cell.colspan - 1; |
|
754 } |
|
755 } |
|
756 } |
|
757 |
|
758 // Expand startY |
|
759 for (x = startX; x <= maxX; x++) { |
|
760 cell = grid[startY][x]; |
|
761 |
|
762 if (!cell.real) { |
|
763 if (startY - (cell.rowspan - 1) < startY) { |
|
764 startY -= cell.rowspan - 1; |
|
765 } |
|
766 } |
|
767 } |
|
768 |
|
769 // Find max X, Y |
|
770 for (y = startY; y <= endY; y++) { |
|
771 for (x = startX; x <= endX; x++) { |
|
772 cell = grid[y][x]; |
|
773 |
|
774 if (cell.real) { |
|
775 colSpan = cell.colspan - 1; |
|
776 rowSpan = cell.rowspan - 1; |
|
777 |
|
778 if (colSpan) { |
|
779 if (x + colSpan > maxX) { |
|
780 maxX = x + colSpan; |
|
781 } |
|
782 } |
|
783 |
|
784 if (rowSpan) { |
|
785 if (y + rowSpan > maxY) { |
|
786 maxY = y + rowSpan; |
|
787 } |
|
788 } |
|
789 } |
|
790 } |
|
791 } |
|
792 |
|
793 // Remove current selection |
|
794 dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected'); |
|
795 |
|
796 // Add new selection |
|
797 for (y = startY; y <= maxY; y++) { |
|
798 for (x = startX; x <= maxX; x++) { |
|
799 if (grid[y][x]) { |
|
800 dom.addClass(grid[y][x].elm, 'mce-item-selected'); |
|
801 } |
|
802 } |
|
803 } |
|
804 } |
|
805 } |
|
806 |
|
807 function moveRelIdx(cellElm, delta) { |
|
808 var pos, index, cell; |
|
809 |
|
810 pos = getPos(cellElm); |
|
811 index = pos.y * gridWidth + pos.x; |
|
812 |
|
813 do { |
|
814 index += delta; |
|
815 cell = getCell(index % gridWidth, Math.floor(index / gridWidth)); |
|
816 |
|
817 if (!cell) { |
|
818 break; |
|
819 } |
|
820 |
|
821 if (cell.elm != cellElm) { |
|
822 selection.select(cell.elm, true); |
|
823 |
|
824 if (dom.isEmpty(cell.elm)) { |
|
825 selection.collapse(true); |
|
826 } |
|
827 |
|
828 return true; |
|
829 } |
|
830 } while (cell.elm == cellElm); |
|
831 |
|
832 return false; |
|
833 } |
|
834 |
|
835 table = table || dom.getParent(selection.getStart(), 'table'); |
|
836 |
|
837 buildGrid(); |
|
838 |
|
839 selectedCell = dom.getParent(selection.getStart(), 'th,td'); |
|
840 if (selectedCell) { |
|
841 startPos = getPos(selectedCell); |
|
842 endPos = findEndPos(); |
|
843 selectedCell = getCell(startPos.x, startPos.y); |
|
844 } |
|
845 |
|
846 Tools.extend(this, { |
|
847 deleteTable: deleteTable, |
|
848 split: split, |
|
849 merge: merge, |
|
850 insertRow: insertRow, |
|
851 insertCol: insertCol, |
|
852 deleteCols: deleteCols, |
|
853 deleteRows: deleteRows, |
|
854 cutRows: cutRows, |
|
855 copyRows: copyRows, |
|
856 pasteRows: pasteRows, |
|
857 getPos: getPos, |
|
858 setStartCell: setStartCell, |
|
859 setEndCell: setEndCell, |
|
860 moveRelIdx: moveRelIdx, |
|
861 refresh: buildGrid |
|
862 }); |
|
863 }; |
|
864 }); |