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