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