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