JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.5.1
[ckeditor.git] / _source / plugins / tabletools / plugin.js
index 0019a49..cacc8cf 100644 (file)
@@ -1,18 +1,10 @@
 /*\r
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
 (function()\r
 {\r
-       function removeRawAttribute( $node, attr )\r
-       {\r
-               if ( CKEDITOR.env.ie )\r
-                       $node.removeAttribute( attr );\r
-               else\r
-                       delete $node[ attr ];\r
-       }\r
-\r
        var cellNodeRegex = /^(?:td|th)$/;\r
 \r
        function getSelectedCells( selection )\r
@@ -123,37 +115,50 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                return null;\r
        }\r
 \r
-       function clearRow( $tr )\r
+       function insertRow( selection, insertBefore )\r
        {\r
-               // Get the array of row's cells.\r
-               var $cells = $tr.cells;\r
-\r
-               // Empty all cells.\r
-               for ( var i = 0 ; i < $cells.length ; i++ )\r
+               var cells = getSelectedCells( selection ),\r
+                               firstCell = cells[ 0 ],\r
+                               table = firstCell.getAscendant( 'table' ),\r
+                               doc = firstCell.getDocument(),\r
+                               startRow = cells[ 0 ].getParent(),\r
+                               startRowIndex = startRow.$.rowIndex,\r
+                               lastCell = cells[ cells.length - 1 ],\r
+                               endRowIndex = lastCell.getParent().$.rowIndex + lastCell.$.rowSpan - 1,\r
+                               endRow = new CKEDITOR.dom.element( table.$.rows[ endRowIndex ] ),\r
+                               rowIndex = insertBefore ? startRowIndex : endRowIndex,\r
+                               row = insertBefore ? startRow : endRow;\r
+\r
+               var map = CKEDITOR.tools.buildTableMap( table ),\r
+                               cloneRow = map[ rowIndex ],\r
+                               nextRow = insertBefore ? map[ rowIndex - 1 ] : map[ rowIndex + 1 ],\r
+                               width = map[0].length;\r
+\r
+               var newRow = doc.createElement( 'tr' );\r
+               for ( var i = 0; i < width; i++ )\r
                {\r
-                       $cells[ i ].innerHTML = '';\r
+                       var cell;\r
+                       // Check whether there's a spanning row here, do not break it.\r
+                       if ( cloneRow[ i ].rowSpan > 1 && nextRow && cloneRow[ i ] == nextRow[ i ] )\r
+                       {\r
+                               cell = cloneRow[ i ];\r
+                               cell.rowSpan += 1;\r
+                       }\r
+                       else\r
+                       {\r
+                               cell = new CKEDITOR.dom.element( cloneRow[ i ] ).clone();\r
+                               cell.removeAttribute( 'rowSpan' );\r
+                               !CKEDITOR.env.ie && cell.appendBogus();\r
+                               newRow.append( cell );\r
+                               cell = cell.$;\r
+                       }\r
 \r
-                       if ( !CKEDITOR.env.ie )\r
-                               ( new CKEDITOR.dom.element( $cells[ i ] ) ).appendBogus();\r
+                       i += cell.colSpan - 1;\r
                }\r
-       }\r
-\r
-       function insertRow( selection, insertBefore )\r
-       {\r
-               // Get the row where the selection is placed in.\r
-               var row = selection.getStartElement().getAscendant( 'tr' );\r
-               if ( !row )\r
-                       return;\r
-\r
-               // Create a clone of the row.\r
-               var newRow = row.clone( 1 );\r
 \r
                insertBefore ?\r
-                       newRow.insertBefore( row ) :\r
-                       newRow.insertAfter( row );\r
-\r
-               // Clean the new row.\r
-               clearRow( newRow.$ );\r
+               newRow.insertBefore( row ) :\r
+               newRow.insertAfter( row );\r
        }\r
 \r
        function deleteRows( selectionOrRow )\r
@@ -161,41 +166,59 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                if ( selectionOrRow instanceof CKEDITOR.dom.selection )\r
                {\r
                        var cells = getSelectedCells( selectionOrRow ),\r
-                               cellsCount = cells.length,\r
-                               rowsToDelete = [],\r
-                               cursorPosition,\r
-                               previousRowIndex,\r
-                               nextRowIndex;\r
-\r
-                       // Queue up the rows - it's possible and likely that we have duplicates.\r
-                       for ( var i = 0 ; i < cellsCount ; i++ )\r
+                                       firstCell = cells[ 0 ],\r
+                                       table = firstCell.getAscendant( 'table' ),\r
+                                       map = CKEDITOR.tools.buildTableMap( table ),\r
+                                       startRow = cells[ 0 ].getParent(),\r
+                                       startRowIndex = startRow.$.rowIndex,\r
+                                       lastCell = cells[ cells.length - 1 ],\r
+                                       endRowIndex = lastCell.getParent().$.rowIndex + lastCell.$.rowSpan - 1,\r
+                                       rowsToDelete = [];\r
+\r
+                       // Delete cell or reduce cell spans by checking through the table map.\r
+                       for ( var i = startRowIndex; i <= endRowIndex; i++ )\r
                        {\r
-                               var row = cells[ i ].getParent(),\r
-                                               rowIndex = row.$.rowIndex;\r
+                               var mapRow = map[ i ],\r
+                                               row = new CKEDITOR.dom.element( table.$.rows[ i ] );\r
+\r
+                               for ( var j = 0; j < mapRow.length; j++ )\r
+                               {\r
+                                       var cell = new CKEDITOR.dom.element( mapRow[ j ] ),\r
+                                                       cellRowIndex = cell.getParent().$.rowIndex;\r
+\r
+                                       if ( cell.$.rowSpan == 1 )\r
+                                               cell.remove();\r
+                                       // Row spanned cell.\r
+                                       else\r
+                                       {\r
+                                               // Span row of the cell, reduce spanning.\r
+                                               cell.$.rowSpan -= 1;\r
+                                               // Root row of the cell, root cell to next row.\r
+                                               if ( cellRowIndex == i )\r
+                                               {\r
+                                                       var nextMapRow = map[ i + 1 ];\r
+                                                       nextMapRow[ j - 1 ] ?\r
+                                                       cell.insertAfter( new CKEDITOR.dom.element( nextMapRow[ j - 1 ] ) )\r
+                                                                       : new CKEDITOR.dom.element( table.$.rows[ i + 1 ] ).append( cell, 1 );\r
+                                               }\r
+                                       }\r
 \r
-                               !i && ( previousRowIndex = rowIndex - 1 );\r
-                               rowsToDelete[ rowIndex ] = row;\r
-                               i == cellsCount - 1 && ( nextRowIndex = rowIndex + 1 );\r
+                                       j += cell.$.colSpan - 1;\r
+                               }\r
+\r
+                               rowsToDelete.push( row );\r
                        }\r
 \r
-                       var table = row.getAscendant( 'table' ),\r
-                                       rows =  table.$.rows,\r
-                                       rowCount = rows.length;\r
+                       var rows = table.$.rows;\r
 \r
                        // Where to put the cursor after rows been deleted?\r
                        // 1. Into next sibling row if any;\r
                        // 2. Into previous sibling row if any;\r
                        // 3. Into table's parent element if it's the very last row.\r
-                       cursorPosition = new CKEDITOR.dom.element(\r
-                               nextRowIndex < rowCount && table.$.rows[ nextRowIndex ] ||\r
-                               previousRowIndex > 0 && table.$.rows[ previousRowIndex ] ||\r
-                               table.$.parentNode );\r
+                       var cursorPosition =  new CKEDITOR.dom.element( rows[ startRowIndex ] || rows[ startRowIndex - 1 ] || table.$.parentNode );\r
 \r
                        for ( i = rowsToDelete.length ; i >= 0 ; i-- )\r
-                       {\r
-                               if ( rowsToDelete[ i ] )\r
-                                       deleteRows( rowsToDelete[ i ] );\r
-                       }\r
+                               deleteRows( rowsToDelete[ i ] );\r
 \r
                        return cursorPosition;\r
                }\r
@@ -209,45 +232,143 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                selectionOrRow.remove();\r
                }\r
 \r
-               return 0;\r
+               return null;\r
        }\r
 \r
-       function insertColumn( selection, insertBefore )\r
+       function getCellColIndex( cell, isStart )\r
        {\r
-               // Get the cell where the selection is placed in.\r
-               var startElement = selection.getStartElement();\r
-               var cell = startElement.getAscendant( 'td', 1 ) || startElement.getAscendant( 'th', 1 );\r
+               var row = cell.getParent(),\r
+                       rowCells = row.$.cells;\r
 \r
-               if ( !cell )\r
-                       return;\r
+               var colIndex = 0;\r
+               for ( var i = 0; i < rowCells.length; i++ )\r
+               {\r
+                       var mapCell = rowCells[ i ];\r
+                       colIndex += isStart ? 1 : mapCell.colSpan;\r
+                       if ( mapCell == cell.$ )\r
+                               break;\r
+               }\r
 \r
-               // Get the cell's table.\r
-               var table = cell.getAscendant( 'table' );\r
-               var cellIndex = cell.$.cellIndex;\r
+               return colIndex -1;\r
+       }\r
 \r
-               // Loop through all rows available in the table.\r
-               for ( var i = 0 ; i < table.$.rows.length ; i++ )\r
+       function getColumnsIndices( cells, isStart )\r
+       {\r
+               var retval = isStart ? Infinity : 0;\r
+               for ( var i = 0; i < cells.length; i++ )\r
                {\r
-                       var $row = table.$.rows[ i ];\r
+                       var colIndex = getCellColIndex( cells[ i ], isStart );\r
+                       if ( isStart ? colIndex < retval  : colIndex > retval )\r
+                               retval = colIndex;\r
+               }\r
+               return retval;\r
+       }\r
 \r
-                       // If the row doesn't have enough cells, ignore it.\r
-                       if ( $row.cells.length < ( cellIndex + 1 ) )\r
-                               continue;\r
+       function insertColumn( selection, insertBefore )\r
+       {\r
+               var cells = getSelectedCells( selection ),\r
+                       firstCell = cells[ 0 ],\r
+                       table = firstCell.getAscendant( 'table' ),\r
+                       startCol =  getColumnsIndices( cells, 1 ),\r
+                       lastCol =  getColumnsIndices( cells ),\r
+                       colIndex = insertBefore? startCol : lastCol;\r
 \r
-                       cell = ( new CKEDITOR.dom.element( $row.cells[ cellIndex ] ) ).clone( 0 );\r
+               var map = CKEDITOR.tools.buildTableMap( table ),\r
+                       cloneCol = [],\r
+                       nextCol = [],\r
+                       height = map.length;\r
 \r
-                       if ( !CKEDITOR.env.ie )\r
-                               cell.appendBogus();\r
+               for ( var i = 0; i < height; i++ )\r
+               {\r
+                       cloneCol.push( map[ i ][ colIndex ] );\r
+                       var nextCell = insertBefore ? map[ i ][ colIndex - 1 ] : map[ i ][ colIndex + 1 ];\r
+                       nextCell && nextCol.push( nextCell );\r
+               }\r
 \r
-                       // Get back the currently selected cell.\r
-                       var baseCell = new CKEDITOR.dom.element( $row.cells[ cellIndex ] );\r
-                       if ( insertBefore )\r
-                               cell.insertBefore( baseCell );\r
+               for ( i = 0; i < height; i++ )\r
+               {\r
+                       var cell;\r
+                       // Check whether there's a spanning column here, do not break it.\r
+                       if ( cloneCol[ i ].colSpan > 1\r
+                               && nextCol.length\r
+                               && nextCol[ i ] == cloneCol[ i ] )\r
+                       {\r
+                               cell = cloneCol[ i ];\r
+                               cell.colSpan += 1;\r
+                       }\r
                        else\r
-                               cell.insertAfter( baseCell );\r
+                       {\r
+                               cell = new CKEDITOR.dom.element( cloneCol[ i ] ).clone();\r
+                               cell.removeAttribute( 'colSpan' );\r
+                               !CKEDITOR.env.ie && cell.appendBogus();\r
+                               cell[ insertBefore? 'insertBefore' : 'insertAfter' ].call( cell, new CKEDITOR.dom.element ( cloneCol[ i ] ) );\r
+                               cell = cell.$;\r
+                       }\r
+\r
+                       i += cell.rowSpan - 1;\r
                }\r
        }\r
 \r
+       function deleteColumns( selectionOrCell )\r
+       {\r
+               var cells = getSelectedCells( selectionOrCell ),\r
+                               firstCell = cells[ 0 ],\r
+                               lastCell = cells[ cells.length - 1 ],\r
+                               table = firstCell.getAscendant( 'table' ),\r
+                               map = CKEDITOR.tools.buildTableMap( table ),\r
+                               startColIndex,\r
+                               endColIndex,\r
+                               rowsToDelete = [];\r
+\r
+               // Figure out selected cells' column indices.\r
+               for ( var i = 0, rows = map.length; i < rows; i++ )\r
+               {\r
+                       for ( var j = 0, cols = map[ i ].length; j < cols; j++ )\r
+                       {\r
+                               if ( map[ i ][ j ] == firstCell.$ )\r
+                                       startColIndex = j;\r
+                               if ( map[ i ][ j ] == lastCell.$ )\r
+                                       endColIndex = j;\r
+                       }\r
+               }\r
+\r
+               // Delete cell or reduce cell spans by checking through the table map.\r
+               for ( i = startColIndex; i <= endColIndex; i++ )\r
+               {\r
+                       for ( j = 0; j < map.length; j++ )\r
+                       {\r
+                               var mapRow = map[ j ],\r
+                                       row = new CKEDITOR.dom.element( table.$.rows[ j ] ),\r
+                                       cell = new CKEDITOR.dom.element( mapRow[ i ] );\r
+\r
+                               if ( cell.$.colSpan == 1 )\r
+                                       cell.remove();\r
+                               // Reduce the col spans.\r
+                               else\r
+                                       cell.$.colSpan -= 1;\r
+\r
+                               j += cell.$.rowSpan - 1;\r
+\r
+                               if ( !row.$.cells.length )\r
+                                       rowsToDelete.push( row );\r
+                       }\r
+               }\r
+\r
+               var firstRowCells = table.$.rows[ 0 ] && table.$.rows[ 0 ].cells;\r
+\r
+               // Where to put the cursor after columns been deleted?\r
+               // 1. Into next cell of the first row if any;\r
+               // 2. Into previous cell of the first row if any;\r
+               // 3. Into table's parent element;\r
+               var cursorPosition =  new CKEDITOR.dom.element( firstRowCells[ startColIndex ] || ( startColIndex ? firstRowCells[ startColIndex - 1 ] : table.$.parentNode ) );\r
+\r
+               // Delete table rows only if all columns are gone (do not remove empty row).\r
+               if ( rowsToDelete.length == rows )\r
+                       table.remove();\r
+\r
+               return cursorPosition;\r
+       }\r
+\r
        function getFocusElementAfterDelCols( cells )\r
        {\r
                var cellIndexList = [],\r
@@ -286,56 +407,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                return targetCell ?  new CKEDITOR.dom.element( targetCell ) :  table.getPrevious();\r
        }\r
 \r
-       function deleteColumns( selectionOrCell )\r
-       {\r
-               if ( selectionOrCell instanceof CKEDITOR.dom.selection )\r
-               {\r
-                       var colsToDelete = getSelectedCells( selectionOrCell ),\r
-                               elementToFocus = getFocusElementAfterDelCols( colsToDelete );\r
-\r
-                       for ( var i = colsToDelete.length - 1 ; i >= 0 ; i-- )\r
-                       {\r
-                               if ( colsToDelete[ i ] )\r
-                                       deleteColumns( colsToDelete[ i ] );\r
-                       }\r
-\r
-                       return elementToFocus;\r
-               }\r
-               else if ( selectionOrCell instanceof CKEDITOR.dom.element )\r
-               {\r
-                       // Get the cell's table.\r
-                       var table = selectionOrCell.getAscendant( 'table' );\r
-                       if ( !table )\r
-                               return null;\r
-\r
-                       // Get the cell index.\r
-                       var cellIndex = selectionOrCell.$.cellIndex;\r
-\r
-                       /*\r
-                        * Loop through all rows from down to up, coz it's possible that some rows\r
-                        * will be deleted.\r
-                        */\r
-                       for ( i = table.$.rows.length - 1 ; i >= 0 ; i-- )\r
-                       {\r
-                               // Get the row.\r
-                               var row = new CKEDITOR.dom.element( table.$.rows[ i ] );\r
-\r
-                               // If the cell to be removed is the first one and the row has just one cell.\r
-                               if ( !cellIndex && row.$.cells.length == 1 )\r
-                               {\r
-                                       deleteRows( row );\r
-                                       continue;\r
-                               }\r
-\r
-                               // Else, just delete the cell.\r
-                               if ( row.$.cells[ cellIndex ] )\r
-                                       row.$.removeChild( row.$.cells[ cellIndex ] );\r
-                       }\r
-               }\r
-\r
-               return null;\r
-       }\r
-\r
        function insertCell( selection, insertBefore )\r
        {\r
                var startElement = selection.getStartElement();\r
@@ -464,13 +535,16 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        var targetCell;\r
                        try\r
                        {\r
+                               var rowspan = parseInt( firstCell.getAttribute( 'rowspan' ), 10 ) || 1;\r
+                               var colspan = parseInt( firstCell.getAttribute( 'colspan' ), 10 ) || 1;\r
+\r
                                targetCell =\r
                                        map[ mergeDirection == 'up' ?\r
-                                                       ( startRow - 1 ):\r
-                                                       mergeDirection == 'down' ? ( startRow + 1 ) : startRow  ] [\r
-                                                mergeDirection == 'left' ?\r
-                                                       ( startColumn - 1 ):\r
-                                                mergeDirection == 'right' ?  ( startColumn + 1 ) : startColumn ];\r
+                                                       ( startRow - rowspan ):\r
+                                                       mergeDirection == 'down' ? ( startRow + rowspan ) : startRow  ] [\r
+                                               mergeDirection == 'left' ?\r
+                                                       ( startColumn - colspan ):\r
+                                               mergeDirection == 'right' ?  ( startColumn + colspan ) : startColumn ];\r
 \r
                        }\r
                        catch( er )\r
@@ -527,7 +601,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                        {\r
                                                var last = frag.getLast( CKEDITOR.dom.walker.whitespaces( true ) );\r
                                                if ( last && !( last.is && last.is( 'br' ) ) )\r
-                                                       frag.append( new CKEDITOR.dom.element( 'br' ) );\r
+                                                       frag.append( 'br' );\r
                                        }\r
 \r
                                        cell.moveChildren( frag );\r