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