1 /* 2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 (function() 7 { 8 var defaultToPixel = CKEDITOR.tools.cssLength; 9 10 var commitValue = function( data ) 11 { 12 var id = this.id; 13 if ( !data.info ) 14 data.info = {}; 15 data.info[id] = this.getValue(); 16 }; 17 18 function tableColumns( table ) 19 { 20 var cols = 0, maxCols = 0; 21 for ( var i = 0, row, rows = table.$.rows.length; i < rows; i++ ) 22 { 23 row = table.$.rows[ i ], cols = 0; 24 for ( var j = 0, cell, cells = row.cells.length; j < cells; j++ ) 25 { 26 cell = row.cells[ j ]; 27 cols += cell.colSpan; 28 } 29 30 cols > maxCols && ( maxCols = cols ); 31 } 32 33 return maxCols; 34 } 35 36 37 // Whole-positive-integer validator. 38 function validatorNum( msg ) 39 { 40 return function() 41 { 42 var value = this.getValue(), 43 pass = !!( CKEDITOR.dialog.validate.integer()( value ) && value > 0 ); 44 45 if ( !pass ) 46 { 47 alert( msg ); 48 this.select(); 49 } 50 51 return pass; 52 }; 53 } 54 55 function tableDialog( editor, command ) 56 { 57 var makeElement = function( name ) 58 { 59 return new CKEDITOR.dom.element( name, editor.document ); 60 }; 61 62 var dialogadvtab = editor.plugins.dialogadvtab; 63 64 return { 65 title : editor.lang.table.title, 66 minWidth : 310, 67 minHeight : CKEDITOR.env.ie ? 310 : 280, 68 69 onLoad : function() 70 { 71 var dialog = this; 72 73 var styles = dialog.getContentElement( 'advanced', 'advStyles' ); 74 75 if ( styles ) 76 { 77 styles.on( 'change', function( evt ) 78 { 79 // Synchronize width value. 80 var width = this.getStyle( 'width', '' ), 81 txtWidth = dialog.getContentElement( 'info', 'txtWidth' ); 82 83 txtWidth && txtWidth.setValue( width, true ); 84 85 // Synchronize height value. 86 var height = this.getStyle( 'height', '' ), 87 txtHeight = dialog.getContentElement( 'info', 'txtHeight' ); 88 89 txtHeight && txtHeight.setValue( height, true ); 90 }); 91 } 92 }, 93 94 onShow : function() 95 { 96 // Detect if there's a selected table. 97 var selection = editor.getSelection(), 98 ranges = selection.getRanges(), 99 selectedTable = null; 100 101 var rowsInput = this.getContentElement( 'info', 'txtRows' ), 102 colsInput = this.getContentElement( 'info', 'txtCols' ), 103 widthInput = this.getContentElement( 'info', 'txtWidth' ), 104 heightInput = this.getContentElement( 'info', 'txtHeight' ); 105 106 if ( command == 'tableProperties' ) 107 { 108 if ( ( selectedTable = selection.getSelectedElement() ) ) 109 selectedTable = selectedTable.getAscendant( 'table', true ); 110 else if ( ranges.length > 0 ) 111 { 112 // Webkit could report the following range on cell selection (#4948): 113 // <table><tr><td>[ </td></tr></table>] 114 if ( CKEDITOR.env.webkit ) 115 ranges[ 0 ].shrink( CKEDITOR.NODE_ELEMENT ); 116 117 var rangeRoot = ranges[0].getCommonAncestor( true ); 118 selectedTable = rangeRoot.getAscendant( 'table', true ); 119 } 120 121 // Save a reference to the selected table, and push a new set of default values. 122 this._.selectedElement = selectedTable; 123 } 124 125 // Enable or disable the row, cols, width fields. 126 if ( selectedTable ) 127 { 128 this.setupContent( selectedTable ); 129 rowsInput && rowsInput.disable(); 130 colsInput && colsInput.disable(); 131 } 132 else 133 { 134 rowsInput && rowsInput.enable(); 135 colsInput && colsInput.enable(); 136 } 137 138 // Call the onChange method for the widht and height fields so 139 // they get reflected into the Advanced tab. 140 widthInput && widthInput.onChange(); 141 heightInput && heightInput.onChange(); 142 }, 143 onOk : function() 144 { 145 var selection = editor.getSelection(), 146 bms = this._.selectedElement && selection.createBookmarks(); 147 148 var table = this._.selectedElement || makeElement( 'table' ), 149 me = this, 150 data = {}; 151 152 this.commitContent( data, table ); 153 154 if ( data.info ) 155 { 156 var info = data.info; 157 158 // Generate the rows and cols. 159 if ( !this._.selectedElement ) 160 { 161 var tbody = table.append( makeElement( 'tbody' ) ), 162 rows = parseInt( info.txtRows, 10 ) || 0, 163 cols = parseInt( info.txtCols, 10 ) || 0; 164 165 for ( var i = 0 ; i < rows ; i++ ) 166 { 167 var row = tbody.append( makeElement( 'tr' ) ); 168 for ( var j = 0 ; j < cols ; j++ ) 169 { 170 var cell = row.append( makeElement( 'td' ) ); 171 if ( !CKEDITOR.env.ie ) 172 cell.append( makeElement( 'br' ) ); 173 } 174 } 175 } 176 177 // Modify the table headers. Depends on having rows and cols generated 178 // correctly so it can't be done in commit functions. 179 180 // Should we make a <thead>? 181 var headers = info.selHeaders; 182 if ( !table.$.tHead && ( headers == 'row' || headers == 'both' ) ) 183 { 184 var thead = new CKEDITOR.dom.element( table.$.createTHead() ); 185 tbody = table.getElementsByTag( 'tbody' ).getItem( 0 ); 186 var theRow = tbody.getElementsByTag( 'tr' ).getItem( 0 ); 187 188 // Change TD to TH: 189 for ( i = 0 ; i < theRow.getChildCount() ; i++ ) 190 { 191 var th = theRow.getChild( i ); 192 // Skip bookmark nodes. (#6155) 193 if ( th.type == CKEDITOR.NODE_ELEMENT && !th.data( 'cke-bookmark' ) ) 194 { 195 th.renameNode( 'th' ); 196 th.setAttribute( 'scope', 'col' ); 197 } 198 } 199 thead.append( theRow.remove() ); 200 } 201 202 if ( table.$.tHead !== null && !( headers == 'row' || headers == 'both' ) ) 203 { 204 // Move the row out of the THead and put it in the TBody: 205 thead = new CKEDITOR.dom.element( table.$.tHead ); 206 tbody = table.getElementsByTag( 'tbody' ).getItem( 0 ); 207 208 var previousFirstRow = tbody.getFirst(); 209 while ( thead.getChildCount() > 0 ) 210 { 211 theRow = thead.getFirst(); 212 for ( i = 0; i < theRow.getChildCount() ; i++ ) 213 { 214 var newCell = theRow.getChild( i ); 215 if ( newCell.type == CKEDITOR.NODE_ELEMENT ) 216 { 217 newCell.renameNode( 'td' ); 218 newCell.removeAttribute( 'scope' ); 219 } 220 } 221 theRow.insertBefore( previousFirstRow ); 222 } 223 thead.remove(); 224 } 225 226 // Should we make all first cells in a row TH? 227 if ( !this.hasColumnHeaders && ( headers == 'col' || headers == 'both' ) ) 228 { 229 for ( row = 0 ; row < table.$.rows.length ; row++ ) 230 { 231 newCell = new CKEDITOR.dom.element( table.$.rows[ row ].cells[ 0 ] ); 232 newCell.renameNode( 'th' ); 233 newCell.setAttribute( 'scope', 'row' ); 234 } 235 } 236 237 // Should we make all first TH-cells in a row make TD? If 'yes' we do it the other way round :-) 238 if ( ( this.hasColumnHeaders ) && !( headers == 'col' || headers == 'both' ) ) 239 { 240 for ( i = 0 ; i < table.$.rows.length ; i++ ) 241 { 242 row = new CKEDITOR.dom.element( table.$.rows[i] ); 243 if ( row.getParent().getName() == 'tbody' ) 244 { 245 newCell = new CKEDITOR.dom.element( row.$.cells[0] ); 246 newCell.renameNode( 'td' ); 247 newCell.removeAttribute( 'scope' ); 248 } 249 } 250 } 251 252 // Set the width and height. 253 info.txtHeight ? table.setStyle( 'height', info.txtHeight ) : table.removeStyle( 'height' ); 254 info.txtWidth ? table.setStyle( 'width', info.txtWidth ) : table.removeStyle( 'width' ); 255 256 if ( !table.getAttribute( 'style' ) ) 257 table.removeAttribute( 'style' ); 258 } 259 260 // Insert the table element if we're creating one. 261 if ( !this._.selectedElement ) 262 { 263 editor.insertElement( table ); 264 // Override the default cursor position after insertElement to place 265 // cursor inside the first cell (#7959), IE needs a while. 266 setTimeout( function() 267 { 268 var firstCell = new CKEDITOR.dom.element( table.$.rows[ 0 ].cells[ 0 ] ); 269 var range = new CKEDITOR.dom.range( editor.document ); 270 range.moveToPosition( firstCell, CKEDITOR.POSITION_AFTER_START ); 271 range.select( 1 ); 272 }, 0 ); 273 } 274 // Properly restore the selection, (#4822) but don't break 275 // because of this, e.g. updated table caption. 276 else 277 try { selection.selectBookmarks( bms ); } catch( er ){} 278 }, 279 contents : [ 280 { 281 id : 'info', 282 label : editor.lang.table.title, 283 elements : 284 [ 285 { 286 type : 'hbox', 287 widths : [ null, null ], 288 styles : [ 'vertical-align:top' ], 289 children : 290 [ 291 { 292 type : 'vbox', 293 padding : 0, 294 children : 295 [ 296 { 297 type : 'text', 298 id : 'txtRows', 299 'default' : 3, 300 label : editor.lang.table.rows, 301 required : true, 302 controlStyle : 'width:5em', 303 validate : validatorNum( editor.lang.table.invalidRows ), 304 setup : function( selectedElement ) 305 { 306 this.setValue( selectedElement.$.rows.length ); 307 }, 308 commit : commitValue 309 }, 310 { 311 type : 'text', 312 id : 'txtCols', 313 'default' : 2, 314 label : editor.lang.table.columns, 315 required : true, 316 controlStyle : 'width:5em', 317 validate : validatorNum( editor.lang.table.invalidCols ), 318 setup : function( selectedTable ) 319 { 320 this.setValue( tableColumns( selectedTable ) ); 321 }, 322 commit : commitValue 323 }, 324 { 325 type : 'html', 326 html : ' ' 327 }, 328 { 329 type : 'select', 330 id : 'selHeaders', 331 'default' : '', 332 label : editor.lang.table.headers, 333 items : 334 [ 335 [ editor.lang.table.headersNone, '' ], 336 [ editor.lang.table.headersRow, 'row' ], 337 [ editor.lang.table.headersColumn, 'col' ], 338 [ editor.lang.table.headersBoth, 'both' ] 339 ], 340 setup : function( selectedTable ) 341 { 342 // Fill in the headers field. 343 var dialog = this.getDialog(); 344 dialog.hasColumnHeaders = true; 345 346 // Check if all the first cells in every row are TH 347 for ( var row = 0 ; row < selectedTable.$.rows.length ; row++ ) 348 { 349 // If just one cell isn't a TH then it isn't a header column 350 var headCell = selectedTable.$.rows[row].cells[0]; 351 if ( headCell && headCell.nodeName.toLowerCase() != 'th' ) 352 { 353 dialog.hasColumnHeaders = false; 354 break; 355 } 356 } 357 358 // Check if the table contains <thead>. 359 if ( ( selectedTable.$.tHead !== null) ) 360 this.setValue( dialog.hasColumnHeaders ? 'both' : 'row' ); 361 else 362 this.setValue( dialog.hasColumnHeaders ? 'col' : '' ); 363 }, 364 commit : commitValue 365 }, 366 { 367 type : 'text', 368 id : 'txtBorder', 369 'default' : 1, 370 label : editor.lang.table.border, 371 controlStyle : 'width:3em', 372 validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidBorder ), 373 setup : function( selectedTable ) 374 { 375 this.setValue( selectedTable.getAttribute( 'border' ) || '' ); 376 }, 377 commit : function( data, selectedTable ) 378 { 379 if ( this.getValue() ) 380 selectedTable.setAttribute( 'border', this.getValue() ); 381 else 382 selectedTable.removeAttribute( 'border' ); 383 } 384 }, 385 { 386 id : 'cmbAlign', 387 type : 'select', 388 'default' : '', 389 label : editor.lang.common.align, 390 items : 391 [ 392 [ editor.lang.common.notSet , ''], 393 [ editor.lang.common.alignLeft , 'left'], 394 [ editor.lang.common.alignCenter , 'center'], 395 [ editor.lang.common.alignRight , 'right'] 396 ], 397 setup : function( selectedTable ) 398 { 399 this.setValue( selectedTable.getAttribute( 'align' ) || '' ); 400 }, 401 commit : function( data, selectedTable ) 402 { 403 if ( this.getValue() ) 404 selectedTable.setAttribute( 'align', this.getValue() ); 405 else 406 selectedTable.removeAttribute( 'align' ); 407 } 408 } 409 ] 410 }, 411 { 412 type : 'vbox', 413 padding : 0, 414 children : 415 [ 416 { 417 type : 'hbox', 418 widths : [ '5em' ], 419 children : 420 [ 421 { 422 type : 'text', 423 id : 'txtWidth', 424 controlStyle : 'width:5em', 425 label : editor.lang.common.width, 426 title : editor.lang.common.cssLengthTooltip, 427 'default' : 500, 428 getValue : defaultToPixel, 429 validate : CKEDITOR.dialog.validate.cssLength( editor.lang.common.invalidCssLength.replace( '%1', editor.lang.common.width ) ), 430 onChange : function() 431 { 432 var styles = this.getDialog().getContentElement( 'advanced', 'advStyles' ); 433 styles && styles.updateStyle( 'width', this.getValue() ); 434 }, 435 setup : function( selectedTable ) 436 { 437 var val = selectedTable.getStyle( 'width' ); 438 val && this.setValue( val ); 439 }, 440 commit : commitValue 441 } 442 ] 443 }, 444 { 445 type : 'hbox', 446 widths : [ '5em' ], 447 children : 448 [ 449 { 450 type : 'text', 451 id : 'txtHeight', 452 controlStyle : 'width:5em', 453 label : editor.lang.common.height, 454 title : editor.lang.common.cssLengthTooltip, 455 'default' : '', 456 getValue : defaultToPixel, 457 validate : CKEDITOR.dialog.validate.cssLength( editor.lang.common.invalidCssLength.replace( '%1', editor.lang.common.height ) ), 458 onChange : function() 459 { 460 var styles = this.getDialog().getContentElement( 'advanced', 'advStyles' ); 461 styles && styles.updateStyle( 'height', this.getValue() ); 462 }, 463 464 setup : function( selectedTable ) 465 { 466 var val = selectedTable.getStyle( 'height' ); 467 val && this.setValue( val ); 468 }, 469 commit : commitValue 470 } 471 ] 472 }, 473 { 474 type : 'html', 475 html : ' ' 476 }, 477 { 478 type : 'text', 479 id : 'txtCellSpace', 480 controlStyle : 'width:3em', 481 label : editor.lang.table.cellSpace, 482 'default' : 1, 483 validate : CKEDITOR.dialog.validate.number( editor.lang.table.invalidCellSpacing ), 484 setup : function( selectedTable ) 485 { 486 this.setValue( selectedTable.getAttribute( 'cellSpacing' ) || '' ); 487 }, 488 commit : function( data, selectedTable ) 489 { 490 if ( this.getValue() ) 491 selectedTable.setAttribute( 'cellSpacing', this.getValue() ); 492 else 493 selectedTable.removeAttribute( 'cellSpacing' ); 494 } 495 }, 496 { 497 type : 'text', 498 id : 'txtCellPad', 499 controlStyle : 'width:3em', 500 label : editor.lang.table.cellPad, 501 'default' : 1, 502 validate : CKEDITOR.dialog.validate.number( editor.lang.table.invalidCellPadding ), 503 setup : function( selectedTable ) 504 { 505 this.setValue( selectedTable.getAttribute( 'cellPadding' ) || '' ); 506 }, 507 commit : function( data, selectedTable ) 508 { 509 if ( this.getValue() ) 510 selectedTable.setAttribute( 'cellPadding', this.getValue() ); 511 else 512 selectedTable.removeAttribute( 'cellPadding' ); 513 } 514 } 515 ] 516 } 517 ] 518 }, 519 { 520 type : 'html', 521 align : 'right', 522 html : '' 523 }, 524 { 525 type : 'vbox', 526 padding : 0, 527 children : 528 [ 529 { 530 type : 'text', 531 id : 'txtCaption', 532 label : editor.lang.table.caption, 533 setup : function( selectedTable ) 534 { 535 this.enable(); 536 537 var nodeList = selectedTable.getElementsByTag( 'caption' ); 538 if ( nodeList.count() > 0 ) 539 { 540 var caption = nodeList.getItem( 0 ); 541 var firstElementChild = caption.getFirst( CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ) ); 542 543 if ( firstElementChild && !firstElementChild.equals( caption.getBogus() ) ) 544 { 545 this.disable(); 546 this.setValue( caption.getText() ); 547 return; 548 } 549 550 caption = CKEDITOR.tools.trim( caption.getText() ); 551 this.setValue( caption ); 552 } 553 }, 554 commit : function( data, table ) 555 { 556 if ( !this.isEnabled() ) 557 return; 558 559 var caption = this.getValue(), 560 captionElement = table.getElementsByTag( 'caption' ); 561 if ( caption ) 562 { 563 if ( captionElement.count() > 0 ) 564 { 565 captionElement = captionElement.getItem( 0 ); 566 captionElement.setHtml( '' ); 567 } 568 else 569 { 570 captionElement = new CKEDITOR.dom.element( 'caption', editor.document ); 571 if ( table.getChildCount() ) 572 captionElement.insertBefore( table.getFirst() ); 573 else 574 captionElement.appendTo( table ); 575 } 576 captionElement.append( new CKEDITOR.dom.text( caption, editor.document ) ); 577 } 578 else if ( captionElement.count() > 0 ) 579 { 580 for ( var i = captionElement.count() - 1 ; i >= 0 ; i-- ) 581 captionElement.getItem( i ).remove(); 582 } 583 } 584 }, 585 { 586 type : 'text', 587 id : 'txtSummary', 588 label : editor.lang.table.summary, 589 setup : function( selectedTable ) 590 { 591 this.setValue( selectedTable.getAttribute( 'summary' ) || '' ); 592 }, 593 commit : function( data, selectedTable ) 594 { 595 if ( this.getValue() ) 596 selectedTable.setAttribute( 'summary', this.getValue() ); 597 else 598 selectedTable.removeAttribute( 'summary' ); 599 } 600 } 601 ] 602 } 603 ] 604 }, 605 dialogadvtab && dialogadvtab.createAdvancedTab( editor ) 606 ] 607 }; 608 } 609 610 CKEDITOR.dialog.add( 'table', function( editor ) 611 { 612 return tableDialog( editor, 'table' ); 613 } ); 614 CKEDITOR.dialog.add( 'tableProperties', function( editor ) 615 { 616 return tableDialog( editor, 'tableProperties' ); 617 } ); 618 })(); 619