1/* 2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net 3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben 4 * 5 * == BEGIN LICENSE == 6 * 7 * Licensed under the terms of any of the following licenses at your 8 * choice: 9 * 10 * - GNU General Public License Version 2 or later (the "GPL") 11 * http://www.gnu.org/licenses/gpl.html 12 * 13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL") 14 * http://www.gnu.org/licenses/lgpl.html 15 * 16 * - Mozilla Public License Version 1.1 or later (the "MPL") 17 * http://www.mozilla.org/MPL/MPL-1.1.html 18 * 19 * == END LICENSE == 20 * 21 * Manage table operations. 22 */ 23 24var FCKTableHandler = new Object() ; 25 26FCKTableHandler.InsertRow = function() 27{ 28 // Get the row where the selection is placed in. 29 var oRow = FCKSelection.MoveToAncestorNode( 'TR' ) ; 30 if ( !oRow ) return ; 31 32 // Create a clone of the row. 33 var oNewRow = oRow.cloneNode( true ) ; 34 35 // Insert the new row (copy) before of it. 36 oRow.parentNode.insertBefore( oNewRow, oRow ) ; 37 38 // Clean the row (it seems that the new row has been added after it). 39 FCKTableHandler.ClearRow( oRow ) ; 40} 41 42FCKTableHandler.DeleteRows = function( row ) 43{ 44 // If no row has been passed as a parameter, 45 // then get the row where the selection is placed in. 46 if ( !row ) 47 row = FCKSelection.MoveToAncestorNode( 'TR' ) ; 48 if ( !row ) return ; 49 50 // Get the row's table. 51 var oTable = FCKTools.GetElementAscensor( row, 'TABLE' ) ; 52 53 // If just one row is available then delete the entire table. 54 if ( oTable.rows.length == 1 ) 55 { 56 FCKTableHandler.DeleteTable( oTable ) ; 57 return ; 58 } 59 60 // Delete the row. 61 row.parentNode.removeChild( row ) ; 62} 63 64FCKTableHandler.DeleteTable = function( table ) 65{ 66 // If no table has been passed as a parameer, 67 // then get the table where the selection is placed in. 68 if ( !table ) 69 { 70 table = FCKSelection.GetSelectedElement() ; 71 if ( !table || table.tagName != 'TABLE' ) 72 table = FCKSelection.MoveToAncestorNode( 'TABLE' ) ; 73 } 74 if ( !table ) return ; 75 76 // Delete the table. 77 FCKSelection.SelectNode( table ) ; 78 FCKSelection.Collapse(); 79 table.parentNode.removeChild( table ) ; 80} 81 82FCKTableHandler.InsertColumn = function() 83{ 84 // Get the cell where the selection is placed in. 85 var oCell = FCKSelection.MoveToAncestorNode('TD') || FCKSelection.MoveToAncestorNode('TH') ; 86 87 if ( !oCell ) return ; 88 89 // Get the cell's table. 90 var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ; 91 92 // Get the index of the column to be created (based on the cell). 93 var iIndex = oCell.cellIndex + 1 ; 94 95 // Loop throw all rows available in the table. 96 for ( var i = 0 ; i < oTable.rows.length ; i++ ) 97 { 98 // Get the row. 99 var oRow = oTable.rows[i] ; 100 101 // If the row doens't have enought cells, ignore it. 102 if ( oRow.cells.length < iIndex ) 103 continue ; 104 105 oCell = oRow.cells[iIndex-1].cloneNode(false) ; 106 107 if ( FCKBrowserInfo.IsGecko ) 108 oCell.innerHTML = GECKO_BOGUS ; 109 110 // Get the cell that is placed in the new cell place. 111 var oBaseCell = oRow.cells[iIndex] ; 112 113 // If the cell is available (we are not in the last cell of the row). 114 if ( oBaseCell ) 115 oRow.insertBefore( oCell, oBaseCell ) ; // Insert the new cell just before of it. 116 else 117 oRow.appendChild( oCell ) ; // Append the cell at the end of the row. 118 } 119} 120 121FCKTableHandler.DeleteColumns = function() 122{ 123 // Get the cell where the selection is placed in. 124 var oCell = FCKSelection.MoveToAncestorNode('TD') || FCKSelection.MoveToAncestorNode('TH') ; 125 126 if ( !oCell ) return ; 127 128 // Get the cell's table. 129 var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ; 130 131 // Get the cell index. 132 var iIndex = oCell.cellIndex ; 133 134 // Loop throw all rows (from down to up, because it's possible that some 135 // rows will be deleted). 136 for ( var i = oTable.rows.length - 1 ; i >= 0 ; i-- ) 137 { 138 // Get the row. 139 var oRow = oTable.rows[i] ; 140 141 // If the cell to be removed is the first one and the row has just one cell. 142 if ( iIndex == 0 && oRow.cells.length == 1 ) 143 { 144 // Remove the entire row. 145 FCKTableHandler.DeleteRows( oRow ) ; 146 continue ; 147 } 148 149 // If the cell to be removed exists the delete it. 150 if ( oRow.cells[iIndex] ) 151 oRow.removeChild( oRow.cells[iIndex] ) ; 152 } 153} 154 155FCKTableHandler.InsertCell = function( cell ) 156{ 157 // Get the cell where the selection is placed in. 158 var oCell = cell ? cell : FCKSelection.MoveToAncestorNode( 'TD' ) ; 159 if ( !oCell ) return null ; 160 161 // Create the new cell element to be added. 162 var oNewCell = FCK.EditorDocument.createElement( 'TD' ) ; 163 if ( FCKBrowserInfo.IsGecko ) 164 oNewCell.innerHTML = GECKO_BOGUS ; 165// oNewCell.innerHTML = " " ; 166 167 // If it is the last cell in the row. 168 if ( oCell.cellIndex == oCell.parentNode.cells.length - 1 ) 169 { 170 // Add the new cell at the end of the row. 171 oCell.parentNode.appendChild( oNewCell ) ; 172 } 173 else 174 { 175 // Add the new cell before the next cell (after the active one). 176 oCell.parentNode.insertBefore( oNewCell, oCell.nextSibling ) ; 177 } 178 179 return oNewCell ; 180} 181 182FCKTableHandler.DeleteCell = function( cell ) 183{ 184 // If this is the last cell in the row. 185 if ( cell.parentNode.cells.length == 1 ) 186 { 187 // Delete the entire row. 188 FCKTableHandler.DeleteRows( FCKTools.GetElementAscensor( cell, 'TR' ) ) ; 189 return ; 190 } 191 192 // Delete the cell from the row. 193 cell.parentNode.removeChild( cell ) ; 194} 195 196FCKTableHandler.DeleteCells = function() 197{ 198 var aCells = FCKTableHandler.GetSelectedCells() ; 199 200 for ( var i = aCells.length - 1 ; i >= 0 ; i-- ) 201 { 202 FCKTableHandler.DeleteCell( aCells[i] ) ; 203 } 204} 205 206FCKTableHandler.MergeCells = function() 207{ 208 // Get all selected cells. 209 var aCells = FCKTableHandler.GetSelectedCells() ; 210 211 // At least 2 cells must be selected. 212 if ( aCells.length < 2 ) 213 return ; 214 215 // The merge can occour only if the selected cells are from the same row. 216 if ( aCells[0].parentNode != aCells[aCells.length-1].parentNode ) 217 return ; 218 219 // Calculate the new colSpan for the first cell. 220 var iColSpan = isNaN( aCells[0].colSpan ) ? 1 : aCells[0].colSpan ; 221 222 var sHtml = '' ; 223 var oCellsContents = FCK.EditorDocument.createDocumentFragment() ; 224 225 for ( var i = aCells.length - 1 ; i >= 0 ; i-- ) 226 { 227 var eCell = aCells[i] ; 228 229 // Move its contents to the document fragment. 230 for ( var c = eCell.childNodes.length - 1 ; c >= 0 ; c-- ) 231 { 232 var eChild = eCell.removeChild( eCell.childNodes[c] ) ; 233 234 if ( ( eChild.hasAttribute && eChild.hasAttribute('_moz_editor_bogus_node') ) || ( eChild.getAttribute && eChild.getAttribute( 'type', 2 ) == '_moz' ) ) 235 continue ; 236 237 oCellsContents.insertBefore( eChild, oCellsContents.firstChild ) ; 238 } 239 240 if ( i > 0 ) 241 { 242 // Accumulate the colspan of the cell. 243 iColSpan += isNaN( eCell.colSpan ) ? 1 : eCell.colSpan ; 244 245 // Delete the cell. 246 FCKTableHandler.DeleteCell( eCell ) ; 247 } 248 } 249 250 // Set the innerHTML of the remaining cell (the first one). 251 aCells[0].colSpan = iColSpan ; 252 253 if ( FCKBrowserInfo.IsGecko && oCellsContents.childNodes.length == 0 ) 254 aCells[0].innerHTML = GECKO_BOGUS ; 255 else 256 aCells[0].appendChild( oCellsContents ) ; 257} 258 259FCKTableHandler.SplitCell = function() 260{ 261 // Check that just one cell is selected, otherwise return. 262 var aCells = FCKTableHandler.GetSelectedCells() ; 263 if ( aCells.length != 1 ) 264 return ; 265 266 var aMap = this._CreateTableMap( aCells[0].parentNode.parentNode ) ; 267 var iCellIndex = FCKTableHandler._GetCellIndexSpan( aMap, aCells[0].parentNode.rowIndex , aCells[0] ) ; 268 269 var aCollCells = this._GetCollumnCells( aMap, iCellIndex ) ; 270 271 for ( var i = 0 ; i < aCollCells.length ; i++ ) 272 { 273 if ( aCollCells[i] == aCells[0] ) 274 { 275 var oNewCell = this.InsertCell( aCells[0] ) ; 276 if ( !isNaN( aCells[0].rowSpan ) && aCells[0].rowSpan > 1 ) 277 oNewCell.rowSpan = aCells[0].rowSpan ; 278 } 279 else 280 { 281 if ( isNaN( aCollCells[i].colSpan ) ) 282 aCollCells[i].colSpan = 2 ; 283 else 284 aCollCells[i].colSpan += 1 ; 285 } 286 } 287} 288 289// Get the cell index from a TableMap. 290FCKTableHandler._GetCellIndexSpan = function( tableMap, rowIndex, cell ) 291{ 292 if ( tableMap.length < rowIndex + 1 ) 293 return null ; 294 295 var oRow = tableMap[ rowIndex ] ; 296 297 for ( var c = 0 ; c < oRow.length ; c++ ) 298 { 299 if ( oRow[c] == cell ) 300 return c ; 301 } 302 303 return null ; 304} 305 306// Get the cells available in a collumn of a TableMap. 307FCKTableHandler._GetCollumnCells = function( tableMap, collumnIndex ) 308{ 309 var aCollCells = new Array() ; 310 311 for ( var r = 0 ; r < tableMap.length ; r++ ) 312 { 313 var oCell = tableMap[r][collumnIndex] ; 314 if ( oCell && ( aCollCells.length == 0 || aCollCells[ aCollCells.length - 1 ] != oCell ) ) 315 aCollCells[ aCollCells.length ] = oCell ; 316 } 317 318 return aCollCells ; 319} 320 321// This function is quite hard to explain. It creates a matrix representing all cells in a table. 322// The difference here is that the "spanned" cells (colSpan and rowSpan) are duplicated on the matrix 323// cells that are "spanned". For example, a row with 3 cells where the second cell has colSpan=2 and rowSpan=3 324// will produce a bi-dimensional matrix with the following values (representing the cells): 325// Cell1, Cell2, Cell2, Cell 3 326// Cell4, Cell2, Cell2, Cell 5 327FCKTableHandler._CreateTableMap = function( table ) 328{ 329 var aRows = table.rows ; 330 331 // Row and Collumn counters. 332 var r = -1 ; 333 334 var aMap = new Array() ; 335 336 for ( var i = 0 ; i < aRows.length ; i++ ) 337 { 338 r++ ; 339 if ( !aMap[r] ) 340 aMap[r] = new Array() ; 341 342 var c = -1 ; 343 344 for ( var j = 0 ; j < aRows[i].cells.length ; j++ ) 345 { 346 var oCell = aRows[i].cells[j] ; 347 348 c++ ; 349 while ( aMap[r][c] ) 350 c++ ; 351 352 var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ; 353 var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ; 354 355 for ( var rs = 0 ; rs < iRowSpan ; rs++ ) 356 { 357 if ( !aMap[r + rs] ) 358 aMap[r + rs] = new Array() ; 359 360 for ( var cs = 0 ; cs < iColSpan ; cs++ ) 361 { 362 aMap[r + rs][c + cs] = aRows[i].cells[j] ; 363 } 364 } 365 366 c += iColSpan - 1 ; 367 } 368 } 369 return aMap ; 370} 371 372FCKTableHandler.ClearRow = function( tr ) 373{ 374 // Get the array of row's cells. 375 var aCells = tr.cells ; 376 377 // Replace the contents of each cell with "nothing". 378 for ( var i = 0 ; i < aCells.length ; i++ ) 379 { 380 if ( FCKBrowserInfo.IsGecko ) 381 aCells[i].innerHTML = GECKO_BOGUS ; 382 else 383 aCells[i].innerHTML = '' ; 384 } 385} 386