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