| 89 | | function createTableMap( $refCell ) |
| 90 | | { |
| 91 | | var refCell = new CKEDITOR.dom.element( $refCell ); |
| 92 | | var $table = ( refCell.getName() == 'table' ? $refCell : refCell.getAscendant( 'table' ) ).$; |
| 93 | | var $rows = $table.rows; |
| 94 | | |
| 95 | | // Row and column counters. |
| 96 | | var r = -1; |
| 97 | | var map = []; |
| 98 | | for ( var i = 0 ; i < $rows.length ; i++ ) |
| 99 | | { |
| 100 | | r++; |
| 101 | | if ( !map[ r ] ) |
| 102 | | map[ r ] = []; |
| 103 | | |
| 104 | | var c = -1; |
| 105 | | |
| 106 | | for ( var j = 0 ; j < $rows[ i ].cells.length ; j++ ) |
| 107 | | { |
| 108 | | var $cell = $rows[ i ].cells[ j ]; |
| 109 | | |
| 110 | | c++; |
| 111 | | while ( map[ r ][ c ] ) |
| 112 | | c++; |
| 113 | | |
| 114 | | var colSpan = isNaN( $cell.colSpan ) ? 1 : $cell.colSpan; |
| 115 | | var rowSpan = isNaN( $cell.rowSpan ) ? 1 : $cell.rowSpan; |
| 116 | | |
| 117 | | for ( var rs = 0 ; rs < rowSpan ; rs++ ) |
| 118 | | { |
| 119 | | if ( !map[ r + rs ] ) |
| 120 | | map[ r + rs ] = []; |
| 121 | | |
| 122 | | for ( var cs = 0 ; cs < colSpan ; cs++ ) |
| 123 | | map [ r + rs ][ c + cs ] = $rows[ i ].cells[ j ]; |
| 124 | | } |
| 125 | | |
| 126 | | c += colSpan - 1; |
| 127 | | } |
| 128 | | } |
| 129 | | |
| 130 | | return map; |
| 131 | | } |
| 132 | | |
| 133 | | function installTableMap( tableMap, $table ) |
| 134 | | { |
| 135 | | /* |
| 136 | | * IE BUG: rowSpan is always 1 in IE if the cell isn't attached to a row. So |
| 137 | | * store is separately in another attribute. (#1917) |
| 138 | | */ |
| 139 | | var rowSpanAttr = CKEDITOR.env.ie ? '_cke_rowspan' : 'rowSpan'; |
| 140 | | |
| 141 | | /* |
| 142 | | * Disconnect all the cells in tableMap from their parents, set all colSpan |
| 143 | | * and rowSpan attributes to 1. |
| 144 | | */ |
| 145 | | for ( var i = 0 ; i < tableMap.length ; i++ ) |
| 146 | | { |
| 147 | | for ( var j = 0 ; j < tableMap[ i ].length ; j++ ) |
| 148 | | { |
| 149 | | var $cell = tableMap[ i ][ j ]; |
| 150 | | if ( $cell.parentNode ) |
| 151 | | $cell.parentNode.removeChild( $cell ); |
| 152 | | $cell.colSpan = $cell[ rowSpanAttr ] = 1; |
| 153 | | } |
| 154 | | } |
| 155 | | |
| 156 | | // Scan by rows and set colSpan. |
| 157 | | var maxCol = 0; |
| 158 | | for ( i = 0 ; i < tableMap.length ; i++ ) |
| 159 | | { |
| 160 | | for ( j = 0 ; j < tableMap[ i ].length ; j++ ) |
| 161 | | { |
| 162 | | $cell = tableMap[ i ][ j ]; |
| 163 | | if ( !$cell ) |
| 164 | | continue; |
| 165 | | if ( j > maxCol ) |
| 166 | | maxCol = j; |
| 167 | | if ( $cell[ '_cke_colScanned' ] ) |
| 168 | | continue; |
| 169 | | if ( tableMap[ i ][ j - 1 ] == $cell ) |
| 170 | | $cell.colSpan++; |
| 171 | | if ( tableMap[ i ][ j + 1 ] != $cell ) |
| 172 | | $cell[ '_cke_colScanned' ] = 1; |
| 173 | | } |
| 174 | | } |
| 175 | | |
| 176 | | // Scan by columns and set rowSpan. |
| 177 | | for ( i = 0 ; i <= maxCol ; i++ ) |
| 178 | | { |
| 179 | | for ( j = 0 ; j < tableMap.length ; j++ ) |
| 180 | | { |
| 181 | | if ( !tableMap[ j ] ) |
| 182 | | continue; |
| 183 | | $cell = tableMap[ j ][ i ]; |
| 184 | | if ( !$cell || $cell[ '_cke_rowScanned' ] ) |
| 185 | | continue; |
| 186 | | if ( tableMap[ j - 1 ] && tableMap[ j - 1 ][ i ] == $cell ) |
| 187 | | $cell[ rowSpanAttr ]++; |
| 188 | | if ( !tableMap[ j + 1 ] || tableMap[ j + 1 ][ i ] != $cell ) |
| 189 | | $cell[ '_cke_rowScanned' ] = 1; |
| 190 | | } |
| 191 | | } |
| 192 | | |
| 193 | | // Clear all temporary flags. |
| 194 | | for ( i = 0 ; i < tableMap.length ; i++ ) |
| 195 | | { |
| 196 | | for ( j = 0 ; j < tableMap[ i ].length ; j++ ) |
| 197 | | { |
| 198 | | $cell = tableMap[ i ][ j ]; |
| 199 | | removeRawAttribute( $cell, '_cke_colScanned' ); |
| 200 | | removeRawAttribute( $cell, '_cke_rowScanned' ); |
| 201 | | } |
| 202 | | } |
| 203 | | |
| 204 | | // Insert physical rows and columns to table. |
| 205 | | for ( i = 0 ; i < tableMap.length ; i++ ) |
| 206 | | { |
| 207 | | var $row = $table.ownerDocument.createElement( 'tr' ); |
| 208 | | for ( j = 0 ; j < tableMap[ i ].length ; ) |
| 209 | | { |
| 210 | | $cell = tableMap[ i ][ j ]; |
| 211 | | if ( tableMap[ i - 1 ] && tableMap[ i - 1 ][ j ] == $cell ) |
| 212 | | { |
| 213 | | j += $cell.colSpan; |
| 214 | | continue; |
| 215 | | } |
| 216 | | $row.appendChild( $cell ); |
| 217 | | if ( rowSpanAttr != 'rowSpan' ) |
| 218 | | { |
| 219 | | $cell.rowSpan = $cell[ rowSpanAttr ]; |
| 220 | | $cell.removeAttribute( rowSpanAttr ); |
| 221 | | } |
| 222 | | j += $cell.colSpan; |
| 223 | | if ( $cell.colSpan == 1 ) |
| 224 | | $cell.removeAttribute( 'colSpan' ); |
| 225 | | if ( $cell.rowSpan == 1 ) |
| 226 | | $cell.removeAttribute( 'rowSpan' ); |
| 227 | | } |
| 228 | | |
| 229 | | if ( CKEDITOR.env.ie ) |
| 230 | | $table.rows[ i ].replaceNode( $row ); |
| 231 | | else |
| 232 | | { |
| 233 | | var dest = new CKEDITOR.dom.element( $table.rows[ i ] ); |
| 234 | | var src = new CKEDITOR.dom.element( $row ); |
| 235 | | dest.setHtml( '' ); |
| 236 | | src.moveChildren( dest ); |
| 237 | | } |
| 238 | | } |
| 239 | | } |
| 240 | | |
| | 267 | function getChildren( element, tagNames ) |
| | 268 | { |
| | 269 | var children = element.getChildren(), |
| | 270 | count = children.count(), |
| | 271 | child, |
| | 272 | retval = []; |
| | 273 | for ( var i = count - 1; i >= 0; i-- ) |
| | 274 | { |
| | 275 | child = children.getItem( i ); |
| | 276 | // Remove tail empty <tr>. |
| | 277 | if( child.is && child.is( 'tr' ) ) |
| | 278 | { |
| | 279 | var tr = child; |
| | 280 | if( !getChildren( tr, { td : 1, th : 1 } ).length |
| | 281 | && !tr.getNext( function( node ){ return node.is && node.is( 'tr' ) } ) ) |
| | 282 | { |
| | 283 | tr.remove(); |
| | 284 | count++; |
| | 285 | continue; |
| | 286 | } |
| | 287 | } |
| | 288 | if( child.is && ( child.getName() in tagNames ) ) |
| | 289 | retval.push( child ); |
| | 290 | } |
| | 291 | return retval; |
| | 292 | } |
| | 293 | |
| | 294 | function placeCursorInCell( cell ) |
| | 295 | { |
| | 296 | var range = new CKEDITOR.dom.range( cell.getDocument() ); |
| | 297 | range.selectNodeContents( cell ); |
| | 298 | range.collapse(); |
| | 299 | range.select( true ); |
| | 300 | } |
| | 301 | |
| | 302 | function buildTableMap( table ) |
| | 303 | { |
| | 304 | |
| | 305 | var aRows = table.$.rows ; |
| | 306 | |
| | 307 | // Row and Column counters. |
| | 308 | var r = -1 ; |
| | 309 | |
| | 310 | var aMap = []; |
| | 311 | |
| | 312 | for ( var i = 0 ; i < aRows.length ; i++ ) |
| | 313 | { |
| | 314 | r++ ; |
| | 315 | !aMap[r] && ( aMap[r] = [] ); |
| | 316 | |
| | 317 | var c = -1 ; |
| | 318 | |
| | 319 | for ( var j = 0 ; j < aRows[i].cells.length ; j++ ) |
| | 320 | { |
| | 321 | var oCell = aRows[i].cells[j] ; |
| | 322 | |
| | 323 | c++ ; |
| | 324 | while ( aMap[r][c] ) |
| | 325 | c++ ; |
| | 326 | |
| | 327 | var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ; |
| | 328 | var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ; |
| | 329 | |
| | 330 | for ( var rs = 0 ; rs < iRowSpan ; rs++ ) |
| | 331 | { |
| | 332 | if ( !aMap[r + rs] ) |
| | 333 | aMap[r + rs] = new Array() ; |
| | 334 | |
| | 335 | for ( var cs = 0 ; cs < iColSpan ; cs++ ) |
| | 336 | { |
| | 337 | aMap[r + rs][c + cs] = aRows[i].cells[j] ; |
| | 338 | } |
| | 339 | } |
| | 340 | |
| | 341 | c += iColSpan - 1 ; |
| | 342 | } |
| | 343 | } |
| | 344 | return aMap ; |
| | 345 | } |
| | 346 | |
| | 347 | function cellInRow( tableMap, rowIndex, cell ) |
| | 348 | { |
| | 349 | var oRow = tableMap[ rowIndex ]; |
| | 350 | if( typeof cell == 'undefined' ) |
| | 351 | return oRow; |
| | 352 | |
| | 353 | for ( var c = 0 ; oRow && c < oRow.length ; c++ ) |
| | 354 | { |
| | 355 | if ( cell.is && oRow[c] == cell.$ ) |
| | 356 | return c; |
| | 357 | else if( c == cell ) |
| | 358 | return new CKEDITOR.dom.element( oRow[ c ] ); |
| | 359 | } |
| | 360 | return cell.is ? -1 : null; |
| | 361 | } |
| | 362 | |
| | 363 | function cellInCol( tableMap, colIndex, cell ) |
| | 364 | { |
| | 365 | var oCol = []; |
| | 366 | for ( var r = 0; r < tableMap.length; r++ ) |
| | 367 | { |
| | 368 | var row = tableMap[ r ]; |
| | 369 | if( typeof cell == 'undefined' ) |
| | 370 | oCol.push( row[ colIndex ] ); |
| | 371 | else if( cell.is && row[ colIndex ] == cell.$ ) |
| | 372 | return r; |
| | 373 | else if( r == cell ) |
| | 374 | return new CKEDITOR.dom.element( row[ colIndex ] ); |
| | 375 | } |
| | 376 | |
| | 377 | return ( typeof cell == 'undefined' )? oCol : cell.is ? -1 : null; |
| | 378 | } |
| | 379 | |
| | 380 | function mergeCells( selection, isDetect ) |
| | 381 | { |
| | 382 | var cells = getSelectedCells( selection ); |
| | 383 | if( cells.length < 2 ) |
| | 384 | return false; |
| | 385 | |
| | 386 | var cell, |
| | 387 | firstCell = cells[ 0 ], |
| | 388 | doc = firstCell.getDocument(), |
| | 389 | map = buildTableMap( firstCell.getAscendant( 'table' ) ), |
| | 390 | startRow = firstCell.getParent().$.rowIndex, |
| | 391 | startColumn = cellInRow( map, startRow, firstCell ), |
| | 392 | lastRowIndex = startRow, |
| | 393 | totalRowSpan = 0, |
| | 394 | totalColSpan = 0, |
| | 395 | // Use a documentFragment as buffer when appending cell contents. |
| | 396 | frag = !isDetect && new CKEDITOR.dom.documentFragment( doc ), |
| | 397 | dimension = 0; |
| | 398 | |
| | 399 | for ( var i = 0; i < cells.length; i++ ) |
| | 400 | { |
| | 401 | cell = cells[ i ]; |
| | 402 | |
| | 403 | var tr = cell.getParent(), |
| | 404 | cellFirstChild = cell.getFirst(), |
| | 405 | colSpan = cell.$.colSpan, |
| | 406 | rowSpan = cell.$.rowSpan, |
| | 407 | rowIndex = tr.$.rowIndex, |
| | 408 | colIndex = cellInRow( map, rowIndex, cell ); |
| | 409 | |
| | 410 | // Accumulated the actual places taken by all selected cells. |
| | 411 | dimension += colSpan * rowSpan; |
| | 412 | // Accumulated the maximum virtual spans from column and row. |
| | 413 | totalColSpan = Math.max( totalColSpan, colIndex - startColumn + colSpan ) ; |
| | 414 | totalRowSpan = Math.max( totalRowSpan, rowIndex - startRow + rowSpan ); |
| | 415 | |
| | 416 | if ( !isDetect ) |
| | 417 | { |
| | 418 | // Create line-break on new row. |
| | 419 | if( rowIndex != lastRowIndex |
| | 420 | && cellFirstChild |
| | 421 | && !( cellFirstChild.isBlockBoundary |
| | 422 | && cellFirstChild.isBlockBoundary( { br : 1 } ) ) ) |
| | 423 | frag.append( new CKEDITOR.dom.element( 'br', doc ) ); |
| | 424 | |
| | 425 | cell.moveChildren( frag ); |
| | 426 | i && cell.remove(); |
| | 427 | } |
| | 428 | lastRowIndex = rowIndex; |
| | 429 | } |
| | 430 | |
| | 431 | if ( !isDetect ) |
| | 432 | { |
| | 433 | frag.moveChildren( firstCell ); |
| | 434 | var resultTr = firstCell.getParent(), |
| | 435 | tbody = resultTr.getParent(); |
| | 436 | |
| | 437 | // Apply/swip rowspan/colspan by futher checking rows and columns. |
| | 438 | if ( getChildren(resultTr, { td : 1, th : 1 }).length > 1 ) |
| | 439 | firstCell.$.rowSpan = totalRowSpan; |
| | 440 | else |
| | 441 | firstCell.removeAttribute('rowSpan'); |
| | 442 | |
| | 443 | if ( getChildren(tbody, { tr : 1 }).length > 1 ) |
| | 444 | firstCell.$.colSpan = totalColSpan; |
| | 445 | else |
| | 446 | firstCell.removeAttribute('colSpan'); |
| | 447 | |
| | 448 | return firstCell; |
| | 449 | } |
| | 450 | // Be able to merge cells only if actual dimension of selected |
| | 451 | // cells equals to the caculated rectangle. |
| | 452 | else |
| | 453 | return ( totalRowSpan * totalColSpan ) == dimension; |
| | 454 | } |
| | 455 | |
| | 456 | function verticalSplitCell ( selection, isDetect ) |
| | 457 | { |
| | 458 | var cells = getSelectedCells( selection ); |
| | 459 | if( cells.length > 1 ) |
| | 460 | return false; |
| | 461 | else if( isDetect ) |
| | 462 | return true; |
| | 463 | |
| | 464 | var cell = cells[ 0 ], |
| | 465 | tr = cell.getParent(), |
| | 466 | table = tr.getAscendant( 'table' ), |
| | 467 | map = buildTableMap( table ), |
| | 468 | rowIndex = tr.$.rowIndex, |
| | 469 | colIndex = cellInRow( map, rowIndex, cell ), |
| | 470 | rowSpan = cell.$.rowSpan, |
| | 471 | newCell, |
| | 472 | newRowSpan, |
| | 473 | newCellRowSpan, |
| | 474 | newRowIndex; |
| | 475 | |
| | 476 | if( rowSpan > 1 ) |
| | 477 | { |
| | 478 | newRowSpan = Math.ceil( rowSpan / 2 ); |
| | 479 | newCellRowSpan = Math.floor( rowSpan / 2 ); |
| | 480 | newRowIndex = rowIndex + newRowSpan; |
| | 481 | var newCellTr = new CKEDITOR.dom.element( table.$.rows[ newRowIndex ] ), |
| | 482 | newCellRow = cellInRow( map, newRowIndex ), |
| | 483 | candidateCell; |
| | 484 | |
| | 485 | newCell = cell.clone(); |
| | 486 | |
| | 487 | // Figure out where to insert the new cell by checking the vitual row. |
| | 488 | for ( var c = 0; c < newCellRow.length; c++ ) |
| | 489 | { |
| | 490 | candidateCell = newCellRow[ c ]; |
| | 491 | // Catch first cell actually following the column. |
| | 492 | if( candidateCell.parentNode == newCellTr.$ |
| | 493 | && c > colIndex ) |
| | 494 | { |
| | 495 | newCell.insertBefore( new CKEDITOR.dom.element( candidateCell ) ); |
| | 496 | break; |
| | 497 | } |
| | 498 | else |
| | 499 | candidateCell = null; |
| | 500 | } |
| | 501 | |
| | 502 | // The destination row is empty, append at will. |
| | 503 | if( !candidateCell ) |
| | 504 | newCellTr.append( newCell, true ); |
| | 505 | } |
| | 506 | else |
| | 507 | { |
| | 508 | newCellRowSpan = newRowSpan = 1; |
| | 509 | var newCellTr = tr.clone(); |
| | 510 | newCellTr.insertAfter( tr ); |
| | 511 | newCellTr.append( newCell = cell.clone() ); |
| | 512 | var cellsInSameRow = cellInRow( map, rowIndex ); |
| | 513 | for ( var i = 0; i < cellsInSameRow.length; i++ ) |
| | 514 | cellsInSameRow[ i ].rowSpan++; |
| | 515 | } |
| | 516 | |
| | 517 | if( !CKEDITOR.env.ie ) |
| | 518 | newCell.appendBogus(); |
| | 519 | cell.$.rowSpan = newRowSpan; |
| | 520 | newCell.$.rowSpan = newCellRowSpan; |
| | 521 | if( newRowSpan == 1 ) |
| | 522 | cell.removeAttribute( 'rowSpan' ); |
| | 523 | if( newCellRowSpan == 1 ) |
| | 524 | newCell.removeAttribute( 'rowSpan' ); |
| | 525 | |
| | 526 | return newCell; |
| | 527 | } |
| | 528 | |
| | 529 | function horizontalSplitCell( selection, isDetect ) |
| | 530 | { |
| | 531 | var cells = getSelectedCells( selection ); |
| | 532 | if( cells.length > 1 ) |
| | 533 | return false; |
| | 534 | else if( isDetect ) |
| | 535 | return true; |
| | 536 | |
| | 537 | var cell = cells[ 0 ], |
| | 538 | tr = cell.getParent(), |
| | 539 | table = tr.getAscendant( 'table' ), |
| | 540 | map = buildTableMap( table ), |
| | 541 | rowIndex = tr.$.rowIndex, |
| | 542 | colIndex = cellInRow( map, rowIndex, cell ), |
| | 543 | colSpan = cell.$.colSpan, |
| | 544 | newCell, |
| | 545 | newColSpan, |
| | 546 | newCellColSpan; |
| | 547 | |
| | 548 | if( colSpan > 1 ) |
| | 549 | { |
| | 550 | newColSpan = Math.ceil( colSpan / 2 ); |
| | 551 | newCellColSpan = Math.floor( colSpan / 2 ); |
| | 552 | } |
| | 553 | else |
| | 554 | { |
| | 555 | newCellColSpan = newColSpan = 1; |
| | 556 | var cellsInSameCol = cellInCol( map, colIndex ); |
| | 557 | for ( var i = 0; i < cellsInSameCol.length; i++ ) |
| | 558 | cellsInSameCol[ i ].colSpan++; |
| | 559 | } |
| | 560 | newCell = cell.clone(); |
| | 561 | newCell.insertAfter( cell ); |
| | 562 | if( !CKEDITOR.env.ie ) |
| | 563 | newCell.appendBogus(); |
| | 564 | |
| | 565 | cell.$.colSpan = newColSpan; |
| | 566 | newCell.$.colSpan = newCellColSpan; |
| | 567 | if( newColSpan == 1 ) |
| | 568 | cell.removeAttribute( 'colSpan' ); |
| | 569 | if( newCellColSpan == 1 ) |
| | 570 | newCell.removeAttribute( 'colSpan' ); |
| | 571 | |
| | 572 | return newCell; |
| | 573 | } |