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( insertBefore ) 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 one of the rows to produce the illusion of inserting an empty row before or after. 39 FCKTableHandler.ClearRow( insertBefore ? oNewRow : oRow ) ; 40} 41 42FCKTableHandler.DeleteRows = function( row ) 43{ 44 // If no row has been passed as a parameter, 45 // then get the row( s ) containing the cells where the selection is placed in. 46 // If user selected multiple rows ( by selecting multiple cells ), walk 47 // the selected cell list and delete the rows containing the selected cells 48 if ( ! row ) 49 { 50 var aCells = FCKTableHandler.GetSelectedCells() ; 51 var aRowsToDelete = new Array() ; 52 //queue up the rows -- it's possible ( and likely ) that we may get duplicates 53 for ( var i = 0; i < aCells.length; i++ ) 54 { 55 var oRow = FCKTools.GetElementAscensor( aCells[i],'TR' ) ; 56 aRowsToDelete[oRow.rowIndex] = oRow ; 57 } 58 for ( var i = aRowsToDelete.length; i >= 0; i-- ) 59 { 60 if ( aRowsToDelete[i] ) 61 FCKTableHandler.DeleteRows( aRowsToDelete[i] ); 62 } 63 return ; 64 } 65 66 // Get the row's table. 67 var oTable = FCKTools.GetElementAscensor( row, 'TABLE' ) ; 68 69 // If just one row is available then delete the entire table. 70 if ( oTable.rows.length == 1 ) 71 { 72 FCKTableHandler.DeleteTable( oTable ) ; 73 return ; 74 } 75 76 // Delete the row. 77 row.parentNode.removeChild( row ) ; 78} 79 80FCKTableHandler.DeleteTable = function( table ) 81{ 82 // If no table has been passed as a parameter, 83 // then get the table where the selection is placed in. 84 if ( !table ) 85 { 86 table = FCKSelection.GetSelectedElement() ; 87 if ( !table || table.tagName != 'TABLE' ) 88 table = FCKSelection.MoveToAncestorNode( 'TABLE' ) ; 89 } 90 if ( !table ) return ; 91 92 // Delete the table. 93 FCKSelection.SelectNode( table ) ; 94 FCKSelection.Collapse(); 95 96 // if the table is wrapped with a singleton <p> ( or something similar ), remove 97 // the surrounding tag -- which likely won't show after deletion anyway 98 if ( table.parentNode.childNodes.length == 1 ) 99 table.parentNode.parentNode.removeChild( table.parentNode ); 100 else 101 table.parentNode.removeChild( table ) ; 102} 103 104FCKTableHandler.InsertColumn = function( insertBefore ) 105{ 106 // Get the cell where the selection is placed in. 107 var oCell = null ; 108 var nodes = this.GetSelectedCells() ; 109 110 if ( nodes && nodes.length ) 111 oCell = nodes[ insertBefore ? 0 : ( nodes.length - 1 ) ] ; 112 113 if ( ! oCell ) 114 return ; 115 116 // Get the cell's table. 117 var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ; 118 119 var iIndex = oCell.cellIndex ; 120 121 // Loop throw all rows available in the table. 122 for ( var i = 0 ; i < oTable.rows.length ; i++ ) 123 { 124 // Get the row. 125 var oRow = oTable.rows[i] ; 126 127 // If the row doens't have enough cells, ignore it. 128 if ( oRow.cells.length < ( iIndex + 1 ) ) 129 continue ; 130 131 oCell = oRow.cells[iIndex].cloneNode(false) ; 132 133 if ( FCKBrowserInfo.IsGeckoLike ) 134 FCKTools.AppendBogusBr( oCell ) ; 135 136 // Get back the currently selected cell. 137 var oBaseCell = oRow.cells[iIndex] ; 138 139 if ( insertBefore ) 140 oRow.insertBefore( oCell, oBaseCell ) ; 141 else if ( oBaseCell.nextSibling ) 142 oRow.insertBefore( oCell, oBaseCell.nextSibling ) ; 143 else 144 oRow.appendChild( oCell ) ; 145 } 146} 147 148FCKTableHandler.DeleteColumns = function( oCell ) 149{ 150 // if user selected multiple cols ( by selecting multiple cells ), walk 151 // the selected cell list and delete the rows containing the selected cells 152 if ( !oCell ) 153 { 154 var aColsToDelete = FCKTableHandler.GetSelectedCells(); 155 for ( var i = aColsToDelete.length; i >= 0; i-- ) 156 { 157 if ( aColsToDelete[i] ) 158 FCKTableHandler.DeleteColumns( aColsToDelete[i] ); 159 } 160 return; 161 } 162 163 if ( !oCell ) return ; 164 165 // Get the cell's table. 166 var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ; 167 168 // Get the cell index. 169 var iIndex = oCell.cellIndex ; 170 171 // Loop throw all rows (from down to up, because it's possible that some 172 // rows will be deleted). 173 for ( var i = oTable.rows.length - 1 ; i >= 0 ; i-- ) 174 { 175 // Get the row. 176 var oRow = oTable.rows[i] ; 177 178 // If the cell to be removed is the first one and the row has just one cell. 179 if ( iIndex == 0 && oRow.cells.length == 1 ) 180 { 181 // Remove the entire row. 182 FCKTableHandler.DeleteRows( oRow ) ; 183 continue ; 184 } 185 186 // If the cell to be removed exists the delete it. 187 if ( oRow.cells[iIndex] ) 188 oRow.removeChild( oRow.cells[iIndex] ) ; 189 } 190} 191 192FCKTableHandler.InsertCell = function( cell, insertBefore ) 193{ 194 // Get the cell where the selection is placed in. 195 var oCell = null ; 196 var nodes = this.GetSelectedCells() ; 197 if ( nodes && nodes.length ) 198 oCell = nodes[ insertBefore ? 0 : ( nodes.length - 1 ) ] ; 199 if ( ! oCell ) 200 return null ; 201 202 // Create the new cell element to be added. 203 var oNewCell = FCK.EditorDocument.createElement( 'TD' ) ; 204 if ( FCKBrowserInfo.IsGeckoLike ) 205 FCKTools.AppendBogusBr( oNewCell ) ; 206 207 if ( !insertBefore && oCell.cellIndex == oCell.parentNode.cells.length - 1 ) 208 oCell.parentNode.appendChild( oNewCell ) ; 209 else 210 oCell.parentNode.insertBefore( oNewCell, insertBefore ? oCell : oCell.nextSibling ) ; 211 212 return oNewCell ; 213} 214 215FCKTableHandler.DeleteCell = function( cell ) 216{ 217 // If this is the last cell in the row. 218 if ( cell.parentNode.cells.length == 1 ) 219 { 220 // Delete the entire row. 221 FCKTableHandler.DeleteRows( FCKTools.GetElementAscensor( cell, 'TR' ) ) ; 222 return ; 223 } 224 225 // Delete the cell from the row. 226 cell.parentNode.removeChild( cell ) ; 227} 228 229FCKTableHandler.DeleteCells = function() 230{ 231 var aCells = FCKTableHandler.GetSelectedCells() ; 232 233 for ( var i = aCells.length - 1 ; i >= 0 ; i-- ) 234 { 235 FCKTableHandler.DeleteCell( aCells[i] ) ; 236 } 237} 238 239FCKTableHandler._MarkCells = function( cells, label ) 240{ 241 for ( var i = 0 ; i < cells.length ; i++ ) 242 cells[i][label] = true ; 243} 244 245FCKTableHandler._UnmarkCells = function( cells, label ) 246{ 247 for ( var i = 0 ; i < cells.length ; i++ ) 248 { 249 if ( FCKBrowserInfo.IsIE ) 250 cells[i].removeAttribute( label ) ; 251 else 252 delete cells[i][label] ; 253 } 254} 255 256FCKTableHandler._ReplaceCellsByMarker = function( tableMap, marker, substitute ) 257{ 258 for ( var i = 0 ; i < tableMap.length ; i++ ) 259 { 260 for ( var j = 0 ; j < tableMap[i].length ; j++ ) 261 { 262 if ( tableMap[i][j][marker] ) 263 tableMap[i][j] = substitute ; 264 } 265 } 266} 267 268FCKTableHandler._GetMarkerGeometry = function( tableMap, rowIdx, colIdx, markerName ) 269{ 270 var selectionWidth = 0 ; 271 var selectionHeight = 0 ; 272 var cellsLeft = 0 ; 273 var cellsUp = 0 ; 274 for ( var i = colIdx ; tableMap[rowIdx][i] && tableMap[rowIdx][i][markerName] ; i++ ) 275 selectionWidth++ ; 276 for ( var i = colIdx - 1 ; tableMap[rowIdx][i] && tableMap[rowIdx][i][markerName] ; i-- ) 277 { 278 selectionWidth++ ; 279 cellsLeft++ ; 280 } 281 for ( var i = rowIdx ; tableMap[i] && tableMap[i][colIdx] && tableMap[i][colIdx][markerName] ; i++ ) 282 selectionHeight++ ; 283 for ( var i = rowIdx - 1 ; tableMap[i] && tableMap[i][colIdx] && tableMap[i][colIdx][markerName] ; i-- ) 284 { 285 selectionHeight++ ; 286 cellsUp++ ; 287 } 288 return { 'width' : selectionWidth, 'height' : selectionHeight, 'x' : cellsLeft, 'y' : cellsUp } ; 289} 290 291FCKTableHandler.CheckIsSelectionRectangular = function() 292{ 293 // If every row and column in an area on a plane are of the same width and height, 294 // Then the area is a rectangle. 295 var cells = FCKTableHandler.GetSelectedCells() ; 296 if ( cells.length < 1 ) 297 return false ; 298 299 this._MarkCells( cells, '_CellSelected' ) ; 300 301 var tableMap = this._CreateTableMap( cells[0].parentNode.parentNode ) ; 302 var rowIdx = cells[0].parentNode.rowIndex ; 303 var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, cells[0] ) ; 304 305 var geometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_CellSelected' ) ; 306 var baseColIdx = colIdx - geometry.x ; 307 var baseRowIdx = rowIdx - geometry.y ; 308 309 if ( geometry.width >= geometry.height ) 310 { 311 for ( colIdx = baseColIdx ; colIdx < baseColIdx + geometry.width ; colIdx++ ) 312 { 313 rowIdx = baseRowIdx + ( colIdx - baseColIdx ) % geometry.height ; 314 if ( ! tableMap[rowIdx] || ! tableMap[rowIdx][colIdx] ) 315 { 316 this._UnmarkCells( cells, '_CellSelected' ) ; 317 return false ; 318 } 319 var g = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_CellSelected' ) ; 320 if ( g.width != geometry.width || g.height != geometry.height ) 321 { 322 this._UnmarkCells( cells, '_CellSelected' ) ; 323 return false ; 324 } 325 } 326 } 327 else 328 { 329 for ( rowIdx = baseRowIdx ; rowIdx < baseRowIdx + geometry.height ; rowIdx++ ) 330 { 331 colIdx = baseColIdx + ( rowIdx - baseRowIdx ) % geometry.width ; 332 if ( ! tableMap[rowIdx] || ! tableMap[rowIdx][colIdx] ) 333 { 334 this._UnmarkCells( cells, '_CellSelected' ) ; 335 return false ; 336 } 337 var g = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_CellSelected' ) ; 338 if ( g.width != geometry.width || g.height != geometry.height ) 339 { 340 this._UnmarkCells( cells, '_CellSelected' ) ; 341 return false ; 342 } 343 } 344 } 345 346 this._UnmarkCells( cells, '_CellSelected' ) ; 347 return true ; 348} 349 350FCKTableHandler.MergeCells = function() 351{ 352 // Get all selected cells. 353 var cells = this.GetSelectedCells() ; 354 if ( cells.length < 2 ) 355 return ; 356 357 // Assume the selected cells are already in a rectangular geometry. 358 // Because the checking is already done by FCKTableCommand. 359 var refCell = cells[0] ; 360 var tableMap = this._CreateTableMap( refCell.parentNode.parentNode ) ; 361 var rowIdx = refCell.parentNode.rowIndex ; 362 var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, refCell ) ; 363 364 this._MarkCells( cells, '_SelectedCells' ) ; 365 var selectionGeometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_SelectedCells' ) ; 366 367 var baseColIdx = colIdx - selectionGeometry.x ; 368 var baseRowIdx = rowIdx - selectionGeometry.y ; 369 var cellContents = refCell.ownerDocument.createDocumentFragment() ; 370 for ( var i = 0 ; i < selectionGeometry.height ; i++ ) 371 { 372 var rowChildNodesCount = 0 ; 373 for ( var j = 0 ; j < selectionGeometry.width ; j++ ) 374 { 375 var currentCell = tableMap[baseRowIdx + i][baseColIdx + j] ; 376 while ( currentCell.childNodes.length > 0 ) 377 { 378 var node = currentCell.removeChild( currentCell.firstChild ) ; 379 if ( node.nodeType != 1 380 || ( node.getAttribute( 'type', 2 ) != '_moz' && node.getAttribute( '_moz_dirty' ) != null ) ) 381 { 382 cellContents.appendChild( node ) ; 383 rowChildNodesCount++ ; 384 } 385 } 386 } 387 if ( rowChildNodesCount > 0 ) 388 cellContents.appendChild( refCell.ownerDocument.createElement( 'br' ) ) ; 389 } 390 391 this._ReplaceCellsByMarker( tableMap, '_SelectedCells', refCell ) ; 392 this._UnmarkCells( cells, '_SelectedCells' ) ; 393 this._InstallTableMap( tableMap, refCell.parentNode.parentNode ) ; 394 refCell.appendChild( cellContents ) ; 395 396 if ( FCKBrowserInfo.IsGeckoLike && ( ! refCell.firstChild ) ) 397 FCKTools.AppendBogusBr( refCell ) ; 398 399 this._MoveCaretToCell( refCell, false ) ; 400} 401 402FCKTableHandler.MergeRight = function() 403{ 404 var target = this.GetMergeRightTarget() ; 405 if ( target == null ) 406 return ; 407 var refCell = target.refCell ; 408 var tableMap = target.tableMap ; 409 var nextCell = target.nextCell ; 410 411 var cellContents = FCK.EditorDocument.createDocumentFragment() ; 412 while ( nextCell && nextCell.childNodes && nextCell.childNodes.length > 0 ) 413 cellContents.appendChild( nextCell.removeChild( nextCell.firstChild ) ) ; 414 415 nextCell.parentNode.removeChild( nextCell ) ; 416 refCell.appendChild( cellContents ) ; 417 this._MarkCells( [nextCell], '_Replace' ) ; 418 this._ReplaceCellsByMarker( tableMap, '_Replace', refCell ) ; 419 this._InstallTableMap( tableMap, refCell.parentNode.parentNode ) ; 420 421 this._MoveCaretToCell( refCell, false ) ; 422} 423 424FCKTableHandler.MergeDown = function() 425{ 426 var target = this.GetMergeDownTarget() ; 427 if ( target == null ) 428 return ; 429 var refCell = target.refCell ; 430 var tableMap = target.tableMap ; 431 var nextCell = target.nextCell ; 432 433 var cellContents = refCell.ownerDocument.createDocumentFragment() ; 434 while ( nextCell && nextCell.childNodes && nextCell.childNodes.length > 0 ) 435 cellContents.appendChild( nextCell.removeChild( nextCell.firstChild ) ) ; 436 if ( cellContents.firstChild ) 437 cellContents.insertBefore( nextCell.ownerDocument.createElement( 'br' ), cellContents.firstChild ) ; 438 refCell.appendChild( cellContents ) ; 439 this._MarkCells( [nextCell], '_Replace' ) ; 440 this._ReplaceCellsByMarker( tableMap, '_Replace', refCell ) ; 441 this._InstallTableMap( tableMap, refCell.parentNode.parentNode ) ; 442 443 this._MoveCaretToCell( refCell, false ) ; 444} 445 446FCKTableHandler.HorizontalSplitCell = function() 447{ 448 var cells = FCKTableHandler.GetSelectedCells() ; 449 if ( cells.length != 1 ) 450 return ; 451 452 var refCell = cells[0] ; 453 var tableMap = this._CreateTableMap( refCell.parentNode.parentNode ) ; 454 var rowIdx = refCell.parentNode.rowIndex ; 455 var colIdx = FCKTableHandler._GetCellIndexSpan( tableMap, rowIdx, refCell ) ; 456 var cellSpan = isNaN( refCell.colSpan ) ? 1 : refCell.colSpan ; 457 458 if ( cellSpan > 1 ) 459 { 460 // Splittng a multi-column cell - original cell gets ceil(colSpan/2) columns, 461 // new cell gets floor(colSpan/2). 462 var newCellSpan = Math.ceil( cellSpan / 2 ) ; 463 var newCell = refCell.ownerDocument.createElement( 'td' ) ; 464 if ( FCKBrowserInfo.IsGeckoLike ) 465 FCKTools.AppendBogusBr( newCell ) ; 466 var startIdx = colIdx + newCellSpan ; 467 var endIdx = colIdx + cellSpan ; 468 var rowSpan = isNaN( refCell.rowSpan ) ? 1 : refCell.rowSpan ; 469 for ( var r = rowIdx ; r < rowIdx + rowSpan ; r++ ) 470 { 471 for ( var i = startIdx ; i < endIdx ; i++ ) 472 tableMap[r][i] = newCell ; 473 } 474 } 475 else 476 { 477 // Splitting a single-column cell - add a new cell, and expand 478 // cells crossing the same column. 479 var newTableMap = [] ; 480 for ( var i = 0 ; i < tableMap.length ; i++ ) 481 { 482 var newRow = tableMap[i].slice( 0, colIdx ) ; 483 if ( tableMap[i].length <= colIdx ) 484 { 485 newTableMap.push( newRow ) ; 486 continue ; 487 } 488 if ( tableMap[i][colIdx] == refCell ) 489 { 490 newRow.push( refCell ) ; 491 newRow.push( refCell.ownerDocument.createElement( 'td' ) ) ; 492 if ( FCKBrowserInfo.IsGeckoLike ) 493 FCKTools.AppendBogusBr( newRow[newRow.length - 1] ) ; 494 } 495 else 496 { 497 newRow.push( tableMap[i][colIdx] ) ; 498 newRow.push( tableMap[i][colIdx] ) ; 499 } 500 for ( var j = colIdx + 1 ; j < tableMap[i].length ; j++ ) 501 newRow.push( tableMap[i][j] ) ; 502 newTableMap.push( newRow ) ; 503 } 504 tableMap = newTableMap ; 505 } 506 507 this._InstallTableMap( tableMap, refCell.parentNode.parentNode ) ; 508} 509 510FCKTableHandler.VerticalSplitCell = function() 511{ 512 var cells = FCKTableHandler.GetSelectedCells() ; 513 if ( cells.length != 1 ) 514 return ; 515 516 var currentCell = cells[0] ; 517 var tableMap = this._CreateTableMap( currentCell.parentNode.parentNode ) ; 518 var cellIndex = FCKTableHandler._GetCellIndexSpan( tableMap, currentCell.parentNode.rowIndex, currentCell ) ; 519 var currentRowSpan = currentCell.rowSpan ; 520 var currentRowIndex = currentCell.parentNode.rowIndex ; 521 if ( isNaN( currentRowSpan ) ) 522 currentRowSpan = 1 ; 523 524 if ( currentRowSpan > 1 ) 525 { 526 // 1. Set the current cell's rowSpan to 1. 527 currentCell.rowSpan = Math.ceil( currentRowSpan / 2 ) ; 528 529 // 2. Find the appropriate place to insert a new cell at the next row. 530 var newCellRowIndex = currentRowIndex + Math.ceil( currentRowSpan / 2 ) ; 531 var insertMarker = null ; 532 for ( var i = cellIndex+1 ; i < tableMap[newCellRowIndex].length ; i++ ) 533 { 534 if ( tableMap[newCellRowIndex][i].parentNode.rowIndex == newCellRowIndex ) 535 { 536 insertMarker = tableMap[newCellRowIndex][i] ; 537 break ; 538 } 539 } 540 541 // 3. Insert the new cell to the indicated place, with the appropriate rowSpan, next row. 542 var newCell = FCK.EditorDocument.createElement( 'td' ) ; 543 newCell.rowSpan = Math.floor( currentRowSpan / 2 ) ; 544 if ( FCKBrowserInfo.IsGeckoLike ) 545 FCKTools.AppendBogusBr( newCell ) ; 546 currentCell.parentNode.parentNode.rows[newCellRowIndex].insertBefore( newCell, insertMarker ) ; 547 } 548 else 549 { 550 // 1. Insert a new row. 551 var newCellRowIndex = currentRowIndex + 1 ; 552 var newRow = FCK.EditorDocument.createElement( 'tr' ) ; 553 currentCell.parentNode.parentNode.insertBefore( newRow, currentCell.parentNode.parentNode.rows[newCellRowIndex] ) ; 554 555 // 2. +1 to rowSpan for all cells crossing currentCell's row. 556 for ( var i = 0 ; i < tableMap[currentRowIndex].length ; ) 557 { 558 var colSpan = tableMap[currentRowIndex][i].colSpan ; 559 if ( isNaN( colSpan ) || colSpan < 1 ) 560 colSpan = 1 ; 561 if ( i == cellIndex ) 562 { 563 i += colSpan ; 564 continue ; 565 } 566 var rowSpan = tableMap[currentRowIndex][i].rowSpan ; 567 if ( isNaN( rowSpan ) ) 568 rowSpan = 1 ; 569 tableMap[currentRowIndex][i].rowSpan = rowSpan + 1 ; 570 i += colSpan ; 571 } 572 573 // 3. Insert a new cell to new row. 574 var newCell = FCK.EditorDocument.createElement( 'td' ) ; 575 if ( FCKBrowserInfo.IsGeckoLike ) 576 FCKTools.AppendBogusBr( newCell ) ; 577 newRow.appendChild( newCell ) ; 578 } 579} 580 581// Get the cell index from a TableMap. 582FCKTableHandler._GetCellIndexSpan = function( tableMap, rowIndex, cell ) 583{ 584 if ( tableMap.length < rowIndex + 1 ) 585 return null ; 586 587 var oRow = tableMap[ rowIndex ] ; 588 589 for ( var c = 0 ; c < oRow.length ; c++ ) 590 { 591 if ( oRow[c] == cell ) 592 return c ; 593 } 594 595 return null ; 596} 597 598// Get the cell location from a TableMap. Returns an array with an [x,y] location 599FCKTableHandler._GetCellLocation = function( tableMap, cell ) 600{ 601 for ( var i = 0 ; i < tableMap.length; i++ ) 602 { 603 for ( var c = 0 ; c < tableMap[i].length ; c++ ) 604 { 605 if ( tableMap[i][c] == cell ) return [i,c]; 606 } 607 } 608 return null ; 609} 610 611// Get the cells available in a column of a TableMap. 612FCKTableHandler._GetColumnCells = function( tableMap, columnIndex ) 613{ 614 var aCollCells = new Array() ; 615 616 for ( var r = 0 ; r < tableMap.length ; r++ ) 617 { 618 var oCell = tableMap[r][columnIndex] ; 619 if ( oCell && ( aCollCells.length == 0 || aCollCells[ aCollCells.length - 1 ] != oCell ) ) 620 aCollCells[ aCollCells.length ] = oCell ; 621 } 622 623 return aCollCells ; 624} 625 626// This function is quite hard to explain. It creates a matrix representing all cells in a table. 627// The difference here is that the "spanned" cells (colSpan and rowSpan) are duplicated on the matrix 628// cells that are "spanned". For example, a row with 3 cells where the second cell has colSpan=2 and rowSpan=3 629// will produce a bi-dimensional matrix with the following values (representing the cells): 630// Cell1, Cell2, Cell2, Cell 3 631// Cell4, Cell2, Cell2, Cell 5 632FCKTableHandler._CreateTableMap = function( table ) 633{ 634 var aRows = table.rows ; 635 636 // Row and Column counters. 637 var r = -1 ; 638 639 var aMap = new Array() ; 640 641 for ( var i = 0 ; i < aRows.length ; i++ ) 642 { 643 r++ ; 644 if ( !aMap[r] ) 645 aMap[r] = new Array() ; 646 647 var c = -1 ; 648 649 for ( var j = 0 ; j < aRows[i].cells.length ; j++ ) 650 { 651 var oCell = aRows[i].cells[j] ; 652 653 c++ ; 654 while ( aMap[r][c] ) 655 c++ ; 656 657 var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ; 658 var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ; 659 660 for ( var rs = 0 ; rs < iRowSpan ; rs++ ) 661 { 662 if ( !aMap[r + rs] ) 663 aMap[r + rs] = new Array() ; 664 665 for ( var cs = 0 ; cs < iColSpan ; cs++ ) 666 { 667 aMap[r + rs][c + cs] = aRows[i].cells[j] ; 668 } 669 } 670 671 c += iColSpan - 1 ; 672 } 673 } 674 return aMap ; 675} 676 677// This function is the inverse of _CreateTableMap - it takes in a table map and converts it to an HTML table. 678FCKTableHandler._InstallTableMap = function( tableMap, table ) 679{ 680 // Clear the table of all rows first. 681 while ( table.rows.length > 0 ) 682 { 683 var row = table.rows[0] ; 684 row.parentNode.removeChild( row ) ; 685 } 686 687 // Disconnect all the cells in tableMap from their parents, set all colSpan and rowSpan attributes to 1. 688 for ( var i = 0 ; i < tableMap.length ; i++ ) 689 { 690 for ( var j = 0 ; j < tableMap[i].length ; j++ ) 691 { 692 var cell = tableMap[i][j] ; 693 if ( cell.parentNode ) 694 cell.parentNode.removeChild( cell ) ; 695 cell.colSpan = cell.rowSpan = 1 ; 696 } 697 } 698 699 // Scan by rows and set colSpan. 700 var maxCol = 0 ; 701 for ( var i = 0 ; i < tableMap.length ; i++ ) 702 { 703 for ( var j = 0 ; j < tableMap[i].length ; j++ ) 704 { 705 var cell = tableMap[i][j] ; 706 if ( ! cell) 707 continue ; 708 if ( j > maxCol ) 709 maxCol = j ; 710 if ( cell._colScanned === true ) 711 continue ; 712 if ( tableMap[i][j-1] == cell ) 713 cell.colSpan++ ; 714 if ( tableMap[i][j+1] != cell ) 715 cell._colScanned = true ; 716 } 717 } 718 719 // Scan by columns and set rowSpan. 720 for ( var i = 0 ; i <= maxCol ; i++ ) 721 { 722 for ( var j = 0 ; j < tableMap.length ; j++ ) 723 { 724 if ( ! tableMap[j] ) 725 continue ; 726 var cell = tableMap[j][i] ; 727 if ( ! cell || cell._rowScanned === true ) 728 continue ; 729 if ( tableMap[j-1] && tableMap[j-1][i] == cell ) 730 cell.rowSpan++ ; 731 if ( ! tableMap[j+1] || tableMap[j+1][i] != cell ) 732 cell._rowScanned = true ; 733 } 734 } 735 736 // Clear all temporary flags. 737 for ( var i = 0 ; i < tableMap.length ; i++ ) 738 { 739 for ( var j = 0 ; j < tableMap[i].length ; j++) 740 { 741 var cell = tableMap[i][j] ; 742 if ( FCKBrowserInfo.IsIE ) 743 { 744 cell.removeAttribute( '_colScanned' ) ; 745 cell.removeAttribute( '_rowScanned' ) ; 746 } 747 else 748 { 749 delete cell._colScanned ; 750 delete cell._rowScanned ; 751 } 752 } 753 } 754 755 // Insert physical rows and columns to the table. 756 for ( var i = 0 ; i < tableMap.length ; i++ ) 757 { 758 var rowObj = table.ownerDocument.createElement( 'tr' ) ; 759 for ( var j = 0 ; j < tableMap[i].length ; ) 760 { 761 var cell = tableMap[i][j] ; 762 if ( tableMap[i-1] && tableMap[i-1][j] == cell ) 763 { 764 j += cell.colSpan ; 765 continue ; 766 } 767 rowObj.appendChild( cell ) ; 768 j += cell.colSpan ; 769 if ( cell.colSpan == 1 ) 770 cell.removeAttribute( 'colspan' ) ; 771 if ( cell.rowSpan == 1 ) 772 cell.removeAttribute( 'rowspan' ) ; 773 } 774 table.appendChild( rowObj ) ; 775 } 776} 777 778FCKTableHandler._MoveCaretToCell = function ( refCell, toStart ) 779{ 780 var range = new FCKDomRange( FCK.EditorWindow ) ; 781 range.MoveToNodeContents( refCell ) ; 782 range.Collapse( toStart ) ; 783 range.Select() ; 784} 785 786FCKTableHandler.ClearRow = function( tr ) 787{ 788 // Get the array of row's cells. 789 var aCells = tr.cells ; 790 791 // Replace the contents of each cell with "nothing". 792 for ( var i = 0 ; i < aCells.length ; i++ ) 793 { 794 aCells[i].innerHTML = '' ; 795 796 if ( FCKBrowserInfo.IsGeckoLike ) 797 FCKTools.AppendBogusBr( aCells[i] ) ; 798 } 799} 800 801FCKTableHandler.GetMergeRightTarget = function() 802{ 803 var cells = this.GetSelectedCells() ; 804 if ( cells.length != 1 ) 805 return null ; 806 807 var refCell = cells[0] ; 808 var tableMap = this._CreateTableMap( refCell.parentNode.parentNode ) ; 809 var rowIdx = refCell.parentNode.rowIndex ; 810 var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, refCell ) ; 811 var nextColIdx = colIdx + ( isNaN( refCell.colSpan ) ? 1 : refCell.colSpan ) ; 812 var nextCell = tableMap[rowIdx][nextColIdx] ; 813 814 if ( ! nextCell ) 815 return null ; 816 817 // The two cells must have the same vertical geometry, otherwise merging does not make sense. 818 this._MarkCells( [refCell, nextCell], '_SizeTest' ) ; 819 var refGeometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_SizeTest' ) ; 820 var nextGeometry = this._GetMarkerGeometry( tableMap, rowIdx, nextColIdx, '_SizeTest' ) ; 821 this._UnmarkCells( [refCell, nextCell], '_SizeTest' ) ; 822 823 if ( refGeometry.height != nextGeometry.height || refGeometry.y != nextGeometry.y ) 824 return null ; 825 826 return { 'refCell' : refCell, 'nextCell' : nextCell, 'tableMap' : tableMap } ; 827} 828 829FCKTableHandler.GetMergeDownTarget = function() 830{ 831 var cells = this.GetSelectedCells() ; 832 if ( cells.length != 1 ) 833 return null ; 834 835 var refCell = cells[0] ; 836 var tableMap = this._CreateTableMap( refCell.parentNode.parentNode ) ; 837 var rowIdx = refCell.parentNode.rowIndex ; 838 var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, refCell ) ; 839 var newRowIdx = rowIdx + ( isNaN( refCell.rowSpan ) ? 1 : refCell.rowSpan ) ; 840 if ( ! tableMap[newRowIdx] ) 841 return null ; 842 843 var nextCell = tableMap[newRowIdx][colIdx] ; 844 845 if ( ! nextCell ) 846 return null ; 847 848 // The two cells must have the same horizontal geometry, otherwise merging does not makes sense. 849 this._MarkCells( [refCell, nextCell], '_SizeTest' ) ; 850 var refGeometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_SizeTest' ) ; 851 var nextGeometry = this._GetMarkerGeometry( tableMap, newRowIdx, colIdx, '_SizeTest' ) ; 852 this._UnmarkCells( [refCell, nextCell], '_SizeTest' ) ; 853 854 if ( refGeometry.width != nextGeometry.width || refGeometry.x != nextGeometry.x ) 855 return null ; 856 857 return { 'refCell' : refCell, 'nextCell' : nextCell, 'tableMap' : tableMap } ; 858} 859