[795] | 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 | |
---|
| 24 | var FCKTableHandler = new Object() ; |
---|
| 25 | |
---|
| 26 | FCKTableHandler.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 | |
---|
| 42 | FCKTableHandler.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 | |
---|
| 64 | FCKTableHandler.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 | |
---|
| 82 | FCKTableHandler.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 | |
---|
| 121 | FCKTableHandler.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 | |
---|
| 155 | FCKTableHandler.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 | |
---|
| 182 | FCKTableHandler.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 | |
---|
| 196 | FCKTableHandler.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 | |
---|
| 206 | FCKTableHandler.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 | |
---|
| 259 | FCKTableHandler.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. |
---|
| 290 | FCKTableHandler._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. |
---|
| 307 | FCKTableHandler._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 |
---|
| 327 | FCKTableHandler._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 | |
---|
| 372 | FCKTableHandler.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 | } |
---|