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>[&nbsp;</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: '&nbsp;'
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: '&nbsp;'
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