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