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 /** @fileoverview The "dialogui" plugin. */
  7
  8 CKEDITOR.plugins.add( 'dialogui' );
  9
 10 (function()
 11 {
 12 	var initPrivateObject = function( elementDefinition )
 13 	{
 14 		this._ || ( this._ = {} );
 15 		this._['default'] = this._.initValue = elementDefinition['default'] || '';
 16 		var args = [ this._ ];
 17 		for ( var i = 1 ; i < arguments.length ; i++ )
 18 			args.push( arguments[i] );
 19 		args.push( true );
 20 		CKEDITOR.tools.extend.apply( CKEDITOR.tools, args );
 21 		return this._;
 22 	},
 23 	textBuilder =
 24 	{
 25 		build : function( dialog, elementDefinition, output )
 26 		{
 27 			return new CKEDITOR.ui.dialog.textInput( dialog, elementDefinition, output );
 28 		}
 29 	},
 30 	commonBuilder =
 31 	{
 32 		build : function( dialog, elementDefinition, output )
 33 		{
 34 			return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, elementDefinition, output );
 35 		}
 36 	},
 37 	commonPrototype =
 38 	{
 39 		isChanged : function()
 40 		{
 41 			return this.getValue() != this.getInitValue();
 42 		},
 43
 44 		reset : function()
 45 		{
 46 			this.setValue( this.getInitValue() );
 47 		},
 48
 49 		setInitValue : function()
 50 		{
 51 			this._.initValue = this.getValue();
 52 		},
 53
 54 		resetInitValue : function()
 55 		{
 56 			this._.initValue = this._['default'];
 57 		},
 58
 59 		getInitValue : function()
 60 		{
 61 			return this._.initValue;
 62 		}
 63 	},
 64 	commonEventProcessors = CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
 65 		{
 66 			onChange : function( dialog, func )
 67 			{
 68 				if ( !this._.domOnChangeRegistered )
 69 				{
 70 					dialog.on( 'load', function()
 71 						{
 72 							this.getInputElement().on( 'change', function(){ this.fire( 'change', { value : this.getValue() } ); }, this );
 73 						}, this );
 74 					this._.domOnChangeRegistered = true;
 75 				}
 76
 77 				this.on( 'change', func );
 78 			}
 79 		}, true ),
 80 	eventRegex = /^on([A-Z]\w+)/,
 81 	cleanInnerDefinition = function( def )
 82 	{
 83 		// An inner UI element should not have the parent's type, title or events.
 84 		for ( var i in def )
 85 		{
 86 			if ( eventRegex.test( i ) || i == 'title' || i == 'type' )
 87 				delete def[i];
 88 		}
 89 		return def;
 90 	};
 91
 92 	CKEDITOR.tools.extend( CKEDITOR.ui.dialog,
 93 		/** @lends CKEDITOR.ui.dialog */
 94 		{
 95 			/**
 96 			 * Base class for all dialog elements with a textual label on the left.
 97 			 * @constructor
 98 			 * @example
 99 			 * @extends CKEDITOR.ui.dialog.uiElement
100 			 * @param {CKEDITOR.dialog} dialog
101 			 * Parent dialog object.
102 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
103 			 * The element definition. Accepted fields:
104 			 * <ul>
105 			 * 	<li><strong>label</strong> (Required) The label string.</li>
106 			 * 	<li><strong>labelLayout</strong> (Optional) Put 'horizontal' here if the
107 			 * 	label element is to be layed out horizontally. Otherwise a vertical
108 			 * 	layout will be used.</li>
109 			 * 	<li><strong>widths</strong> (Optional) This applies only for horizontal
110 			 * 	layouts - an 2-element array of lengths to specify the widths of the
111 			 * 	label and the content element.</li>
112 			 * </ul>
113 			 * @param {Array} htmlList
114 			 * List of HTML code to output to.
115 			 * @param {Function} contentHtml
116 			 * A function returning the HTML code string to be added inside the content
117 			 * cell.
118 			 */
119 			labeledElement : function( dialog, elementDefinition, htmlList, contentHtml )
120 			{
121 				if ( arguments.length < 4 )
122 					return;
123
124 				var _ = initPrivateObject.call( this, elementDefinition );
125 				_.labelId = CKEDITOR.tools.getNextNumber() + '_label';
126 				var children = this._.children = [];
127 				/** @ignore */
128 				var innerHTML = function()
129 				{
130 					var html = [];
131 					if ( elementDefinition.labelLayout != 'horizontal' )
132 						html.push( '<div class="cke_dialog_ui_labeled_label" id="',
133 								_.labelId,
134 								'" >',
135 								elementDefinition.label,
136 								'</div>',
137 								'<div class="cke_dialog_ui_labeled_content">',
138 								contentHtml( dialog, elementDefinition ),
139 								'</div>' );
140 					else
141 					{
142 						var hboxDefinition = {
143 							type : 'hbox',
144 							widths : elementDefinition.widths,
145 							padding : 0,
146 							children :
147 							[
148 								{
149 									type : 'html',
150 									html : '<span class="cke_dialog_ui_labeled_label" ' +
151 										'id="' + _.labelId + '">' +  CKEDITOR.tools.htmlEncode( elementDefinition.label ) +
152 										'</span>'
153 								},
154 								{
155 									type : 'html',
156 									html : '<span class="cke_dialog_ui_labeled_content">' +
157 										contentHtml( dialog, elementDefinition ) +
158 										'</span>'
159 								}
160 							]
161 						};
162 						CKEDITOR.dialog._.uiElementBuilders.hbox.build( dialog, hboxDefinition, html );
163 					}
164 					return html.join( '' );
165 				};
166 				CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'div', null, null, innerHTML );
167 			},
168
169 			/**
170 			 * A text input with a label. This UI element class represents both the
171 			 * single-line text inputs and password inputs in dialog boxes.
172 			 * @constructor
173 			 * @example
174 			 * @extends CKEDITOR.ui.dialog.labeledElement
175 			 * @param {CKEDITOR.dialog} dialog
176 			 * Parent dialog object.
177 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
178 			 * The element definition. Accepted fields:
179 			 * <ul>
180 			 * 	<li><strong>default</strong> (Optional) The default value.</li>
181 			 * 	<li><strong>validate</strong> (Optional) The validation function. </li>
182 			 * 	<li><strong>maxLength</strong> (Optional) The maximum length of text box
183 			 * 	contents.</li>
184 			 * 	<li><strong>size</strong> (Optional) The size of the text box. This is
185 			 * 	usually overridden by the size defined by the skin, however.</li>
186 			 * </ul>
187 			 * @param {Array} htmlList
188 			 * List of HTML code to output to.
189 			 */
190 			textInput : function( dialog, elementDefinition, htmlList )
191 			{
192 				if ( arguments.length < 3 )
193 					return;
194
195 				initPrivateObject.call( this, elementDefinition );
196 				var domId = this._.inputId = CKEDITOR.tools.getNextNumber() + '_textInput',
197 					attributes = { 'class' : 'cke_dialog_ui_input_' + elementDefinition.type, id : domId, type : 'text' },
198 					i;
199
200 				// Set the validator, if any.
201 				if ( elementDefinition.validate )
202 					this.validate = elementDefinition.validate;
203
204 				// Set the max length and size.
205 				if ( elementDefinition.maxLength )
206 					attributes.maxlength = elementDefinition.maxLength;
207 				if ( elementDefinition.size )
208 					attributes.size = elementDefinition.size;
209
210 				// If user presses Enter in a text box, it implies clicking OK for the dialog.
211 				var me = this, keyPressedOnMe = false;
212 				dialog.on( 'load', function()
213 					{
214 						me.getInputElement().on( 'keydown', function( evt )
215 							{
216 								if ( evt.data.getKeystroke() == 13 )
217 									keyPressedOnMe = true;
218 							} );
219
220 						// Lower the priority this 'keyup' since 'ok' will close the dialog.(#3749)
221 						me.getInputElement().on( 'keyup', function( evt )
222 							{
223 								if ( evt.data.getKeystroke() == 13 && keyPressedOnMe )
224 								{
225 									dialog.getButton( 'ok' ) && dialog.getButton( 'ok' ).click();
226 									keyPressedOnMe = false;
227 								}
228 							}, null, null, 1000 );
229 					} );
230
231 				/** @ignore */
232 				var innerHTML = function()
233 				{
234 					// IE BUG: Text input fields in IE at 100% would exceed a <td> or inline
235 					// container's width, so need to wrap it inside a <div>.
236 					var html = [ '<div class="cke_dialog_ui_input_', elementDefinition.type, '"><input ' ];
237 					for ( var i in attributes )
238 						html.push( i + '="' + attributes[i] + '" ' );
239 					html.push( ' /></div>' );
240 					return html.join( '' );
241 				};
242 				CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
243 			},
244
245 			/**
246 			 * A text area with a label on the top or left.
247 			 * @constructor
248 			 * @extends CKEDITOR.ui.dialog.labeledElement
249 			 * @example
250 			 * @param {CKEDITOR.dialog} dialog
251 			 * Parent dialog object.
252 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
253 			 * The element definition. Accepted fields:
254 			 * <ul>
255 			 * 	<li><strong>rows</strong> (Optional) The number of rows displayed.
256 			 * 	Defaults to 5 if not defined.</li>
257 			 * 	<li><strong>cols</strong> (Optional) The number of cols displayed.
258 			 * 	Defaults to 20 if not defined. Usually overridden by skins.</li>
259 			 * 	<li><strong>default</strong> (Optional) The default value.</li>
260 			 * 	<li><strong>validate</strong> (Optional) The validation function. </li>
261 			 * </ul>
262 			 * @param {Array} htmlList
263 			 * List of HTML code to output to.
264 			 */
265 			textarea : function( dialog, elementDefinition, htmlList )
266 			{
267 				if ( arguments.length < 3 )
268 					return;
269
270 				initPrivateObject.call( this, elementDefinition );
271 				var me = this,
272 					domId = this._.inputId = CKEDITOR.tools.getNextNumber() + '_textarea',
273 					attributes = {};
274
275 				if ( elementDefinition.validate )
276 					this.validate = elementDefinition.validate;
277
278 				// Generates the essential attributes for the textarea tag.
279 				attributes.rows = elementDefinition.rows || 5;
280 				attributes.cols = elementDefinition.cols || 20;
281
282 				/** @ignore */
283 				var innerHTML = function()
284 				{
285 					var html = [ '<div class="cke_dialog_ui_input_textarea"><textarea class="cke_dialog_ui_input_textarea" id="', domId, '" ' ];
286 					for ( var i in attributes )
287 						html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" ' );
288 					html.push( '>', CKEDITOR.tools.htmlEncode( me._['default'] ), '</textarea></div>' );
289 					return html.join( '' );
290 				};
291 				CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
292 			},
293
294 			/**
295 			 * A single checkbox with a label on the right.
296 			 * @constructor
297 			 * @extends CKEDITOR.ui.dialog.uiElement
298 			 * @example
299 			 * @param {CKEDITOR.dialog} dialog
300 			 * Parent dialog object.
301 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
302 			 * The element definition. Accepted fields:
303 			 * <ul>
304 			 * 	<li><strong>checked</strong> (Optional) Whether the checkbox is checked
305 			 * 	on instantiation. Defaults to false.</li>
306 			 * 	<li><strong>validate</strong> (Optional) The validation function.</li>
307 			 * 	<li><strong>label</strong> (Optional) The checkbox label.</li>
308 			 * </ul>
309 			 * @param {Array} htmlList
310 			 * List of HTML code to output to.
311 			 */
312 			checkbox : function( dialog, elementDefinition, htmlList )
313 			{
314 				if ( arguments.length < 3 )
315 					return;
316
317 				var _ = initPrivateObject.call( this, elementDefinition, { 'default' : !!elementDefinition[ 'default' ] } );
318
319 				if ( elementDefinition.validate )
320 					this.validate = elementDefinition.validate;
321
322 				/** @ignore */
323 				var innerHTML = function()
324 				{
325 					var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition,
326 							{
327 								id : elementDefinition.id ? elementDefinition.id + '_checkbox' : CKEDITOR.tools.getNextNumber() + '_checkbox'
328 							}, true ),
329 						html = [],
330 						attributes = { 'class' : 'cke_dialog_ui_checkbox_input', type : 'checkbox' };
331 					cleanInnerDefinition( myDefinition );
332 					if ( elementDefinition[ 'default' ] )
333 						attributes.checked = 'checked';
334 					_.checkbox = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'input', null, attributes );
335 					html.push( ' ', CKEDITOR.tools.htmlEncode( elementDefinition.label ) );
336 					return html.join( '' );
337 				};
338
339 				CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'label', null, null, innerHTML );
340 			},
341
342 			/**
343 			 * A group of radio buttons.
344 			 * @constructor
345 			 * @example
346 			 * @extends CKEDITOR.ui.dialog.labeledElement
347 			 * @param {CKEDITOR.dialog} dialog
348 			 * Parent dialog object.
349 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
350 			 * The element definition. Accepted fields:
351 			 * <ul>
352 			 * 	<li><strong>default</strong> (Required) The default value.</li>
353 			 * 	<li><strong>validate</strong> (Optional) The validation function.</li>
354 			 * 	<li><strong>items</strong> (Required) An array of options. Each option
355 			 * 	is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value'
356 			 * 	is missing, then the value would be assumed to be the same as the
357 			 * 	description.</li>
358 			 * </ul>
359 			 * @param {Array} htmlList
360 			 * List of HTML code to output to.
361 			 */
362 			radio : function( dialog, elementDefinition, htmlList )
363 			{
364 				if ( arguments.length < 3)
365 					return;
366
367 				initPrivateObject.call( this, elementDefinition );
368 				if ( !this._['default'] )
369 					this._['default'] = this._.initValue = elementDefinition.items[0][1];
370 				if ( elementDefinition.validate )
371 					this.validate = elementDefinition.valdiate;
372 				var children = [], me = this;
373
374 				/** @ignore */
375 				var innerHTML = function()
376 				{
377 					var inputHtmlList = [], html = [],
378 						commonAttributes = { 'class' : 'cke_dialog_ui_radio_item' },
379 						commonName = elementDefinition.id ? elementDefinition.id + '_radio' : CKEDITOR.tools.getNextNumber() + '_radio';
380 					for ( var i = 0 ; i < elementDefinition.items.length ; i++ )
381 					{
382 						var item = elementDefinition.items[i],
383 							title = item[2] !== undefined ? item[2] : item[0],
384 							value = item[1] !== undefined ? item[1] : item[0],
385 							inputDefinition = CKEDITOR.tools.extend( {}, elementDefinition,
386 									{
387 										id : CKEDITOR.tools.getNextNumber() + '_radio_input',
388 										title : null,
389 										type : null
390 									}, true ),
391 							labelDefinition = CKEDITOR.tools.extend( {}, inputDefinition,
392 									{
393 										id : null,
394 										title : title
395 									}, true ),
396 							inputHtml = [],
397 							inputAttributes =
398 							{
399 								type : 'radio',
400 								'class' : 'cke_dialog_ui_radio_input',
401 								name : commonName,
402 								value : value
403 							};
404 						if ( me._['default'] == value )
405 							inputAttributes.checked = 'checked';
406 						cleanInnerDefinition( inputDefinition );
407 						cleanInnerDefinition( labelDefinition );
408 						children.push( new CKEDITOR.ui.dialog.uiElement( dialog, inputDefinition, inputHtml, 'input', null, inputAttributes ) );
409 						new CKEDITOR.ui.dialog.uiElement( dialog, labelDefinition, inputHtmlList, 'label', null, null,
410 							   inputHtml.join( '' ) + ' ' + item[0] );
411 					}
412 					new CKEDITOR.ui.dialog.hbox( dialog, [], inputHtmlList, html );
413 					return html.join( '' );
414 				};
415
416 				CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
417 				this._.children = children;
418 			},
419
420 			/**
421 			 * A button with a label inside.
422 			 * @constructor
423 			 * @example
424 			 * @extends CKEDITOR.ui.dialog.uiElement
425 			 * @param {CKEDITOR.dialog} dialog
426 			 * Parent dialog object.
427 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
428 			 * The element definition. Accepted fields:
429 			 * <ul>
430 			 * 	<li><strong>label</strong> (Required) The button label.</li>
431 			 * 	<li><strong>disabled</strong> (Optional) Set to true if you want the
432 			 * 	button to appear in disabled state.</li>
433 			 * </ul>
434 			 * @param {Array} htmlList
435 			 * List of HTML code to output to.
436 			 */
437 			button : function( dialog, elementDefinition, htmlList )
438 			{
439 				if ( !arguments.length )
440 					return;
441
442 				if ( typeof elementDefinition == 'function' )
443 					elementDefinition = elementDefinition( dialog.getParentEditor() );
444
445 				initPrivateObject.call( this, elementDefinition, { disabled : elementDefinition.disabled || false } );
446
447 				// Add OnClick event to this input.
448 				CKEDITOR.event.implementOn( this );
449
450 				var me = this;
451
452 				// Register an event handler for processing button clicks.
453 				dialog.on( 'load', function( eventInfo )
454 					{
455 						var element = this.getElement();
456
457 						(function()
458 						{
459 							element.on( 'click', function( evt )
460 								{
461 									me.fire( 'click', { dialog : me.getDialog() } );
462 									evt.data.preventDefault();
463 								} );
464 						})();
465
466 						element.unselectable();
467 					}, this );
468
469 				var outerDefinition = CKEDITOR.tools.extend( {}, elementDefinition );
470 				delete outerDefinition.style;
471
472 				CKEDITOR.ui.dialog.uiElement.call(
473 					this,
474 					dialog,
475 					outerDefinition,
476 					htmlList,
477 					'a',
478 					null,
479 					{
480 						style : elementDefinition.style,
481 						href : 'javascript:void(0)',
482 						title : elementDefinition.label,
483 						hidefocus : 'true',
484 						'class' : elementDefinition['class']
485 					},
486 					'<span class="cke_dialog_ui_button">' +
487 						CKEDITOR.tools.htmlEncode( elementDefinition.label ) +
488 					'</span>' );
489 			},
490
491 			/**
492 			 * A select box.
493 			 * @extends CKEDITOR.ui.dialog.uiElement
494 			 * @example
495 			 * @constructor
496 			 * @param {CKEDITOR.dialog} dialog
497 			 * Parent dialog object.
498 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
499 			 * The element definition. Accepted fields:
500 			 * <ul>
501 			 * 	<li><strong>default</strong> (Required) The default value.</li>
502 			 * 	<li><strong>validate</strong> (Optional) The validation function.</li>
503 			 * 	<li><strong>items</strong> (Required) An array of options. Each option
504 			 * 	is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value'
505 			 * 	is missing, then the value would be assumed to be the same as the
506 			 * 	description.</li>
507 			 * 	<li><strong>multiple</strong> (Optional) Set this to true if you'd like
508 			 * 	to have a multiple-choice select box.</li>
509 			 * 	<li><strong>size</strong> (Optional) The number of items to display in
510 			 * 	the select box.</li>
511 			 * </ul>
512 			 * @param {Array} htmlList
513 			 * List of HTML code to output to.
514 			 */
515 			select : function( dialog, elementDefinition, htmlList )
516 			{
517 				if ( arguments.length < 3 )
518 					return;
519
520 				var _ = initPrivateObject.call( this, elementDefinition );
521
522 				if ( elementDefinition.validate )
523 					this.validate = elementDefinition.validate;
524
525 				/** @ignore */
526 				var innerHTML = function()
527 				{
528 					var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition,
529 							{
530 								id : elementDefinition.id ? elementDefinition.id + '_select' : CKEDITOR.tools.getNextNumber() + '_select'
531 							}, true ),
532 						html = [],
533 						innerHTML = [],
534 						attributes = { 'class' : 'cke_dialog_ui_input_select' };
535
536 					// Add multiple and size attributes from element definition.
537 					if ( elementDefinition.size != undefined )
538 						attributes.size = elementDefinition.size;
539 					if ( elementDefinition.multiple != undefined )
540 						attributes.multiple = elementDefinition.multiple;
541
542 					cleanInnerDefinition( myDefinition );
543 					for ( var i = 0, item ; i < elementDefinition.items.length && ( item = elementDefinition.items[i] ) ; i++ )
544 					{
545 						innerHTML.push( '<option value="',
546 							CKEDITOR.tools.htmlEncode( item[1] !== undefined ? item[1] : item[0] ), '" /> ',
547 							CKEDITOR.tools.htmlEncode( item[0] ) );
548 					}
549
550 					_.select = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'select', null, attributes, innerHTML.join( '' ) );
551 					return html.join( '' );
552 				};
553
554 				CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
555 			},
556
557 			/**
558 			 * A file upload input.
559 			 * @extends CKEDITOR.ui.dialog.labeledElement
560 			 * @example
561 			 * @constructor
562 			 * @param {CKEDITOR.dialog} dialog
563 			 * Parent dialog object.
564 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
565 			 * The element definition. Accepted fields:
566 			 * <ul>
567 			 * 	<li><strong>validate</strong> (Optional) The validation function.</li>
568 			 * </ul>
569 			 * @param {Array} htmlList
570 			 * List of HTML code to output to.
571 			 */
572 			file : function( dialog, elementDefinition, htmlList )
573 			{
574 				if ( arguments.length < 3 )
575 					return;
576
577 				if ( elementDefinition['default'] === undefined )
578 					elementDefinition['default'] = '';
579
580 				var _ = CKEDITOR.tools.extend( initPrivateObject.call( this, elementDefinition ), { definition : elementDefinition, buttons : [] } );
581
582 				if ( elementDefinition.validate )
583 					this.validate = elementDefinition.validate;
584
585 				/** @ignore */
586 				var innerHTML = function()
587 				{
588 					_.frameId = CKEDITOR.tools.getNextNumber() + '_fileInput';
589
590 					// Support for custom document.domain in IE.
591 					var isCustomDomain = CKEDITOR.env.ie && document.domain != window.location.hostname;
592
593 					var html = [
594 						'<iframe' +
595 							' frameborder="0"' +
596 							' allowtransparency="0"' +
597 							' class="cke_dialog_ui_input_file"' +
598 							' id="', _.frameId, '"' +
599 							' src="javascript:void(' ];
600
601 					html.push(
602 							isCustomDomain ?
603 								'(function(){' +
604 									'document.open();' +
605 									'document.domain=\'' + document.domain + '\';' +
606 									'document.close();' +
607 								'})()'
608 							:
609 								'0' );
610
611 					html.push(
612 							')">' +
613 						'</iframe>' );
614
615 					return html.join( '' );
616 				};
617
618 				// IE BUG: Parent container does not resize to contain the iframe automatically.
619 				dialog.on( 'load', function()
620 					{
621 						var iframe = CKEDITOR.document.getById( _.frameId ),
622 							contentDiv = iframe.getParent();
623 						contentDiv.addClass( 'cke_dialog_ui_input_file' );
624 					} );
625
626 				CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
627 			},
628
629 			/**
630 			 * A button for submitting the file in a file upload input.
631 			 * @extends CKEDITOR.ui.dialog.button
632 			 * @example
633 			 * @constructor
634 			 * @param {CKEDITOR.dialog} dialog
635 			 * Parent dialog object.
636 			 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
637 			 * The element definition. Accepted fields:
638 			 * <ul>
639 			 * 	<li><strong>for</strong> (Required) The file input's page and element Id
640 			 * 	to associate to, in a 2-item array format: [ 'page_id', 'element_id' ].
641 			 * 	</li>
642 			 * 	<li><strong>validate</strong> (Optional) The validation function.</li>
643 			 * </ul>
644 			 * @param {Array} htmlList
645 			 * List of HTML code to output to.
646 			 */
647 			fileButton : function( dialog, elementDefinition, htmlList )
648 			{
649 				if ( arguments.length < 3 )
650 					return;
651
652 				var _ = initPrivateObject.call( this, elementDefinition ),
653 					me = this;
654
655 				if ( elementDefinition.validate )
656 					this.validate = elementDefinition.validate;
657
658 				var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition );
659 				var onClick = myDefinition.onClick;
660 				myDefinition.className = ( myDefinition.className ? myDefinition.className + ' ' : '' ) + 'cke_dialog_ui_button';
661 				myDefinition.onClick = function( evt )
662 				{
663 					var target = elementDefinition[ 'for' ];		// [ pageId, elementId ]
664 					if ( !onClick || onClick.call( this, evt ) !== false )
665 					{
666 						dialog.getContentElement( target[0], target[1] ).submit();
667 						this.disable();
668 					}
669 				};
670
671 				dialog.on( 'load', function()
672 						{
673 							dialog.getContentElement( elementDefinition[ 'for' ][0], elementDefinition[ 'for' ][1] )._.buttons.push( me );
674 						} );
675
676 				CKEDITOR.ui.dialog.button.call( this, dialog, myDefinition, htmlList );
677 			},
678
679 			html : (function()
680 			{
681 				var myHtmlRe = /^\s*<[\w:]+\s+([^>]*)?>/,
682 					theirHtmlRe = /^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,
683 					emptyTagRe = /\/$/;
684 				/**
685 				 * A dialog element made from raw HTML code.
686 				 * @extends CKEDITOR.ui.dialog.uiElement
687 				 * @name CKEDITOR.ui.dialog.html
688 				 * @param {CKEDITOR.dialog} dialog Parent dialog object.
689 				 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition Element definition.
690 				 * Accepted fields:
691 				 * <ul>
692 				 * 	<li><strong>html</strong> (Required) HTML code of this element.</li>
693 				 * </ul>
694 				 * @param {Array} htmlList List of HTML code to be added to the dialog's content area.
695 				 * @example
696 				 * @constructor
697 				 */
698 				return function( dialog, elementDefinition, htmlList )
699 				{
700 					if ( arguments.length < 3 )
701 						return;
702
703 					var myHtmlList = [],
704 						myHtml,
705 						theirHtml = elementDefinition.html,
706 						myMatch, theirMatch;
707
708 					// If the HTML input doesn't contain any tags at the beginning, add a <span> tag around it.
709 					if ( theirHtml.charAt( 0 ) != '<' )
710 						theirHtml = '<span>' + theirHtml + '</span>';
711
712 					// Look for focus function in definition.
713 					if ( elementDefinition.focus )
714 					{
715 						var oldFocus = this.focus;
716 						this.focus = function()
717 						{
718 							oldFocus.call( this );
719 							elementDefinition.focus.call( this );
720 							this.fire( 'focus' );
721 						};
722 						if ( elementDefinition.isFocusable )
723 						{
724 							var oldIsFocusable = this.isFocusable;
725 							this.isFocusable = oldIsFocusable;
726 						}
727 						this.keyboardFocusable = true;
728 					}
729
730 					CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, myHtmlList, 'span', null, null, '' );
731
732 					// Append the attributes created by the uiElement call to the real HTML.
733 					myHtml = myHtmlList.join( '' );
734 					myMatch = myHtml.match( myHtmlRe );
735 					theirMatch = theirHtml.match( theirHtmlRe ) || [ '', '', '' ];
736
737 					if ( emptyTagRe.test( theirMatch[1] ) )
738 					{
739 						theirMatch[1] = theirMatch[1].slice( 0, -1 );
740 						theirMatch[2] = '/' + theirMatch[2];
741 					}
742
743 					htmlList.push( [ theirMatch[1], ' ', myMatch[1] || '', theirMatch[2] ].join( '' ) );
744 				};
745 			})()
746 		}, true );
747
748 	CKEDITOR.ui.dialog.html.prototype = new CKEDITOR.ui.dialog.uiElement;
749
750 	CKEDITOR.ui.dialog.labeledElement.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
751 			/** @lends CKEDITOR.ui.dialog.labeledElement.prototype */
752 			{
753 				/**
754 				 * Sets the label text of the element.
755 				 * @param {String} label The new label text.
756 				 * @returns {CKEDITOR.ui.dialog.labeledElement} The current labeled element.
757 				 * @example
758 				 */
759 				setLabel : function( label )
760 				{
761 					var node = CKEDITOR.document.getById( this._.labelId );
762 					if ( node.getChildCount() < 1 )
763 						( new CKEDITOR.dom.text( label, CKEDITOR.document ) ).appendTo( node );
764 					else
765 						node.getChild( 0 ).$.nodeValue = label;
766 					return this;
767 				},
768
769 				/**
770 				 * Retrieves the current label text of the elment.
771 				 * @returns {String} The current label text.
772 				 * @example
773 				 */
774 				getLabel : function()
775 				{
776 					var node = CKEDITOR.document.getById( this._.labelId );
777 					if ( !node || node.getChildCount() < 1 )
778 						return '';
779 					else
780 						return node.getChild( 0 ).getText();
781 				},
782
783 				/**
784 				 * Defines the onChange event for UI element definitions.
785 				 * @field
786 				 * @type Object
787 				 * @example
788 				 */
789 				eventProcessors : commonEventProcessors
790 			}, true );
791
792 	CKEDITOR.ui.dialog.button.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
793 			/** @lends CKEDITOR.ui.dialog.button.prototype */
794 			{
795 				/**
796 				 * Simulates a click to the button.
797 				 * @example
798 				 * @returns {Object} Return value of the 'click' event.
799 				 */
800 				click : function()
801 				{
802 					if ( !this._.disabled )
803 						return this.fire( 'click', { dialog : this._.dialog } );
804 					this.getElement().$.blur();
805 					return false;
806 				},
807
808 				/**
809 				 * Enables the button.
810 				 * @example
811 				 */
812 				enable : function()
813 				{
814 					this._.disabled = false;
815 					this.getElement().removeClass( 'disabled' );
816 				},
817
818 				/**
819 				 * Disables the button.
820 				 * @example
821 				 */
822 				disable : function()
823 				{
824 					this._.disabled = true;
825 					this.getElement().addClass( 'disabled' );
826 				},
827
828 				isVisible : function()
829 				{
830 					return !!this.getElement().$.firstChild.offsetHeight;
831 				},
832
833 				isEnabled : function()
834 				{
835 					return !this._.disabled;
836 				},
837
838 				/**
839 				 * Defines the onChange event and onClick for button element definitions.
840 				 * @field
841 				 * @type Object
842 				 * @example
843 				 */
844 				eventProcessors : CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
845 					{
846 						/** @ignore */
847 						onClick : function( dialog, func )
848 						{
849 							this.on( 'click', func );
850 						}
851 					}, true ),
852
853 				/**
854 				 * Handler for the element's access key up event. Simulates a click to
855 				 * the button.
856 				 * @example
857 				 */
858 				accessKeyUp : function()
859 				{
860 					this.click();
861 				},
862
863 				/**
864 				 * Handler for the element's access key down event. Simulates a mouse
865 				 * down to the button.
866 				 * @example
867 				 */
868 				accessKeyDown : function()
869 				{
870 					this.focus();
871 				},
872
873 				keyboardFocusable : true
874 			}, true );
875
876 	CKEDITOR.ui.dialog.textInput.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement,
877 			/** @lends CKEDITOR.ui.dialog.textInput.prototype */
878 			{
879 				/**
880 				 * Gets the text input DOM element under this UI object.
881 				 * @example
882 				 * @returns {CKEDITOR.dom.element} The DOM element of the text input.
883 				 */
884 				getInputElement : function()
885 				{
886 					return CKEDITOR.document.getById( this._.inputId );
887 				},
888
889 				/**
890 				 * Puts focus into the text input.
891 				 * @example
892 				 */
893 				focus : function()
894 				{
895 					var me = this.selectParentTab();
896
897 					// GECKO BUG: setTimeout() is needed to workaround invisible selections.
898 					setTimeout( function(){ me.getInputElement().$.focus(); }, 0 );
899 				},
900
901 				/**
902 				 * Selects all the text in the text input.
903 				 * @example
904 				 */
905 				select : function()
906 				{
907 					var me = this.selectParentTab();
908
909 					// GECKO BUG: setTimeout() is needed to workaround invisible selections.
910 					setTimeout( function(){ var e = me.getInputElement().$; e.focus(); e.select(); }, 0 );
911 				},
912
913 				/**
914 				 * Handler for the text input's access key up event. Makes a select()
915 				 * call to the text input.
916 				 * @example
917 				 */
918 				accessKeyUp : function()
919 				{
920 					this.select();
921 				},
922
923 				/**
924 				 * Sets the value of this text input object.
925 				 * @param {Object} value The new value.
926 				 * @returns {CKEDITOR.ui.dialog.textInput} The current UI element.
927 				 * @example
928 				 * uiElement.setValue( 'Blamo' );
929 				 */
930 				setValue : function( value )
931 				{
932 					value = value || '';
933 					return CKEDITOR.ui.dialog.uiElement.prototype.setValue.call( this, value );
934 				},
935
936 				keyboardFocusable : true
937 			}, commonPrototype, true );
938
939 	CKEDITOR.ui.dialog.textarea.prototype = new CKEDITOR.ui.dialog.textInput();
940
941 	CKEDITOR.ui.dialog.select.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement,
942 			/** @lends CKEDITOR.ui.dialog.select.prototype */
943 			{
944 				/**
945 				 * Gets the DOM element of the select box.
946 				 * @returns {CKEDITOR.dom.element} The <select> element of this UI
947 				 * element.
948 				 * @example
949 				 */
950 				getInputElement : function()
951 				{
952 					return this._.select.getElement();
953 				},
954
955 				/**
956 				 * Adds an option to the select box.
957 				 * @param {String} label Option label.
958 				 * @param {String} value (Optional) Option value, if not defined it'll be
959 				 * assumed to be the same as the label.
960 				 * @param {Number} index (Optional) Position of the option to be inserted
961 				 * to. If not defined the new option will be inserted to the end of list.
962 				 * @example
963 				 * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
964 				 */
965 				add : function( label, value, index )
966 				{
967 					var option = new CKEDITOR.dom.element( 'option', this.getDialog().getParentEditor().document ),
968 						selectElement = this.getInputElement().$;
969 					option.$.text = label;
970 					option.$.value = ( value === undefined || value === null ) ? label : value;
971 					if ( index === undefined || index === null )
972 					{
973 						if ( CKEDITOR.env.ie )
974 							selectElement.add( option.$ );
975 						else
976 							selectElement.add( option.$, null );
977 					}
978 					else
979 						selectElement.add( option.$, index );
980 					return this;
981 				},
982
983 				/**
984 				 * Removes an option from the selection list.
985 				 * @param {Number} index Index of the option to be removed.
986 				 * @example
987 				 * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
988 				 */
989 				remove : function( index )
990 				{
991 					var selectElement = this.getInputElement().$;
992 					selectElement.remove( index );
993 					return this;
994 				},
995
996 				/**
997 				 * Clears all options out of the selection list.
998 				 * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
999 				 */
1000 				clear : function()
1001 				{
1002 					var selectElement = this.getInputElement().$;
1003 					while ( selectElement.length > 0 )
1004 						selectElement.remove( 0 );
1005 					return this;
1006 				},
1007
1008 				keyboardFocusable : true
1009 			}, commonPrototype, true );
1010
1011 	CKEDITOR.ui.dialog.checkbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
1012 			/** @lends CKEDITOR.ui.dialog.checkbox.prototype */
1013 			{
1014 				/**
1015 				 * Gets the checkbox DOM element.
1016 				 * @example
1017 				 * @returns {CKEDITOR.dom.element} The DOM element of the checkbox.
1018 				 */
1019 				getInputElement : function()
1020 				{
1021 					return this._.checkbox.getElement();
1022 				},
1023
1024 				/**
1025 				 * Sets the state of the checkbox.
1026 				 * @example
1027 				 * @param {Boolean} true to tick the checkbox, false to untick it.
1028 				 */
1029 				setValue : function( checked )
1030 				{
1031 					this.getInputElement().$.checked = checked;
1032 					this.fire( 'change', { value : checked } );
1033 				},
1034
1035 				/**
1036 				 * Gets the state of the checkbox.
1037 				 * @example
1038 				 * @returns {Boolean} true means the checkbox is ticked, false means it's not ticked.
1039 				 */
1040 				getValue : function()
1041 				{
1042 					return this.getInputElement().$.checked;
1043 				},
1044
1045 				/**
1046 				 * Handler for the access key up event. Toggles the checkbox.
1047 				 * @example
1048 				 */
1049 				accessKeyUp : function()
1050 				{
1051 					this.setValue( !this.getValue() );
1052 				},
1053
1054 				/**
1055 				 * Defines the onChange event for UI element definitions.
1056 				 * @field
1057 				 * @type Object
1058 				 * @example
1059 				 */
1060 				eventProcessors :
1061 				{
1062 					onChange : function( dialog, func )
1063 					{
1064 						if ( !CKEDITOR.env.ie )
1065 							return commonEventProcessors.onChange.apply( this, arguments );
1066 						else
1067 						{
1068 							dialog.on( 'load', function()
1069 								{
1070 									var element = this._.checkbox.getElement();
1071 									element.on( 'propertychange', function( evt )
1072 										{
1073 											evt = evt.data.$;
1074 											if ( evt.propertyName == 'checked' )
1075 												this.fire( 'change', { value : element.$.checked } );
1076 										}, this );
1077 								}, this );
1078 							this.on( 'change', func );
1079 						}
1080 						return null;
1081 					}
1082 				},
1083
1084 				keyboardFocusable : true
1085 			}, commonPrototype, true );
1086
1087 	CKEDITOR.ui.dialog.radio.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
1088 			/** @lends CKEDITOR.ui.dialog.radio.prototype */
1089 			{
1090 				/**
1091 				 * Checks one of the radio buttons in this button group.
1092 				 * @example
1093 				 * @param {String} value The value of the button to be chcked.
1094 				 */
1095 				setValue : function( value )
1096 				{
1097 					var children = this._.children,
1098 						item;
1099 					for ( var i = 0 ; ( i < children.length ) && ( item = children[i] ) ; i++ )
1100 						item.getElement().$.checked = ( item.getValue() == value );
1101 					this.fire( 'change', { value : value } );
1102 				},
1103
1104 				/**
1105 				 * Gets the value of the currently checked radio button.
1106 				 * @example
1107 				 * @returns {String} The currently checked button's value.
1108 				 */
1109 				getValue : function()
1110 				{
1111 					var children = this._.children;
1112 					for ( var i = 0 ; i < children.length ; i++ )
1113 					{
1114 						if ( children[i].getElement().$.checked )
1115 							return children[i].getValue();
1116 					}
1117 					return null;
1118 				},
1119
1120 				/**
1121 				 * Handler for the access key up event. Focuses the currently
1122 				 * selected radio button, or the first radio button if none is
1123 				 * selected.
1124 				 * @example
1125 				 */
1126 				accessKeyUp : function()
1127 				{
1128 					var children = this._.children, i;
1129 					for ( i = 0 ; i < children.length ; i++ )
1130 					{
1131 						if ( children[i].getElement().$.checked )
1132 						{
1133 							children[i].getElement().focus();
1134 							return;
1135 						}
1136 					}
1137 					children[0].getElement().focus();
1138 				},
1139
1140 				/**
1141 				 * Defines the onChange event for UI element definitions.
1142 				 * @field
1143 				 * @type Object
1144 				 * @example
1145 				 */
1146 				eventProcessors :
1147 				{
1148 					onChange : function( dialog, func )
1149 					{
1150 						if ( !CKEDITOR.env.ie )
1151 							return commonEventProcessors.onChange.apply( this, arguments );
1152 						else
1153 						{
1154 							dialog.on( 'load', function()
1155 								{
1156 									var children = this._.children, me = this;
1157 									for ( var i = 0 ; i < children.length ; i++ )
1158 									{
1159 										var element = children[i].getElement();
1160 										element.on( 'propertychange', function( evt )
1161 											{
1162 												evt = evt.data.$;
1163 												if ( evt.propertyName == 'checked' && this.$.checked )
1164 													me.fire( 'change', { value : this.getAttribute( 'value' ) } );
1165 											} );
1166 									}
1167 								}, this );
1168 							this.on( 'change', func );
1169 						}
1170 						return null;
1171 					}
1172 				},
1173
1174 				keyboardFocusable : true
1175 			}, commonPrototype, true );
1176
1177 	CKEDITOR.ui.dialog.file.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement,
1178 			commonPrototype,
1179 			/** @lends CKEDITOR.ui.dialog.file.prototype */
1180 			{
1181 				/**
1182 				 * Gets the <input> element of this file input.
1183 				 * @returns {CKEDITOR.dom.element} The file input element.
1184 				 * @example
1185 				 */
1186 				getInputElement : function()
1187 				{
1188 					var frameDocument = CKEDITOR.document.getById( this._.frameId ).getFrameDocument();
1189 					return frameDocument.$.forms.length > 0 ?
1190 						new CKEDITOR.dom.element( frameDocument.$.forms[0].elements[0] ) :
1191 						this.getElement();
1192 				},
1193
1194 				/**
1195 				 * Uploads the file in the file input.
1196 				 * @returns {CKEDITOR.ui.dialog.file} This object.
1197 				 * @example
1198 				 */
1199 				submit : function()
1200 				{
1201 					this.getInputElement().getParent().$.submit();
1202 					return this;
1203 				},
1204
1205 				/**
1206 				 * Get the action assigned to the form.
1207 				 * @returns {String} The value of the action.
1208 				 * @example
1209 				 */
1210 				getAction : function( action )
1211 				{
1212 					return this.getInputElement().getParent().$.action;
1213 				},
1214
1215 				/**
1216 				 * Redraws the file input and resets the file path in the file input.
1217 				 * The redraw logic is necessary because non-IE browsers tend to clear
1218 				 * the <iframe> containing the file input after closing the dialog.
1219 				 * @example
1220 				 */
1221 				reset : function()
1222 				{
1223 					var frameElement = CKEDITOR.document.getById( this._.frameId ),
1224 						frameDocument = frameElement.getFrameDocument(),
1225 						elementDefinition = this._.definition,
1226 						buttons = this._.buttons;
1227
1228 					function generateFormField()
1229 					{
1230 						frameDocument.$.open();
1231
1232 						// Support for custom document.domain in IE.
1233 						if ( CKEDITOR.env.isCustomDomain() )
1234 							frameDocument.$.domain = document.domain;
1235
1236 						frameDocument.$.write( [ '<html><head><title></title></head><body style="margin: 0; overflow: hidden; background: transparent;">',
1237 								'<form enctype="multipart/form-data" method="POST" action="',
1238 								CKEDITOR.tools.htmlEncode( elementDefinition.action ),
1239 								'">',
1240 								'<input type="file" name="',
1241 								CKEDITOR.tools.htmlEncode( elementDefinition.id || 'cke_upload' ),
1242 								'" size="',
1243 								CKEDITOR.tools.htmlEncode( elementDefinition.size || '' ),
1244 								'" />',
1245 								'</form>',
1246 								'</body></html>' ].join( '' ) );
1247
1248 						frameDocument.$.close();
1249
1250 						for ( var i = 0 ; i < buttons.length ; i++ )
1251 							buttons[i].enable();
1252 					}
1253
1254 					// #3465: Wait for the browser to finish rendering the dialog first.
1255 					if ( CKEDITOR.env.gecko )
1256 						setTimeout( generateFormField, 500 );
1257 					else
1258 						generateFormField();
1259 				},
1260
1261 				getValue : function()
1262 				{
1263 					// The file path returned from the input tag is incomplete anyway, so it's
1264 					// safe to ignore it and prevent the confirmation dialog from appearing.
1265 					// (Part of #3465)
1266 					return '';
1267 				},
1268
1269 				/**
1270 				 * Defines the onChange event for UI element definitions.
1271 				 * @field
1272 				 * @type Object
1273 				 * @example
1274 				 */
1275 				eventProcessors : commonEventProcessors,
1276
1277 				keyboardFocusable : true
1278 			}, true );
1279
1280 	CKEDITOR.ui.dialog.fileButton.prototype = new CKEDITOR.ui.dialog.button;
1281
1282 	CKEDITOR.dialog.addUIElement( 'text', textBuilder );
1283 	CKEDITOR.dialog.addUIElement( 'password', textBuilder );
1284 	CKEDITOR.dialog.addUIElement( 'textarea', commonBuilder );
1285 	CKEDITOR.dialog.addUIElement( 'checkbox', commonBuilder );
1286 	CKEDITOR.dialog.addUIElement( 'radio', commonBuilder );
1287 	CKEDITOR.dialog.addUIElement( 'button', commonBuilder );
1288 	CKEDITOR.dialog.addUIElement( 'select', commonBuilder );
1289 	CKEDITOR.dialog.addUIElement( 'file', commonBuilder );
1290 	CKEDITOR.dialog.addUIElement( 'fileButton', commonBuilder );
1291 	CKEDITOR.dialog.addUIElement( 'html', commonBuilder );
1292 })();
1293