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 | } |