Nation Notes module contributed by Z&H Healthcare.
[openemr.git] / library / custom_template / ckeditor / _source / plugins / tabletools / plugin.js
blob1db55309c45b31097901b11dc8055cb26f932814
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
6 (function()
8         var cellNodeRegex = /^(?:td|th)$/;
10         function getSelectedCells( selection )
11         {
12                 // Walker will try to split text nodes, which will make the current selection
13                 // invalid. So save bookmarks before doing anything.
14                 var bookmarks = selection.createBookmarks();
16                 var ranges = selection.getRanges();
17                 var retval = [];
18                 var database = {};
20                 function moveOutOfCellGuard( node )
21                 {
22                         // Apply to the first cell only.
23                         if ( retval.length > 0 )
24                                 return;
26                         // If we are exiting from the first </td>, then the td should definitely be
27                         // included.
28                         if ( node.type == CKEDITOR.NODE_ELEMENT && cellNodeRegex.test( node.getName() )
29                                         && !node.getCustomData( 'selected_cell' ) )
30                         {
31                                 CKEDITOR.dom.element.setMarker( database, node, 'selected_cell', true );
32                                 retval.push( node );
33                         }
34                 }
36                 for ( var i = 0 ; i < ranges.length ; i++ )
37                 {
38                         var range = ranges[ i ];
40                         if ( range.collapsed )
41                         {
42                                 // Walker does not handle collapsed ranges yet - fall back to old API.
43                                 var startNode = range.getCommonAncestor();
44                                 var nearestCell = startNode.getAscendant( 'td', true ) || startNode.getAscendant( 'th', true );
45                                 if ( nearestCell )
46                                         retval.push( nearestCell );
47                         }
48                         else
49                         {
50                                 var walker = new CKEDITOR.dom.walker( range );
51                                 var node;
52                                 walker.guard = moveOutOfCellGuard;
54                                 while ( ( node = walker.next() ) )
55                                 {
56                                         // If may be possible for us to have a range like this:
57                                         // <td>^1</td><td>^2</td>
58                                         // The 2nd td shouldn't be included.
59                                         //
60                                         // So we have to take care to include a td we've entered only when we've
61                                         // walked into its children.
63                                         var parent = node.getParent();
64                                         if ( parent && cellNodeRegex.test( parent.getName() ) && !parent.getCustomData( 'selected_cell' ) )
65                                         {
66                                                 CKEDITOR.dom.element.setMarker( database, parent, 'selected_cell', true );
67                                                 retval.push( parent );
68                                         }
69                                 }
70                         }
71                 }
73                 CKEDITOR.dom.element.clearAllMarkers( database );
75                 // Restore selection position.
76                 selection.selectBookmarks( bookmarks );
78                 return retval;
79         }
81         function getFocusElementAfterDelCells( cellsToDelete ) {
82                 var i = 0,
83                         last = cellsToDelete.length - 1,
84                         database = {},
85                         cell,focusedCell,
86                         tr;
88                 while ( ( cell = cellsToDelete[ i++ ] ) )
89                         CKEDITOR.dom.element.setMarker( database, cell, 'delete_cell', true );
91                 // 1.first we check left or right side focusable cell row by row;
92                 i = 0;
93                 while ( ( cell = cellsToDelete[ i++ ] ) )
94                 {
95                         if ( ( focusedCell = cell.getPrevious() ) && !focusedCell.getCustomData( 'delete_cell' )
96                           || ( focusedCell = cell.getNext()     ) && !focusedCell.getCustomData( 'delete_cell' ) )
97                         {
98                                 CKEDITOR.dom.element.clearAllMarkers( database );
99                                 return focusedCell;
100                         }
101                 }
103                 CKEDITOR.dom.element.clearAllMarkers( database );
105                 // 2. then we check the toppest row (outside the selection area square) focusable cell
106                 tr = cellsToDelete[ 0 ].getParent();
107                 if ( ( tr = tr.getPrevious() ) )
108                         return tr.getLast();
110                 // 3. last we check the lowerest  row focusable cell
111                 tr = cellsToDelete[ last ].getParent();
112                 if ( ( tr = tr.getNext() ) )
113                         return tr.getChild( 0 );
115                 return null;
116         }
118         function insertRow( selection, insertBefore )
119         {
120                 var cells = getSelectedCells( selection ),
121                                 firstCell = cells[ 0 ],
122                                 table = firstCell.getAscendant( 'table' ),
123                                 doc = firstCell.getDocument(),
124                                 startRow = cells[ 0 ].getParent(),
125                                 startRowIndex = startRow.$.rowIndex,
126                                 lastCell = cells[ cells.length - 1 ],
127                                 endRowIndex = lastCell.getParent().$.rowIndex + lastCell.$.rowSpan - 1,
128                                 endRow = new CKEDITOR.dom.element( table.$.rows[ endRowIndex ] ),
129                                 rowIndex = insertBefore ? startRowIndex : endRowIndex,
130                                 row = insertBefore ? startRow : endRow;
132                 var map = CKEDITOR.tools.buildTableMap( table ),
133                                 cloneRow = map[ rowIndex ],
134                                 nextRow = insertBefore ? map[ rowIndex - 1 ] : map[ rowIndex + 1 ],
135                                 width = map[0].length;
137                 var newRow = doc.createElement( 'tr' );
138                 for ( var i = 0; i < width; i++ )
139                 {
140                         var cell;
141                         // Check whether there's a spanning row here, do not break it.
142                         if ( cloneRow[ i ].rowSpan > 1 && nextRow && cloneRow[ i ] == nextRow[ i ] )
143                         {
144                                 cell = cloneRow[ i ];
145                                 cell.rowSpan += 1;
146                         }
147                         else
148                         {
149                                 cell = new CKEDITOR.dom.element( cloneRow[ i ] ).clone();
150                                 cell.removeAttribute( 'rowSpan' );
151                                 !CKEDITOR.env.ie && cell.appendBogus();
152                                 newRow.append( cell );
153                                 cell = cell.$;
154                         }
156                         i += cell.colSpan - 1;
157                 }
159                 insertBefore ?
160                 newRow.insertBefore( row ) :
161                 newRow.insertAfter( row );
162         }
164         function deleteRows( selectionOrRow )
165         {
166                 if ( selectionOrRow instanceof CKEDITOR.dom.selection )
167                 {
168                         var cells = getSelectedCells( selectionOrRow ),
169                                         firstCell = cells[ 0 ],
170                                         table = firstCell.getAscendant( 'table' ),
171                                         map = CKEDITOR.tools.buildTableMap( table ),
172                                         startRow = cells[ 0 ].getParent(),
173                                         startRowIndex = startRow.$.rowIndex,
174                                         lastCell = cells[ cells.length - 1 ],
175                                         endRowIndex = lastCell.getParent().$.rowIndex + lastCell.$.rowSpan - 1,
176                                         rowsToDelete = [];
178                         // Delete cell or reduce cell spans by checking through the table map.
179                         for ( var i = startRowIndex; i <= endRowIndex; i++ )
180                         {
181                                 var mapRow = map[ i ],
182                                                 row = new CKEDITOR.dom.element( table.$.rows[ i ] );
184                                 for ( var j = 0; j < mapRow.length; j++ )
185                                 {
186                                         var cell = new CKEDITOR.dom.element( mapRow[ j ] ),
187                                                         cellRowIndex = cell.getParent().$.rowIndex;
189                                         if ( cell.$.rowSpan == 1 )
190                                                 cell.remove();
191                                         // Row spanned cell.
192                                         else
193                                         {
194                                                 // Span row of the cell, reduce spanning.
195                                                 cell.$.rowSpan -= 1;
196                                                 // Root row of the cell, root cell to next row.
197                                                 if ( cellRowIndex == i )
198                                                 {
199                                                         var nextMapRow = map[ i + 1 ];
200                                                         nextMapRow[ j - 1 ] ?
201                                                         cell.insertAfter( new CKEDITOR.dom.element( nextMapRow[ j - 1 ] ) )
202                                                                         : new CKEDITOR.dom.element( table.$.rows[ i + 1 ] ).append( cell, 1 );
203                                                 }
204                                         }
206                                         j += cell.$.colSpan - 1;
207                                 }
209                                 rowsToDelete.push( row );
210                         }
212                         var rows = table.$.rows;
214                         // Where to put the cursor after rows been deleted?
215                         // 1. Into next sibling row if any;
216                         // 2. Into previous sibling row if any;
217                         // 3. Into table's parent element if it's the very last row.
218                         var cursorPosition =  new CKEDITOR.dom.element( rows[ startRowIndex ] || rows[ startRowIndex - 1 ] || table.$.parentNode );
220                         for ( i = rowsToDelete.length ; i >= 0 ; i-- )
221                                 deleteRows( rowsToDelete[ i ] );
223                         return cursorPosition;
224                 }
225                 else if ( selectionOrRow instanceof CKEDITOR.dom.element )
226                 {
227                         table = selectionOrRow.getAscendant( 'table' );
229                         if ( table.$.rows.length == 1 )
230                                 table.remove();
231                         else
232                                 selectionOrRow.remove();
233                 }
235                 return null;
236         }
238         function getCellColIndex( cell, isStart )
239         {
240                 var row = cell.getParent(),
241                         rowCells = row.$.cells;
243                 var colIndex = 0;
244                 for ( var i = 0; i < rowCells.length; i++ )
245                 {
246                         var mapCell = rowCells[ i ];
247                         colIndex += isStart ? 1 : mapCell.colSpan;
248                         if ( mapCell == cell.$ )
249                                 break;
250                 }
252                 return colIndex -1;
253         }
255         function getColumnsIndices( cells, isStart )
256         {
257                 var retval = isStart ? Infinity : 0;
258                 for ( var i = 0; i < cells.length; i++ )
259                 {
260                         var colIndex = getCellColIndex( cells[ i ], isStart );
261                         if ( isStart ? colIndex < retval  : colIndex > retval )
262                                 retval = colIndex;
263                 }
264                 return retval;
265         }
267         function insertColumn( selection, insertBefore )
268         {
269                 var cells = getSelectedCells( selection ),
270                         firstCell = cells[ 0 ],
271                         table = firstCell.getAscendant( 'table' ),
272                         startCol =  getColumnsIndices( cells, 1 ),
273                         lastCol =  getColumnsIndices( cells ),
274                         colIndex = insertBefore? startCol : lastCol;
276                 var map = CKEDITOR.tools.buildTableMap( table ),
277                         cloneCol = [],
278                         nextCol = [],
279                         height = map.length;
281                 for ( var i = 0; i < height; i++ )
282                 {
283                         cloneCol.push( map[ i ][ colIndex ] );
284                         var nextCell = insertBefore ? map[ i ][ colIndex - 1 ] : map[ i ][ colIndex + 1 ];
285                         nextCell && nextCol.push( nextCell );
286                 }
288                 for ( i = 0; i < height; i++ )
289                 {
290                         var cell;
291                         // Check whether there's a spanning column here, do not break it.
292                         if ( cloneCol[ i ].colSpan > 1
293                                 && nextCol.length
294                                 && nextCol[ i ] == cloneCol[ i ] )
295                         {
296                                 cell = cloneCol[ i ];
297                                 cell.colSpan += 1;
298                         }
299                         else
300                         {
301                                 cell = new CKEDITOR.dom.element( cloneCol[ i ] ).clone();
302                                 cell.removeAttribute( 'colSpan' );
303                                 !CKEDITOR.env.ie && cell.appendBogus();
304                                 cell[ insertBefore? 'insertBefore' : 'insertAfter' ].call( cell, new CKEDITOR.dom.element ( cloneCol[ i ] ) );
305                                 cell = cell.$;
306                         }
308                         i += cell.rowSpan - 1;
309                 }
310         }
312         function deleteColumns( selectionOrCell )
313         {
314                 var cells = getSelectedCells( selectionOrCell ),
315                                 firstCell = cells[ 0 ],
316                                 lastCell = cells[ cells.length - 1 ],
317                                 table = firstCell.getAscendant( 'table' ),
318                                 map = CKEDITOR.tools.buildTableMap( table ),
319                                 startColIndex,
320                                 endColIndex,
321                                 rowsToDelete = [];
323                 // Figure out selected cells' column indices.
324                 for ( var i = 0, rows = map.length; i < rows; i++ )
325                 {
326                         for ( var j = 0, cols = map[ i ].length; j < cols; j++ )
327                         {
328                                 if ( map[ i ][ j ] == firstCell.$ )
329                                         startColIndex = j;
330                                 if ( map[ i ][ j ] == lastCell.$ )
331                                         endColIndex = j;
332                         }
333                 }
335                 // Delete cell or reduce cell spans by checking through the table map.
336                 for ( i = startColIndex; i <= endColIndex; i++ )
337                 {
338                         for ( j = 0; j < map.length; j++ )
339                         {
340                                 var mapRow = map[ j ],
341                                         row = new CKEDITOR.dom.element( table.$.rows[ j ] ),
342                                         cell = new CKEDITOR.dom.element( mapRow[ i ] );
344                                 if ( cell.$.colSpan == 1 )
345                                         cell.remove();
346                                 // Reduce the col spans.
347                                 else
348                                         cell.$.colSpan -= 1;
350                                 j += cell.$.rowSpan - 1;
352                                 if ( !row.$.cells.length )
353                                         rowsToDelete.push( row );
354                         }
355                 }
357                 var firstRowCells = table.$.rows[ 0 ] && table.$.rows[ 0 ].cells;
359                 // Where to put the cursor after columns been deleted?
360                 // 1. Into next cell of the first row if any;
361                 // 2. Into previous cell of the first row if any;
362                 // 3. Into table's parent element;
363                 var cursorPosition =  new CKEDITOR.dom.element( firstRowCells[ startColIndex ] || ( startColIndex ? firstRowCells[ startColIndex - 1 ] : table.$.parentNode ) );
365                 // Delete table rows only if all columns are gone (do not remove empty row).
366                 if ( rowsToDelete.length == rows )
367                         table.remove();
369                 return cursorPosition;
370         }
372         function getFocusElementAfterDelCols( cells )
373         {
374                 var cellIndexList = [],
375                         table = cells[ 0 ] && cells[ 0 ].getAscendant( 'table' ),
376                         i, length,
377                         targetIndex, targetCell;
379                 // get the cellIndex list of delete cells
380                 for ( i = 0, length = cells.length; i < length; i++ )
381                         cellIndexList.push( cells[i].$.cellIndex );
383                 // get the focusable column index
384                 cellIndexList.sort();
385                 for ( i = 1, length = cellIndexList.length; i < length; i++ )
386                 {
387                         if ( cellIndexList[ i ] - cellIndexList[ i - 1 ] > 1 )
388                         {
389                                 targetIndex = cellIndexList[ i - 1 ] + 1;
390                                 break;
391                         }
392                 }
394                 if ( !targetIndex )
395                         targetIndex = cellIndexList[ 0 ] > 0 ? ( cellIndexList[ 0 ] - 1 )
396                                                         : ( cellIndexList[ cellIndexList.length - 1 ] + 1 );
398                 // scan row by row to get the target cell
399                 var rows = table.$.rows;
400                 for ( i = 0, length = rows.length; i < length ; i++ )
401                 {
402                         targetCell = rows[ i ].cells[ targetIndex ];
403                         if ( targetCell )
404                                 break;
405                 }
407                 return targetCell ?  new CKEDITOR.dom.element( targetCell ) :  table.getPrevious();
408         }
410         function insertCell( selection, insertBefore )
411         {
412                 var startElement = selection.getStartElement();
413                 var cell = startElement.getAscendant( 'td', 1 ) || startElement.getAscendant( 'th', 1 );
415                 if ( !cell )
416                         return;
418                 // Create the new cell element to be added.
419                 var newCell = cell.clone();
420                 if ( !CKEDITOR.env.ie )
421                         newCell.appendBogus();
423                 if ( insertBefore )
424                         newCell.insertBefore( cell );
425                 else
426                         newCell.insertAfter( cell );
427         }
429         function deleteCells( selectionOrCell )
430         {
431                 if ( selectionOrCell instanceof CKEDITOR.dom.selection )
432                 {
433                         var cellsToDelete = getSelectedCells( selectionOrCell );
434                         var table = cellsToDelete[ 0 ] && cellsToDelete[ 0 ].getAscendant( 'table' );
435                         var cellToFocus   = getFocusElementAfterDelCells( cellsToDelete );
437                         for ( var i = cellsToDelete.length - 1 ; i >= 0 ; i-- )
438                                 deleteCells( cellsToDelete[ i ] );
440                         if ( cellToFocus )
441                                 placeCursorInCell( cellToFocus, true );
442                         else if ( table )
443                                 table.remove();
444                 }
445                 else if ( selectionOrCell instanceof CKEDITOR.dom.element )
446                 {
447                         var tr = selectionOrCell.getParent();
448                         if ( tr.getChildCount() == 1 )
449                                 tr.remove();
450                         else
451                                 selectionOrCell.remove();
452                 }
453         }
455         // Remove filler at end and empty spaces around the cell content.
456         function trimCell( cell )
457         {
458                 var bogus = cell.getBogus();
459                 bogus && bogus.remove();
460                 cell.trim();
461         }
463         function placeCursorInCell( cell, placeAtEnd )
464         {
465                 var range = new CKEDITOR.dom.range( cell.getDocument() );
466                 if ( !range[ 'moveToElementEdit' + ( placeAtEnd ? 'End' : 'Start' ) ]( cell ) )
467                 {
468                         range.selectNodeContents( cell );
469                         range.collapse( placeAtEnd ? false : true );
470                 }
471                 range.select( true );
472         }
474         function cellInRow( tableMap, rowIndex, cell )
475         {
476                 var oRow = tableMap[ rowIndex ];
477                 if ( typeof cell == 'undefined' )
478                         return oRow;
480                 for ( var c = 0 ; oRow && c < oRow.length ; c++ )
481                 {
482                         if ( cell.is && oRow[c] == cell.$ )
483                                 return c;
484                         else if ( c == cell )
485                                 return new CKEDITOR.dom.element( oRow[ c ] );
486                 }
487                 return cell.is ? -1 : null;
488         }
490         function cellInCol( tableMap, colIndex, cell )
491         {
492                 var oCol = [];
493                 for ( var r = 0; r < tableMap.length; r++ )
494                 {
495                         var row = tableMap[ r ];
496                         if ( typeof cell == 'undefined' )
497                                 oCol.push( row[ colIndex ] );
498                         else if ( cell.is && row[ colIndex ] == cell.$ )
499                                 return r;
500                         else if ( r == cell )
501                                 return new CKEDITOR.dom.element( row[ colIndex ] );
502                 }
504                 return ( typeof cell == 'undefined' )? oCol : cell.is ? -1 :  null;
505         }
507         function mergeCells( selection, mergeDirection, isDetect )
508         {
509                 var cells = getSelectedCells( selection );
511                 // Invalid merge request if:
512                 // 1. In batch mode despite that less than two selected.
513                 // 2. In solo mode while not exactly only one selected.
514                 // 3. Cells distributed in different table groups (e.g. from both thead and tbody).
515                 var commonAncestor;
516                 if ( ( mergeDirection ? cells.length != 1 : cells.length < 2 )
517                                 || ( commonAncestor = selection.getCommonAncestor() )
518                                 && commonAncestor.type == CKEDITOR.NODE_ELEMENT
519                                 && commonAncestor.is( 'table' ) )
520                 {
521                         return false;
522                 }
524                 var     cell,
525                         firstCell = cells[ 0 ],
526                         table = firstCell.getAscendant( 'table' ),
527                         map = CKEDITOR.tools.buildTableMap( table ),
528                         mapHeight = map.length,
529                         mapWidth = map[ 0 ].length,
530                         startRow = firstCell.getParent().$.rowIndex,
531                         startColumn = cellInRow( map, startRow, firstCell );
533                 if ( mergeDirection )
534                 {
535                         var targetCell;
536                         try
537                         {
538                                 var rowspan = parseInt( firstCell.getAttribute( 'rowspan' ), 10 ) || 1;
539                                 var colspan = parseInt( firstCell.getAttribute( 'colspan' ), 10 ) || 1;
541                                 targetCell =
542                                         map[ mergeDirection == 'up' ?
543                                                         ( startRow - rowspan ):
544                                                         mergeDirection == 'down' ? ( startRow + rowspan ) : startRow  ] [
545                                                 mergeDirection == 'left' ?
546                                                         ( startColumn - colspan ):
547                                                 mergeDirection == 'right' ?  ( startColumn + colspan ) : startColumn ];
549                         }
550                         catch( er )
551                         {
552                                 return false;
553                         }
555                         // 1. No cell could be merged.
556                         // 2. Same cell actually.
557                         if ( !targetCell || firstCell.$ == targetCell  )
558                                 return false;
560                         // Sort in map order regardless of the DOM sequence.
561                         cells[ ( mergeDirection == 'up' || mergeDirection == 'left' ) ?
562                                  'unshift' : 'push' ]( new CKEDITOR.dom.element( targetCell ) );
563                 }
565                 // Start from here are merging way ignorance (merge up/right, batch merge).
566                 var     doc = firstCell.getDocument(),
567                         lastRowIndex = startRow,
568                         totalRowSpan = 0,
569                         totalColSpan = 0,
570                         // Use a documentFragment as buffer when appending cell contents.
571                         frag = !isDetect && new CKEDITOR.dom.documentFragment( doc ),
572                         dimension = 0;
574                 for ( var i = 0; i < cells.length; i++ )
575                 {
576                         cell = cells[ i ];
578                         var tr = cell.getParent(),
579                                 cellFirstChild = cell.getFirst(),
580                                 colSpan = cell.$.colSpan,
581                                 rowSpan = cell.$.rowSpan,
582                                 rowIndex = tr.$.rowIndex,
583                                 colIndex = cellInRow( map, rowIndex, cell );
585                         // Accumulated the actual places taken by all selected cells.
586                         dimension += colSpan * rowSpan;
587                         // Accumulated the maximum virtual spans from column and row.
588                         totalColSpan = Math.max( totalColSpan, colIndex - startColumn + colSpan ) ;
589                         totalRowSpan = Math.max( totalRowSpan, rowIndex - startRow + rowSpan );
591                         if ( !isDetect )
592                         {
593                                 // Trim all cell fillers and check to remove empty cells.
594                                 if ( trimCell( cell ), cell.getChildren().count() )
595                                 {
596                                         // Merge vertically cells as two separated paragraphs.
597                                         if ( rowIndex != lastRowIndex
598                                                 && cellFirstChild
599                                                 && !( cellFirstChild.isBlockBoundary
600                                                           && cellFirstChild.isBlockBoundary( { br : 1 } ) ) )
601                                         {
602                                                 var last = frag.getLast( CKEDITOR.dom.walker.whitespaces( true ) );
603                                                 if ( last && !( last.is && last.is( 'br' ) ) )
604                                                         frag.append( 'br' );
605                                         }
607                                         cell.moveChildren( frag );
608                                 }
609                                 i ? cell.remove() : cell.setHtml( '' );
610                         }
611                         lastRowIndex = rowIndex;
612                 }
614                 if ( !isDetect )
615                 {
616                         frag.moveChildren( firstCell );
618                         if ( !CKEDITOR.env.ie )
619                                 firstCell.appendBogus();
621                         if ( totalColSpan >= mapWidth )
622                                 firstCell.removeAttribute( 'rowSpan' );
623                         else
624                                 firstCell.$.rowSpan = totalRowSpan;
626                         if ( totalRowSpan >= mapHeight )
627                                 firstCell.removeAttribute( 'colSpan' );
628                         else
629                                 firstCell.$.colSpan = totalColSpan;
631                         // Swip empty <tr> left at the end of table due to the merging.
632                         var trs = new CKEDITOR.dom.nodeList( table.$.rows ),
633                                 count = trs.count();
635                         for ( i = count - 1; i >= 0; i-- )
636                         {
637                                 var tailTr = trs.getItem( i );
638                                 if ( !tailTr.$.cells.length )
639                                 {
640                                         tailTr.remove();
641                                         count++;
642                                         continue;
643                                 }
644                         }
646                         return firstCell;
647                 }
648                 // Be able to merge cells only if actual dimension of selected
649                 // cells equals to the caculated rectangle.
650                 else
651                         return ( totalRowSpan * totalColSpan ) == dimension;
652         }
654         function verticalSplitCell ( selection, isDetect )
655         {
656                 var cells = getSelectedCells( selection );
657                 if ( cells.length > 1 )
658                         return false;
659                 else if ( isDetect )
660                         return true;
662                 var cell = cells[ 0 ],
663                         tr = cell.getParent(),
664                         table = tr.getAscendant( 'table' ),
665                         map = CKEDITOR.tools.buildTableMap( table ),
666                         rowIndex = tr.$.rowIndex,
667                         colIndex = cellInRow( map, rowIndex, cell ),
668                         rowSpan = cell.$.rowSpan,
669                         newCell,
670                         newRowSpan,
671                         newCellRowSpan,
672                         newRowIndex;
674                 if ( rowSpan > 1 )
675                 {
676                         newRowSpan = Math.ceil( rowSpan / 2 );
677                         newCellRowSpan = Math.floor( rowSpan / 2 );
678                         newRowIndex = rowIndex + newRowSpan;
679                         var newCellTr = new CKEDITOR.dom.element( table.$.rows[ newRowIndex ] ),
680                                 newCellRow = cellInRow( map, newRowIndex ),
681                                 candidateCell;
683                         newCell = cell.clone();
685                         // Figure out where to insert the new cell by checking the vitual row.
686                         for ( var c = 0; c < newCellRow.length; c++ )
687                         {
688                                 candidateCell = newCellRow[ c ];
689                                 // Catch first cell actually following the column.
690                                 if ( candidateCell.parentNode == newCellTr.$
691                                         && c > colIndex )
692                                 {
693                                         newCell.insertBefore( new CKEDITOR.dom.element( candidateCell ) );
694                                         break;
695                                 }
696                                 else
697                                         candidateCell = null;
698                         }
700                         // The destination row is empty, append at will.
701                         if ( !candidateCell )
702                                 newCellTr.append( newCell, true );
703                 }
704                 else
705                 {
706                         newCellRowSpan = newRowSpan = 1;
708                         newCellTr = tr.clone();
709                         newCellTr.insertAfter( tr );
710                         newCellTr.append( newCell = cell.clone() );
712                         var cellsInSameRow = cellInRow( map, rowIndex );
713                         for ( var i = 0; i < cellsInSameRow.length; i++ )
714                                 cellsInSameRow[ i ].rowSpan++;
715                 }
717                 if ( !CKEDITOR.env.ie )
718                         newCell.appendBogus();
720                 cell.$.rowSpan = newRowSpan;
721                 newCell.$.rowSpan = newCellRowSpan;
722                 if ( newRowSpan == 1 )
723                         cell.removeAttribute( 'rowSpan' );
724                 if ( newCellRowSpan == 1 )
725                         newCell.removeAttribute( 'rowSpan' );
727                 return newCell;
728         }
730         function horizontalSplitCell( selection, isDetect )
731         {
732                 var cells = getSelectedCells( selection );
733                 if ( cells.length > 1 )
734                         return false;
735                 else if ( isDetect )
736                         return true;
738                 var cell = cells[ 0 ],
739                         tr = cell.getParent(),
740                         table = tr.getAscendant( 'table' ),
741                         map = CKEDITOR.tools.buildTableMap( table ),
742                         rowIndex = tr.$.rowIndex,
743                         colIndex = cellInRow( map, rowIndex, cell ),
744                         colSpan = cell.$.colSpan,
745                         newCell,
746                         newColSpan,
747                         newCellColSpan;
749                 if ( colSpan > 1 )
750                 {
751                         newColSpan = Math.ceil( colSpan / 2 );
752                         newCellColSpan = Math.floor( colSpan / 2 );
753                 }
754                 else
755                 {
756                         newCellColSpan = newColSpan = 1;
757                         var cellsInSameCol = cellInCol( map, colIndex );
758                         for ( var i = 0; i < cellsInSameCol.length; i++ )
759                                 cellsInSameCol[ i ].colSpan++;
760                 }
761                 newCell = cell.clone();
762                 newCell.insertAfter( cell );
763                 if ( !CKEDITOR.env.ie )
764                         newCell.appendBogus();
766                 cell.$.colSpan = newColSpan;
767                 newCell.$.colSpan = newCellColSpan;
768                 if ( newColSpan == 1 )
769                         cell.removeAttribute( 'colSpan' );
770                 if ( newCellColSpan == 1 )
771                         newCell.removeAttribute( 'colSpan' );
773                 return newCell;
774         }
775         // Context menu on table caption incorrect (#3834)
776         var contextMenuTags = { thead : 1, tbody : 1, tfoot : 1, td : 1, tr : 1, th : 1 };
778         CKEDITOR.plugins.tabletools =
779         {
780                 init : function( editor )
781                 {
782                         var lang = editor.lang.table;
784                         editor.addCommand( 'cellProperties', new CKEDITOR.dialogCommand( 'cellProperties' ) );
785                         CKEDITOR.dialog.add( 'cellProperties', this.path + 'dialogs/tableCell.js' );
787                         editor.addCommand( 'tableDelete',
788                                 {
789                                         exec : function( editor )
790                                         {
791                                                 var selection = editor.getSelection(),
792                                                         startElement = selection && selection.getStartElement(),
793                                                         table = startElement && startElement.getAscendant( 'table', 1 );
795                                                 if ( !table )
796                                                         return;
798                                                 // Maintain the selection point at where the table was deleted.
799                                                 selection.selectElement( table );
800                                                 var range = selection.getRanges()[0];
801                                                 range.collapse();
802                                                 selection.selectRanges( [ range ] );
804                                                 // If the table's parent has only one child remove it as well (unless it's the body or a table cell) (#5416, #6289)
805                                                 var parent = table.getParent();
806                                                 if ( parent.getChildCount() == 1 && !parent.is( 'body', 'td', 'th' ) )
807                                                         parent.remove();
808                                                 else
809                                                         table.remove();
810                                         }
811                                 } );
813                         editor.addCommand( 'rowDelete',
814                                 {
815                                         exec : function( editor )
816                                         {
817                                                 var selection = editor.getSelection();
818                                                 placeCursorInCell( deleteRows( selection ) );
819                                         }
820                                 } );
822                         editor.addCommand( 'rowInsertBefore',
823                                 {
824                                         exec : function( editor )
825                                         {
826                                                 var selection = editor.getSelection();
827                                                 insertRow( selection, true );
828                                         }
829                                 } );
831                         editor.addCommand( 'rowInsertAfter',
832                                 {
833                                         exec : function( editor )
834                                         {
835                                                 var selection = editor.getSelection();
836                                                 insertRow( selection );
837                                         }
838                                 } );
840                         editor.addCommand( 'columnDelete',
841                                 {
842                                         exec : function( editor )
843                                         {
844                                                 var selection = editor.getSelection();
845                                                 var element = deleteColumns( selection );
846                                                 element &&  placeCursorInCell( element, true );
847                                         }
848                                 } );
850                         editor.addCommand( 'columnInsertBefore',
851                                 {
852                                         exec : function( editor )
853                                         {
854                                                 var selection = editor.getSelection();
855                                                 insertColumn( selection, true );
856                                         }
857                                 } );
859                         editor.addCommand( 'columnInsertAfter',
860                                 {
861                                         exec : function( editor )
862                                         {
863                                                 var selection = editor.getSelection();
864                                                 insertColumn( selection );
865                                         }
866                                 } );
868                         editor.addCommand( 'cellDelete',
869                                 {
870                                         exec : function( editor )
871                                         {
872                                                 var selection = editor.getSelection();
873                                                 deleteCells( selection );
874                                         }
875                                 } );
877                         editor.addCommand( 'cellMerge',
878                                 {
879                                         exec : function( editor )
880                                         {
881                                                 placeCursorInCell( mergeCells( editor.getSelection() ), true );
882                                         }
883                                 } );
885                         editor.addCommand( 'cellMergeRight',
886                                 {
887                                         exec : function( editor )
888                                         {
889                                                 placeCursorInCell( mergeCells( editor.getSelection(), 'right' ), true );
890                                         }
891                                 } );
893                         editor.addCommand( 'cellMergeDown',
894                                 {
895                                         exec : function( editor )
896                                         {
897                                                 placeCursorInCell( mergeCells( editor.getSelection(), 'down' ), true );
898                                         }
899                                 } );
901                         editor.addCommand( 'cellVerticalSplit',
902                                 {
903                                         exec : function( editor )
904                                         {
905                                                 placeCursorInCell( verticalSplitCell( editor.getSelection() ) );
906                                         }
907                                 } );
909                         editor.addCommand( 'cellHorizontalSplit',
910                                 {
911                                         exec : function( editor )
912                                         {
913                                                 placeCursorInCell( horizontalSplitCell( editor.getSelection() ) );
914                                         }
915                                 } );
917                         editor.addCommand( 'cellInsertBefore',
918                                 {
919                                         exec : function( editor )
920                                         {
921                                                 var selection = editor.getSelection();
922                                                 insertCell( selection, true );
923                                         }
924                                 } );
926                         editor.addCommand( 'cellInsertAfter',
927                                 {
928                                         exec : function( editor )
929                                         {
930                                                 var selection = editor.getSelection();
931                                                 insertCell( selection );
932                                         }
933                                 } );
935                         // If the "menu" plugin is loaded, register the menu items.
936                         if ( editor.addMenuItems )
937                         {
938                                 editor.addMenuItems(
939                                         {
940                                                 tablecell :
941                                                 {
942                                                         label : lang.cell.menu,
943                                                         group : 'tablecell',
944                                                         order : 1,
945                                                         getItems : function()
946                                                         {
947                                                                 var selection = editor.getSelection(),
948                                                                         cells = getSelectedCells( selection );
949                                                                 return {
950                                                                         tablecell_insertBefore : CKEDITOR.TRISTATE_OFF,
951                                                                         tablecell_insertAfter : CKEDITOR.TRISTATE_OFF,
952                                                                         tablecell_delete : CKEDITOR.TRISTATE_OFF,
953                                                                         tablecell_merge : mergeCells( selection, null, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
954                                                                         tablecell_merge_right : mergeCells( selection, 'right', true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
955                                                                         tablecell_merge_down : mergeCells( selection, 'down', true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
956                                                                         tablecell_split_vertical : verticalSplitCell( selection, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
957                                                                         tablecell_split_horizontal : horizontalSplitCell( selection, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
958                                                                         tablecell_properties : cells.length > 0 ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
959                                                                 };
960                                                         }
961                                                 },
963                                                 tablecell_insertBefore :
964                                                 {
965                                                         label : lang.cell.insertBefore,
966                                                         group : 'tablecell',
967                                                         command : 'cellInsertBefore',
968                                                         order : 5
969                                                 },
971                                                 tablecell_insertAfter :
972                                                 {
973                                                         label : lang.cell.insertAfter,
974                                                         group : 'tablecell',
975                                                         command : 'cellInsertAfter',
976                                                         order : 10
977                                                 },
979                                                 tablecell_delete :
980                                                 {
981                                                         label : lang.cell.deleteCell,
982                                                         group : 'tablecell',
983                                                         command : 'cellDelete',
984                                                         order : 15
985                                                 },
987                                                 tablecell_merge :
988                                                 {
989                                                         label : lang.cell.merge,
990                                                         group : 'tablecell',
991                                                         command : 'cellMerge',
992                                                         order : 16
993                                                 },
995                                                 tablecell_merge_right :
996                                                 {
997                                                         label : lang.cell.mergeRight,
998                                                         group : 'tablecell',
999                                                         command : 'cellMergeRight',
1000                                                         order : 17
1001                                                 },
1003                                                 tablecell_merge_down :
1004                                                 {
1005                                                         label : lang.cell.mergeDown,
1006                                                         group : 'tablecell',
1007                                                         command : 'cellMergeDown',
1008                                                         order : 18
1009                                                 },
1011                                                 tablecell_split_horizontal :
1012                                                 {
1013                                                         label : lang.cell.splitHorizontal,
1014                                                         group : 'tablecell',
1015                                                         command : 'cellHorizontalSplit',
1016                                                         order : 19
1017                                                 },
1019                                                 tablecell_split_vertical :
1020                                                 {
1021                                                         label : lang.cell.splitVertical,
1022                                                         group : 'tablecell',
1023                                                         command : 'cellVerticalSplit',
1024                                                         order : 20
1025                                                 },
1027                                                 tablecell_properties :
1028                                                 {
1029                                                         label : lang.cell.title,
1030                                                         group : 'tablecellproperties',
1031                                                         command : 'cellProperties',
1032                                                         order : 21
1033                                                 },
1035                                                 tablerow :
1036                                                 {
1037                                                         label : lang.row.menu,
1038                                                         group : 'tablerow',
1039                                                         order : 1,
1040                                                         getItems : function()
1041                                                         {
1042                                                                 return {
1043                                                                         tablerow_insertBefore : CKEDITOR.TRISTATE_OFF,
1044                                                                         tablerow_insertAfter : CKEDITOR.TRISTATE_OFF,
1045                                                                         tablerow_delete : CKEDITOR.TRISTATE_OFF
1046                                                                 };
1047                                                         }
1048                                                 },
1050                                                 tablerow_insertBefore :
1051                                                 {
1052                                                         label : lang.row.insertBefore,
1053                                                         group : 'tablerow',
1054                                                         command : 'rowInsertBefore',
1055                                                         order : 5
1056                                                 },
1058                                                 tablerow_insertAfter :
1059                                                 {
1060                                                         label : lang.row.insertAfter,
1061                                                         group : 'tablerow',
1062                                                         command : 'rowInsertAfter',
1063                                                         order : 10
1064                                                 },
1066                                                 tablerow_delete :
1067                                                 {
1068                                                         label : lang.row.deleteRow,
1069                                                         group : 'tablerow',
1070                                                         command : 'rowDelete',
1071                                                         order : 15
1072                                                 },
1074                                                 tablecolumn :
1075                                                 {
1076                                                         label : lang.column.menu,
1077                                                         group : 'tablecolumn',
1078                                                         order : 1,
1079                                                         getItems : function()
1080                                                         {
1081                                                                 return {
1082                                                                         tablecolumn_insertBefore : CKEDITOR.TRISTATE_OFF,
1083                                                                         tablecolumn_insertAfter : CKEDITOR.TRISTATE_OFF,
1084                                                                         tablecolumn_delete : CKEDITOR.TRISTATE_OFF
1085                                                                 };
1086                                                         }
1087                                                 },
1089                                                 tablecolumn_insertBefore :
1090                                                 {
1091                                                         label : lang.column.insertBefore,
1092                                                         group : 'tablecolumn',
1093                                                         command : 'columnInsertBefore',
1094                                                         order : 5
1095                                                 },
1097                                                 tablecolumn_insertAfter :
1098                                                 {
1099                                                         label : lang.column.insertAfter,
1100                                                         group : 'tablecolumn',
1101                                                         command : 'columnInsertAfter',
1102                                                         order : 10
1103                                                 },
1105                                                 tablecolumn_delete :
1106                                                 {
1107                                                         label : lang.column.deleteColumn,
1108                                                         group : 'tablecolumn',
1109                                                         command : 'columnDelete',
1110                                                         order : 15
1111                                                 }
1112                                         });
1113                         }
1115                         // If the "contextmenu" plugin is laoded, register the listeners.
1116                         if ( editor.contextMenu )
1117                         {
1118                                 editor.contextMenu.addListener( function( element, selection )
1119                                         {
1120                                                 if ( !element || element.isReadOnly() )
1121                                                         return null;
1123                                                 while ( element )
1124                                                 {
1125                                                         if ( element.getName() in contextMenuTags )
1126                                                         {
1127                                                                 return {
1128                                                                         tablecell : CKEDITOR.TRISTATE_OFF,
1129                                                                         tablerow : CKEDITOR.TRISTATE_OFF,
1130                                                                         tablecolumn : CKEDITOR.TRISTATE_OFF
1131                                                                 };
1132                                                         }
1133                                                         element = element.getParent();
1134                                                 }
1136                                                 return null;
1137                                         } );
1138                         }
1139                 },
1141                 getSelectedCells : getSelectedCells
1143         };
1144         CKEDITOR.plugins.add( 'tabletools', CKEDITOR.plugins.tabletools );
1145 })();
1148  * Create a two-dimension array that reflects the actual layout of table cells,
1149  * with cell spans, with mappings to the original td elements.
1150  * @param table {CKEDITOR.dom.element}
1151  */
1152 CKEDITOR.tools.buildTableMap = function ( table )
1154         var aRows = table.$.rows ;
1156         // Row and Column counters.
1157         var r = -1 ;
1159         var aMap = [];
1161         for ( var i = 0 ; i < aRows.length ; i++ )
1162         {
1163                 r++ ;
1164                 !aMap[r] && ( aMap[r] = [] );
1166                 var c = -1 ;
1168                 for ( var j = 0 ; j < aRows[i].cells.length ; j++ )
1169                 {
1170                         var oCell = aRows[i].cells[j] ;
1172                         c++ ;
1173                         while ( aMap[r][c] )
1174                                 c++ ;
1176                         var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ;
1177                         var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ;
1179                         for ( var rs = 0 ; rs < iRowSpan ; rs++ )
1180                         {
1181                                 if ( !aMap[r + rs] )
1182                                         aMap[r + rs] = [];
1184                                 for ( var cs = 0 ; cs < iColSpan ; cs++ )
1185                                 {
1186                                         aMap[r + rs][c + cs] = aRows[i].cells[j] ;
1187                                 }
1188                         }
1190                         c += iColSpan - 1 ;
1191                 }
1192         }
1193         return aMap ;