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