1/** 2 * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. 3 * For licensing, see LICENSE.md or http://ckeditor.com/license 4 */ 5 6( function() { 7 var defaultToPixel = CKEDITOR.tools.cssLength; 8 9 var commitValue = function( data ) { 10 var id = this.id; 11 if ( !data.info ) 12 data.info = {}; 13 data.info[ id ] = this.getValue(); 14 }; 15 16 function tableColumns( table ) { 17 var cols = 0, 18 maxCols = 0; 19 for ( var i = 0, row, rows = table.$.rows.length; i < rows; i++ ) { 20 row = table.$.rows[ i ], cols = 0; 21 for ( var j = 0, cell, cells = row.cells.length; j < cells; j++ ) { 22 cell = row.cells[ j ]; 23 cols += cell.colSpan; 24 } 25 26 cols > maxCols && ( maxCols = cols ); 27 } 28 29 return maxCols; 30 } 31 32 33 // Whole-positive-integer validator. 34 function validatorNum( msg ) { 35 return function() { 36 var value = this.getValue(), 37 pass = !!( CKEDITOR.dialog.validate.integer()( value ) && value > 0 ); 38 39 if ( !pass ) { 40 alert( msg ); 41 this.select(); 42 } 43 44 return pass; 45 }; 46 } 47 48 function tableDialog( editor, command ) { 49 var makeElement = function( name ) { 50 return new CKEDITOR.dom.element( name, editor.document ); 51 }; 52 53 var editable = editor.editable(); 54 55 var dialogadvtab; // = editor.plugins.dialogadvtab; 56 57 return { 58 title: editor.lang.table.title, 59 minWidth: 310, 60 minHeight: CKEDITOR.env.ie ? 310 : 280, 61 62 onLoad: function() { 63 var dialog = this; 64 65 var styles;// = dialog.getContentElement( 'advanced', 'advStyles' ); 66 67 }, 68 69 onShow: function() { 70 // Detect if there's a selected table. 71 var selection = editor.getSelection(), 72 ranges = selection.getRanges(), 73 table; 74 75 var rowsInput = this.getContentElement( 'info', 'txtRows' ), 76 colsInput = this.getContentElement( 'info', 'txtCols' ), 77 widthInput = this.getContentElement( 'info', 'txtWidth' ), 78 heightInput = this.getContentElement( 'info', 'txtHeight' ); 79 80 if ( command == 'tableProperties' ) { 81 var selected = selection.getSelectedElement(); 82 if ( selected && selected.is( 'table' ) ) 83 table = selected; 84 else if ( ranges.length > 0 ) { 85 // Webkit could report the following range on cell selection (#4948): 86 // <table><tr><td>[ </td></tr></table>] 87 if ( CKEDITOR.env.webkit ) 88 ranges[ 0 ].shrink( CKEDITOR.NODE_ELEMENT ); 89 90 table = editor.elementPath( ranges[ 0 ].getCommonAncestor( true ) ).contains( 'table', 1 ); 91 } 92 93 // Save a reference to the selected table, and push a new set of default values. 94 this._.selectedElement = table; 95 } 96 97 // Enable or disable the row, cols, width fields. 98 if ( table ) { 99 this.setupContent( table ); 100 rowsInput && rowsInput.disable(); 101 colsInput && colsInput.disable(); 102 } else { 103 rowsInput && rowsInput.enable(); 104 colsInput && colsInput.enable(); 105 } 106 107 }, 108 onOk: function() { 109 var selection = editor.getSelection(), 110 bms = this._.selectedElement && selection.createBookmarks(); 111 112 var table = this._.selectedElement || makeElement( 'table' ), 113 me = this, 114 data = {}; 115 116 this.commitContent( data, table ); 117 118 if ( data.info ) { 119 var info = data.info; 120 121 // Generate the rows and cols. 122 if ( !this._.selectedElement ) { 123 var tbody = table.append( makeElement( 'tbody' ) ), 124 rows = parseInt( info.txtRows, 10 ) || 0, 125 cols = parseInt( info.txtCols, 10 ) || 0; 126 127 for ( var i = 0; i < rows; i++ ) { 128 var row = tbody.append( makeElement( 'tr' ) ); 129 for ( var j = 0; j < cols; j++ ) { 130 var cell = row.append( makeElement( 'td' ) ); 131 cell.appendBogus(); 132 } 133 } 134 } 135 136 // Modify the table headers. Depends on having rows and cols generated 137 // correctly so it can't be done in commit functions. 138 139 // Should we make a <thead>? 140 var headers = info.selHeaders; 141 if ( !table.$.tHead && ( headers == 'row' || headers == 'both' ) ) { 142 var thead = new CKEDITOR.dom.element( table.$.createTHead() ); 143 tbody = table.getElementsByTag( 'tbody' ).getItem( 0 ); 144 var theRow = tbody.getElementsByTag( 'tr' ).getItem( 0 ); 145 146 // Change TD to TH: 147 for ( i = 0; i < theRow.getChildCount(); i++ ) { 148 var th = theRow.getChild( i ); 149 // Skip bookmark nodes. (#6155) 150 if ( th.type == CKEDITOR.NODE_ELEMENT && !th.data( 'cke-bookmark' ) ) { 151 th.renameNode( 'th' ); 152 th.setAttribute( 'scope', 'col' ); 153 } 154 } 155 thead.append( theRow.remove() ); 156 } 157 158 if ( table.$.tHead !== null && !( headers == 'row' || headers == 'both' ) ) { 159 // Move the row out of the THead and put it in the TBody: 160 thead = new CKEDITOR.dom.element( table.$.tHead ); 161 tbody = table.getElementsByTag( 'tbody' ).getItem( 0 ); 162 163 var previousFirstRow = tbody.getFirst(); 164 while ( thead.getChildCount() > 0 ) { 165 theRow = thead.getFirst(); 166 for ( i = 0; i < theRow.getChildCount(); i++ ) { 167 var newCell = theRow.getChild( i ); 168 if ( newCell.type == CKEDITOR.NODE_ELEMENT ) { 169 newCell.renameNode( 'td' ); 170 newCell.removeAttribute( 'scope' ); 171 } 172 } 173 theRow.insertBefore( previousFirstRow ); 174 } 175 thead.remove(); 176 } 177 178 // Should we make all first cells in a row TH? 179 if ( !this.hasColumnHeaders && ( headers == 'col' || headers == 'both' ) ) { 180 for ( row = 0; row < table.$.rows.length; row++ ) { 181 newCell = new CKEDITOR.dom.element( table.$.rows[ row ].cells[ 0 ] ); 182 newCell.renameNode( 'th' ); 183 newCell.setAttribute( 'scope', 'row' ); 184 } 185 } 186 187 // Should we make all first TH-cells in a row make TD? If 'yes' we do it the other way round :-) 188 if ( ( this.hasColumnHeaders ) && !( headers == 'col' || headers == 'both' ) ) { 189 for ( i = 0; i < table.$.rows.length; i++ ) { 190 row = new CKEDITOR.dom.element( table.$.rows[ i ] ); 191 if ( row.getParent().getName() == 'tbody' ) { 192 newCell = new CKEDITOR.dom.element( row.$.cells[ 0 ] ); 193 newCell.renameNode( 'td' ); 194 newCell.removeAttribute( 'scope' ); 195 } 196 } 197 } 198 199 // Set the width and height. 200 info.txtHeight ? table.setStyle( 'height', info.txtHeight ) : table.removeStyle( 'height' ); 201 info.txtWidth ? table.setStyle( 'width', info.txtWidth ) : table.removeStyle( 'width' ); 202 203 if ( !table.getAttribute( 'style' ) ) 204 table.removeAttribute( 'style' ); 205 } 206 207 // Insert the table element if we're creating one. 208 if ( !this._.selectedElement ) { 209 editor.insertElement( table ); 210 // Override the default cursor position after insertElement to place 211 // cursor inside the first cell (#7959), IE needs a while. 212 setTimeout( function() { 213 var firstCell = new CKEDITOR.dom.element( table.$.rows[ 0 ].cells[ 0 ] ); 214 var range = editor.createRange(); 215 range.moveToPosition( firstCell, CKEDITOR.POSITION_AFTER_START ); 216 range.select(); 217 }, 0 ); 218 } 219 // Properly restore the selection, (#4822) but don't break 220 // because of this, e.g. updated table caption. 221 else 222 try { 223 selection.selectBookmarks( bms ); 224 } catch ( er ) {} 225 }, 226 contents: [ 227 { 228 id: 'info', 229 label: editor.lang.table.title, 230 elements: [ 231 { 232 type: 'hbox', 233 widths: [ null, null ], 234 styles: [ 'vertical-align:top' ], 235 children: [ 236 { 237 type: 'vbox', 238 padding: 0, 239 children: [ 240 { 241 type: 'text', 242 id: 'txtRows', 243 'default': 3, 244 label: editor.lang.table.rows, 245 required: true, 246 controlStyle: 'width:5em', 247 validate: validatorNum( editor.lang.table.invalidRows ), 248 setup: function( selectedElement ) { 249 this.setValue( selectedElement.$.rows.length ); 250 }, 251 commit: commitValue 252 }, 253 { 254 type: 'text', 255 id: 'txtCols', 256 'default': 2, 257 label: editor.lang.table.columns, 258 required: true, 259 controlStyle: 'width:5em', 260 validate: validatorNum( editor.lang.table.invalidCols ), 261 setup: function( selectedTable ) { 262 this.setValue( tableColumns( selectedTable ) ); 263 }, 264 commit: commitValue 265 }, 266 { 267 type: 'html', 268 html: ' ' 269 }, 270 { 271 type: 'select', 272 id: 'selHeaders', 273 requiredContent: 'th', 274 'default': '', 275 label: editor.lang.table.headers, 276 items: [ 277 [ editor.lang.table.headersNone, '' ], 278 [ editor.lang.table.headersRow, 'row' ], 279 [ editor.lang.table.headersColumn, 'col' ], 280 [ editor.lang.table.headersBoth, 'both' ] 281 ], 282 setup: function( selectedTable ) { 283 // Fill in the headers field. 284 var dialog = this.getDialog(); 285 dialog.hasColumnHeaders = true; 286 287 // Check if all the first cells in every row are TH 288 for ( var row = 0; row < selectedTable.$.rows.length; row++ ) { 289 // If just one cell isn't a TH then it isn't a header column 290 var headCell = selectedTable.$.rows[ row ].cells[ 0 ]; 291 if ( headCell && headCell.nodeName.toLowerCase() != 'th' ) { 292 dialog.hasColumnHeaders = false; 293 break; 294 } 295 } 296 297 // Check if the table contains <thead>. 298 if ( ( selectedTable.$.tHead !== null ) ) 299 this.setValue( dialog.hasColumnHeaders ? 'both' : 'row' ); 300 else 301 this.setValue( dialog.hasColumnHeaders ? 'col' : '' ); 302 }, 303 commit: commitValue 304 }, 305 { 306 type: 'text', 307 id: 'txtBorder', 308 hidden: true, 309 'default': 1, 310 label: editor.lang.table.border, 311 controlStyle: 'width:3em', 312 commit: function( data, selectedTable ) { 313 } 314 }, 315 { 316 id: 'cmbAlign', 317 type: 'select', 318 hidden: true, 319 'default': '', 320 label: editor.lang.common.align, 321 items: [ 322 [ editor.lang.common.notSet, '' ], 323 [ editor.lang.common.alignLeft, 'left' ], 324 [ editor.lang.common.alignCenter, 'center' ], 325 [ editor.lang.common.alignRight, 'right' ] 326 ], 327 setup: function( selectedTable ) { 328 this.setValue( selectedTable.getAttribute( 'align' ) || '' ); 329 }, 330 commit: function( data, selectedTable ) { 331 if ( this.getValue() ) 332 selectedTable.setAttribute( 'align', this.getValue() ); 333 else 334 selectedTable.removeAttribute( 'align' ); 335 } 336 } 337 ] 338 }, 339 { 340 type: 'vbox', 341 padding: 0, 342 children: [ 343 { 344 type: 'hbox', 345 widths: [ '5em' ], 346 children: [ 347 { 348 type: 'text', 349 id: 'txtWidth', 350 hidden: true, 351 controlStyle: 'width:5em', 352 label: editor.lang.common.width, 353 title: editor.lang.common.cssLengthTooltip, 354 // Smarter default table width. (#9600) 355 'default': editor.filter.check( 'table{width}' ) ? ( editable.getSize( 'width' ) < 500 ? '100%' : 500 ) : 0, 356 getValue: defaultToPixel, 357 validate: CKEDITOR.dialog.validate.cssLength( editor.lang.common.invalidCssLength.replace( '%1', editor.lang.common.width ) ), 358 onChange: function() { 359 var styles = this.getDialog().getContentElement( 'advanced', 'advStyles' ); 360 styles && styles.updateStyle( 'width', this.getValue() ); 361 }, 362 setup: function( selectedTable ) { 363 var val = selectedTable.getStyle( 'width' ); 364 this.setValue( val ); 365 }, 366 commit: commitValue 367 } 368 ] 369 }, 370 { 371 type: 'hbox', 372 widths: [ '5em' ], 373 children: [ 374 { 375 type: 'text', 376 id: 'txtHeight', 377 hidden: true, 378 controlStyle: 'width:5em', 379 label: editor.lang.common.height, 380 title: editor.lang.common.cssLengthTooltip, 381 setup: function( selectedTable ) { 382 }, 383 commit: commitValue 384 } 385 ] 386 }, 387 { 388 type: 'html', 389 html: ' ' 390 }, 391 { 392 type: 'text', 393 id: 'txtCellSpace', 394 hidden: true, 395 setup: function( selectedTable ) { 396 }, 397 commit: function( data, selectedTable ) { 398 } 399 }, 400 401 ] 402 } 403 ] 404 }, 405 406 ] 407 }, 408 //dialogadvtab && dialogadvtab.createAdvancedTab( editor ) 409 ] 410 }; 411 } 412 413 CKEDITOR.dialog.add( 'table', function( editor ) { 414 return tableDialog( editor, 'table' ); 415 } ); 416 CKEDITOR.dialog.add( 'tableProperties', function( editor ) { 417 return tableDialog( editor, 'tableProperties' ); 418 } ); 419} )(); 420