2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
7 * @fileOverview The floating dialog plugin.
11 * No resize for this dialog.
14 CKEDITOR.DIALOG_RESIZE_NONE = 0;
17 * Only allow horizontal resizing for this dialog, disable vertical resizing.
20 CKEDITOR.DIALOG_RESIZE_WIDTH = 1;
23 * Only allow vertical resizing for this dialog, disable horizontal resizing.
26 CKEDITOR.DIALOG_RESIZE_HEIGHT = 2;
29 * Allow the dialog to be resized in both directions.
32 CKEDITOR.DIALOG_RESIZE_BOTH = 3;
36 var cssLength = CKEDITOR.tools.cssLength;
37 function isTabVisible( tabId )
39 return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight;
42 function getPreviousVisibleTab()
44 var tabId = this._.currentTabId,
45 length = this._.tabIdList.length,
46 tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length;
48 for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- )
50 if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) )
51 return this._.tabIdList[ i % length ];
57 function getNextVisibleTab()
59 var tabId = this._.currentTabId,
60 length = this._.tabIdList.length,
61 tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId );
63 for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ )
65 if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) )
66 return this._.tabIdList[ i % length ];
73 function clearOrRecoverTextInputValue( container, isRecover )
75 var inputs = container.$.getElementsByTagName( 'input' );
76 for ( var i = 0, length = inputs.length; i < length ; i++ )
78 var item = new CKEDITOR.dom.element( inputs[ i ] );
80 if ( item.getAttribute( 'type' ).toLowerCase() == 'text' )
84 item.setAttribute( 'value', item.getCustomData( 'fake_value' ) || '' );
85 item.removeCustomData( 'fake_value' );
89 item.setCustomData( 'fake_value', item.getAttribute( 'value' ) );
90 item.setAttribute( 'value', '' );
96 // Handle dialog element validation state UI changes.
97 function handleFieldValidated( isValid, msg )
99 var input = this.getInputElement();
102 isValid ? input.removeAttribute( 'aria-invalid' )
103 : input.setAttribute( 'aria-invalid', true );
116 this.fire( 'validated', { valid : isValid, msg : msg } );
119 function resetField()
121 var input = this.getInputElement();
122 input && input.removeAttribute( 'aria-invalid' );
127 * This is the base class for runtime dialog objects. An instance of this
128 * class represents a single named dialog for a single editor instance.
129 * @param {Object} editor The editor which created the dialog.
130 * @param {String} dialogName The dialog's registered name.
133 * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' );
135 CKEDITOR.dialog = function( editor, dialogName )
137 // Load the dialog definition.
138 var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ],
139 defaultDefinition = CKEDITOR.tools.clone( defaultDialogDefinition ),
140 buttonsOrder = editor.config.dialog_buttonsOrder || 'OS',
141 dir = editor.lang.dir;
143 if ( ( buttonsOrder == 'OS' && CKEDITOR.env.mac ) || // The buttons in MacOS Apps are in reverse order (#4750)
144 ( buttonsOrder == 'rtl' && dir == 'ltr' ) ||
145 ( buttonsOrder == 'ltr' && dir == 'rtl' ) )
146 defaultDefinition.buttons.reverse();
149 // Completes the definition with the default values.
150 definition = CKEDITOR.tools.extend( definition( editor ), defaultDefinition );
152 // Clone a functionally independent copy for this dialog.
153 definition = CKEDITOR.tools.clone( definition );
155 // Create a complex definition object, extending it with the API
157 definition = new definitionObject( this, definition );
159 var doc = CKEDITOR.document;
161 var themeBuilt = editor.theme.buildDialog( editor );
163 // Initialize some basic parameters.
167 element : themeBuilt.element,
169 contentSize : { width : 0, height : 0 },
170 size : { width : 0, height : 0 },
175 // Initialize the tab and page map.
179 currentTabIndex : null,
184 // Initialize the tab order array for input widgets.
186 currentFocusIndex : 0,
190 this.parts = themeBuilt.parts;
192 CKEDITOR.tools.setTimeout( function()
194 editor.fire( 'ariaWidget', this.parts.contents );
198 // Set the startup styles for the dialog, avoiding it enlarging the
199 // page size on the dialog creation.
201 position : CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed',
203 visibility : 'hidden'
206 startStyles[ dir == 'rtl' ? 'right' : 'left' ] = 0;
207 this.parts.dialog.setStyles( startStyles );
210 // Call the CKEDITOR.event constructor to initialize this instance.
211 CKEDITOR.event.call( this );
213 // Fire the "dialogDefinition" event, making it possible to customize
214 // the dialog definition.
215 this.definition = definition = CKEDITOR.fire( 'dialogDefinition',
218 definition : definition
220 , editor ).definition;
222 var tabsToRemove = {};
223 // Cache tabs that should be removed.
224 if ( !( 'removeDialogTabs' in editor._ ) && editor.config.removeDialogTabs )
226 var removeContents = editor.config.removeDialogTabs.split( ';' );
228 for ( i = 0; i < removeContents.length; i++ )
230 var parts = removeContents[ i ].split( ':' );
231 if ( parts.length == 2 )
233 var removeDialogName = parts[ 0 ];
234 if ( !tabsToRemove[ removeDialogName ] )
235 tabsToRemove[ removeDialogName ] = [];
236 tabsToRemove[ removeDialogName ].push( parts[ 1 ] );
239 editor._.removeDialogTabs = tabsToRemove;
242 // Remove tabs of this dialog.
243 if ( editor._.removeDialogTabs && ( tabsToRemove = editor._.removeDialogTabs[ dialogName ] ) )
245 for ( i = 0; i < tabsToRemove.length; i++ )
246 definition.removeContents( tabsToRemove[ i ] );
249 // Initialize load, show, hide, ok and cancel events.
250 if ( definition.onLoad )
251 this.on( 'load', definition.onLoad );
253 if ( definition.onShow )
254 this.on( 'show', definition.onShow );
256 if ( definition.onHide )
257 this.on( 'hide', definition.onHide );
259 if ( definition.onOk )
261 this.on( 'ok', function( evt )
263 // Dialog confirm might probably introduce content changes (#5415).
264 editor.fire( 'saveSnapshot' );
265 setTimeout( function () { editor.fire( 'saveSnapshot' ); }, 0 );
266 if ( definition.onOk.call( this, evt ) === false )
267 evt.data.hide = false;
271 if ( definition.onCancel )
273 this.on( 'cancel', function( evt )
275 if ( definition.onCancel.call( this, evt ) === false )
276 evt.data.hide = false;
282 // Iterates over all items inside all content in the dialog, calling a
283 // function for each of them.
284 var iterContents = function( func )
286 var contents = me._.contents,
289 for ( var i in contents )
291 for ( var j in contents[i] )
293 stop = func.call( this, contents[i][j] );
300 this.on( 'ok', function( evt )
302 iterContents( function( item )
306 var retval = item.validate( this ),
307 invalid = typeof ( retval ) == 'string' || retval === false;
311 evt.data.hide = false;
315 handleFieldValidated.call( item, !invalid, typeof retval == 'string' ? retval : undefined );
321 this.on( 'cancel', function( evt )
323 iterContents( function( item )
325 if ( item.isChanged() )
327 if ( !confirm( editor.lang.common.confirmCancel ) )
328 evt.data.hide = false;
334 this.parts.close.on( 'click', function( evt )
336 if ( this.fire( 'cancel', { hide : true } ).hide !== false )
338 evt.data.preventDefault();
341 // Sort focus list according to tab order definitions.
342 function setupFocus()
344 var focusList = me._.focusList;
345 focusList.sort( function( a, b )
347 // Mimics browser tab order logics;
348 if ( a.tabIndex != b.tabIndex )
349 return b.tabIndex - a.tabIndex;
350 // Sort is not stable in some browsers,
351 // fall-back the comparator to 'focusIndex';
353 return a.focusIndex - b.focusIndex;
356 var size = focusList.length;
357 for ( var i = 0; i < size; i++ )
358 focusList[ i ].focusIndex = i;
361 function changeFocus( forward )
363 var focusList = me._.focusList,
364 offset = forward ? 1 : -1;
365 if ( focusList.length < 1 )
368 var current = me._.currentFocusIndex;
370 // Trigger the 'blur' event of any input element before anything,
371 // since certain UI updates may depend on it.
374 focusList[ current ].getInputElement().$.blur();
378 var startIndex = ( current + offset + focusList.length ) % focusList.length,
379 currentIndex = startIndex;
380 while ( !focusList[ currentIndex ].isFocusable() )
382 currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length;
383 if ( currentIndex == startIndex )
386 focusList[ currentIndex ].focus();
388 // Select whole field content.
389 if ( focusList[ currentIndex ].type == 'text' )
390 focusList[ currentIndex ].select();
393 this.changeFocus = changeFocus;
397 function focusKeydownHandler( evt )
399 // If I'm not the top dialog, ignore.
400 if ( me != CKEDITOR.dialog._.currentTop )
403 var keystroke = evt.data.getKeystroke(),
404 rtl = editor.lang.dir == 'rtl';
407 if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 )
409 var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 );
411 // Handling Tab and Shift-Tab.
412 if ( me._.tabBarMode )
415 var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me );
416 me.selectPage( nextId );
417 me._.tabs[ nextId ][ 0 ].focus();
421 // Change the focus of inputs.
422 changeFocus( !shiftPressed );
427 else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 )
429 // Alt-F10 puts focus into the current tab item in the tab bar.
430 me._.tabBarMode = true;
431 me._.tabs[ me._.currentTabId ][ 0 ].focus();
434 else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode )
436 // Arrow keys - used for changing tabs.
437 nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) );
438 me.selectPage( nextId );
439 me._.tabs[ nextId ][ 0 ].focus();
442 else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode )
444 this.selectPage( this._.currentTabId );
445 this._.tabBarMode = false;
446 this._.currentFocusIndex = -1;
454 evt.data.preventDefault();
458 function focusKeyPressHandler( evt )
460 processed && evt.data.preventDefault();
463 var dialogElement = this._.element;
464 // Add the dialog keyboard handlers.
465 this.on( 'show', function()
467 dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 );
468 // Some browsers instead, don't cancel key events in the keydown, but in the
469 // keypress. So we must do a longer trip in those cases. (#4531)
470 if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
471 dialogElement.on( 'keypress', focusKeyPressHandler, this );
474 this.on( 'hide', function()
476 dialogElement.removeListener( 'keydown', focusKeydownHandler );
477 if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
478 dialogElement.removeListener( 'keypress', focusKeyPressHandler );
480 // Reset fields state when closing dialog.
481 iterContents( function( item ) { resetField.apply( item ); } );
483 this.on( 'iframeAdded', function( evt )
485 var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document );
486 doc.on( 'keydown', focusKeydownHandler, this, null, 0 );
489 // Auto-focus logic in dialog.
490 this.on( 'show', function()
492 // Setup tabIndex on showing the dialog instead of on loading
493 // to allow dynamic tab order happen in dialog definition.
496 if ( editor.config.dialog_startupFocusTab
497 && me._.pageCount > 1 )
499 me._.tabBarMode = true;
500 me._.tabs[ me._.currentTabId ][ 0 ].focus();
502 else if ( !this._.hasFocus )
504 this._.currentFocusIndex = -1;
506 // Decide where to put the initial focus.
507 if ( definition.onFocus )
509 var initialFocus = definition.onFocus.call( this );
510 // Focus the field that the user specified.
511 initialFocus && initialFocus.focus();
513 // Focus the first field in layout order.
518 * IE BUG: If the initial focus went into a non-text element (e.g. button),
519 * then IE would still leave the caret inside the editing area.
521 if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie )
523 var $selection = editor.document.$.selection,
524 $range = $selection.createRange();
528 if ( $range.parentElement && $range.parentElement().ownerDocument == editor.document.$
529 || $range.item && $range.item( 0 ).ownerDocument == editor.document.$ )
531 var $myRange = document.body.createTextRange();
532 $myRange.moveToElementText( this.getElement().getFirst().$ );
533 $myRange.collapse( true );
539 }, this, null, 0xffffffff );
541 // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661).
542 // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken.
543 if ( CKEDITOR.env.ie6Compat )
545 this.on( 'load', function( evt )
547 var outer = this.getElement(),
548 inner = outer.getFirst();
550 inner.appendTo( outer );
554 initDragAndDrop( this );
555 initResizeHandles( this );
558 ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title );
560 // Insert the tabs and contents.
561 for ( var i = 0 ; i < definition.contents.length ; i++ )
563 var page = definition.contents[i];
564 page && this.addPage( page );
567 this.parts[ 'tabs' ].on( 'click', function( evt )
569 var target = evt.data.getTarget();
570 // If we aren't inside a tab, bail out.
571 if ( target.hasClass( 'cke_dialog_tab' ) )
573 // Get the ID of the tab, without the 'cke_' prefix and the unique number suffix.
574 var id = target.$.id;
575 this.selectPage( id.substring( 4, id.lastIndexOf( '_' ) ) );
577 if ( this._.tabBarMode )
579 this._.tabBarMode = false;
580 this._.currentFocusIndex = -1;
583 evt.data.preventDefault();
588 var buttonsHtml = [],
589 buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this,
592 className : 'cke_dialog_footer_buttons',
594 children : definition.buttons
595 }, buttonsHtml ).getChild();
596 this.parts.footer.setHtml( buttonsHtml.join( '' ) );
598 for ( i = 0 ; i < buttons.length ; i++ )
599 this._.buttons[ buttons[i].id ] = buttons[i];
602 // Focusable interface. Use it via dialog.addFocusable.
603 function Focusable( dialog, element, index )
605 this.element = element;
606 this.focusIndex = index;
607 // TODO: support tabIndex for focusables.
609 this.isFocusable = function()
611 return !element.getAttribute( 'disabled' ) && element.isVisible();
613 this.focus = function()
615 dialog._.currentFocusIndex = this.focusIndex;
616 this.element.focus();
619 element.on( 'keydown', function( e )
621 if ( e.data.getKeystroke() in { 32:1, 13:1 } )
622 this.fire( 'click' );
624 element.on( 'focus', function()
626 this.fire( 'mouseover' );
628 element.on( 'blur', function()
630 this.fire( 'mouseout' );
634 CKEDITOR.dialog.prototype =
639 this._.element.remove();
643 * Resizes the dialog.
644 * @param {Number} width The width of the dialog in pixels.
645 * @param {Number} height The height of the dialog in pixels.
648 * dialogObj.resize( 800, 640 );
652 return function( width, height )
654 if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height )
657 CKEDITOR.dialog.fire( 'resize',
660 skin : this._.editor.skinName,
667 skin : this._.editor.skinName,
672 // Update dialog position when dimension get changed in RTL.
673 if ( this._.editor.lang.dir == 'rtl' && this._.position )
674 this._.position.x = CKEDITOR.document.getWindow().getViewPaneSize().width -
675 this._.contentSize.width - parseInt( this._.element.getFirst().getStyle( 'right' ), 10 );
677 this._.contentSize = { width : width, height : height };
682 * Gets the current size of the dialog in pixels.
683 * @returns {Object} An object with "width" and "height" properties.
685 * var width = dialogObj.getSize().width;
689 var element = this._.element.getFirst();
690 return { width : element.$.offsetWidth || 0, height : element.$.offsetHeight || 0};
694 * Moves the dialog to an (x, y) coordinate relative to the window.
696 * @param {Number} x The target x-coordinate.
697 * @param {Number} y The target y-coordinate.
698 * @param {Boolean} save Flag indicate whether the dialog position should be remembered on next open up.
700 * dialogObj.move( 10, 40 );
705 return function( x, y, save )
707 // The dialog may be fixed positioned or absolute positioned. Ask the
708 // browser what is the current situation first.
709 var element = this._.element.getFirst(),
710 rtl = this._.editor.lang.dir == 'rtl';
712 if ( isFixed === undefined )
713 isFixed = element.getComputedStyle( 'position' ) == 'fixed';
715 if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y )
718 // Save the current position.
719 this._.position = { x : x, y : y };
721 // If not fixed positioned, add scroll position to the coordinates.
724 var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition();
725 x += scrollPosition.x;
726 y += scrollPosition.y;
729 // Translate coordinate for RTL.
732 var dialogSize = this.getSize(),
733 viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize();
734 x = viewPaneSize.width - dialogSize.width - x;
737 var styles = { 'top' : ( y > 0 ? y : 0 ) + 'px' };
738 styles[ rtl ? 'right' : 'left' ] = ( x > 0 ? x : 0 ) + 'px';
740 element.setStyles( styles );
742 save && ( this._.moved = 1 );
747 * Gets the dialog's position in the window.
748 * @returns {Object} An object with "x" and "y" properties.
750 * var dialogX = dialogObj.getPosition().x;
752 getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); },
755 * Shows the dialog box.
761 // Insert the dialog's element to the root document.
762 var element = this._.element;
763 var definition = this.definition;
764 if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) )
765 element.appendTo( CKEDITOR.document.getBody() );
767 element.setStyle( 'display', 'block' );
769 // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8.
770 if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )
772 var dialogElement = this.parts.dialog;
773 dialogElement.setStyle( 'position', 'absolute' );
774 setTimeout( function()
776 dialogElement.setStyle( 'position', 'fixed' );
781 // First, set the dialog to an appropriate size.
782 this.resize( this._.contentSize && this._.contentSize.width || definition.width || definition.minWidth,
783 this._.contentSize && this._.contentSize.height || definition.height || definition.minHeight );
785 // Reset all inputs back to their default value.
788 // Select the first tab by default.
789 this.selectPage( this.definition.contents[0].id );
792 if ( CKEDITOR.dialog._.currentZIndex === null )
793 CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex;
794 this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 );
796 // Maintain the dialog ordering and dialog cover.
797 // Also register key handlers if first dialog.
798 if ( CKEDITOR.dialog._.currentTop === null )
800 CKEDITOR.dialog._.currentTop = this;
801 this._.parentDialog = null;
802 showCover( this._.editor );
804 element.on( 'keydown', accessKeyDownHandler );
805 element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler );
807 // Prevent some keys from bubbling up. (#4269)
808 for ( var event in { keyup :1, keydown :1, keypress :1 } )
809 element.on( event, preventKeyBubbling );
813 this._.parentDialog = CKEDITOR.dialog._.currentTop;
814 var parentElement = this._.parentDialog.getElement().getFirst();
815 parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 );
816 CKEDITOR.dialog._.currentTop = this;
819 // Register the Esc hotkeys.
820 registerAccessKey( this, this, '\x1b', null, function()
822 this.getButton( 'cancel' ) && this.getButton( 'cancel' ).click();
825 // Reset the hasFocus state.
826 this._.hasFocus = false;
828 CKEDITOR.tools.setTimeout( function()
831 this.parts.dialog.setStyle( 'visibility', '' );
833 // Execute onLoad for the first show.
834 this.fireOnce( 'load', {} );
835 CKEDITOR.ui.fire( 'ready', this );
837 this.fire( 'show', {} );
838 this._.editor.fire( 'dialogShow', this );
840 // Save the initial values of the dialog.
841 this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } );
848 * Rearrange the dialog to its previous position or the middle of the window.
853 var viewSize = CKEDITOR.document.getWindow().getViewPaneSize(),
854 dialogSize = this.getSize();
856 this.move( this._.moved ? this._.position.x : ( viewSize.width - dialogSize.width ) / 2,
857 this._.moved ? this._.position.y : ( viewSize.height - dialogSize.height ) / 2 );
861 * Executes a function for each UI element.
862 * @param {Function} fn Function to execute for each UI element.
863 * @returns {CKEDITOR.dialog} The current dialog object.
865 foreach : function( fn )
867 for ( var i in this._.contents )
869 for ( var j in this._.contents[i] )
870 fn.call( this, this._.contents[i][j] );
876 * Resets all input values in the dialog.
879 * @returns {CKEDITOR.dialog} The current dialog object.
883 var fn = function( widget ){ if ( widget.reset ) widget.reset( 1 ); };
884 return function(){ this.foreach( fn ); return this; };
889 * Calls the {@link CKEDITOR.dialog.definition.uiElement#setup} method of each of the UI elements, with the arguments passed through it.
890 * It is usually being called when the dialog is opened, to put the initial value inside the field.
892 * dialogObj.setupContent();
894 * var timestamp = ( new Date() ).valueOf();
895 * dialogObj.setupContent( timestamp );
897 setupContent : function()
899 var args = arguments;
900 this.foreach( function( widget )
903 widget.setup.apply( widget, args );
908 * Calls the {@link CKEDITOR.dialog.definition.uiElement#commit} method of each of the UI elements, with the arguments passed through it.
909 * It is usually being called when the user confirms the dialog, to process the values.
911 * dialogObj.commitContent();
913 * var timestamp = ( new Date() ).valueOf();
914 * dialogObj.commitContent( timestamp );
916 commitContent : function()
918 var args = arguments;
919 this.foreach( function( widget )
921 // Make sure IE triggers "change" event on last focused input before closing the dialog. (#7915)
922 if ( CKEDITOR.env.ie && this._.currentFocusIndex == widget.focusIndex )
923 widget.getInputElement().$.blur();
926 widget.commit.apply( widget, args );
931 * Hides the dialog box.
937 if ( !this.parts.dialog.isVisible() )
940 this.fire( 'hide', {} );
941 this._.editor.fire( 'dialogHide', this );
942 var element = this._.element;
943 element.setStyle( 'display', 'none' );
944 this.parts.dialog.setStyle( 'visibility', 'hidden' );
945 // Unregister all access keys associated with this dialog.
946 unregisterAccessKey( this );
948 // Close any child(top) dialogs first.
949 while( CKEDITOR.dialog._.currentTop != this )
950 CKEDITOR.dialog._.currentTop.hide();
952 // Maintain dialog ordering and remove cover if needed.
953 if ( !this._.parentDialog )
957 var parentElement = this._.parentDialog.getElement().getFirst();
958 parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) );
960 CKEDITOR.dialog._.currentTop = this._.parentDialog;
962 // Deduct or clear the z-index.
963 if ( !this._.parentDialog )
965 CKEDITOR.dialog._.currentZIndex = null;
967 // Remove access key handlers.
968 element.removeListener( 'keydown', accessKeyDownHandler );
969 element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler );
971 // Remove bubbling-prevention handler. (#4269)
972 for ( var event in { keyup :1, keydown :1, keypress :1 } )
973 element.removeListener( event, preventKeyBubbling );
975 var editor = this._.editor;
978 if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie )
980 var selection = editor.getSelection();
981 selection && selection.unlock( true );
985 CKEDITOR.dialog._.currentZIndex -= 10;
987 delete this._.parentDialog;
988 // Reset the initial values of the dialog.
989 this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } );
993 * Adds a tabbed page into the dialog.
994 * @param {Object} contents Content definition.
997 addPage : function( contents )
1000 titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '',
1001 elements = contents.elements,
1002 vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this,
1005 className : 'cke_dialog_page_contents',
1006 children : contents.elements,
1007 expand : !!contents.expand,
1008 padding : contents.padding,
1009 style : contents.style || 'width: 100%;height:100%'
1012 // Create the HTML for the tab and the content block.
1013 var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) );
1014 page.setAttribute( 'role', 'tabpanel' );
1016 var env = CKEDITOR.env;
1017 var tabId = 'cke_' + contents.id + '_' + CKEDITOR.tools.getNextNumber(),
1018 tab = CKEDITOR.dom.element.createFromHtml( [
1019 '<a class="cke_dialog_tab"',
1020 ( this._.pageCount > 0 ? ' cke_last' : 'cke_first' ),
1022 ( !!contents.hidden ? ' style="display:none"' : '' ),
1023 ' id="', tabId, '"',
1024 env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"',
1026 ' hidefocus="true"',
1032 page.setAttribute( 'aria-labelledby', tabId );
1034 // Take records for the tabs and elements created.
1035 this._.tabs[ contents.id ] = [ tab, page ];
1036 this._.tabIdList.push( contents.id );
1037 !contents.hidden && this._.pageCount++;
1038 this._.lastTab = tab;
1041 var contentMap = this._.contents[ contents.id ] = {},
1043 children = vbox.getChild();
1045 while ( ( cursor = children.shift() ) )
1047 contentMap[ cursor.id ] = cursor;
1048 if ( typeof( cursor.getChild ) == 'function' )
1049 children.push.apply( children, cursor.getChild() );
1052 // Attach the DOM nodes.
1054 page.setAttribute( 'name', contents.id );
1055 page.appendTo( this.parts.contents );
1058 this.parts.tabs.append( tab );
1060 // Add access key handlers if access key is defined.
1061 if ( contents.accessKey )
1063 registerAccessKey( this, this, 'CTRL+' + contents.accessKey,
1064 tabAccessKeyDown, tabAccessKeyUp );
1065 this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id;
1070 * Activates a tab page in the dialog by its id.
1071 * @param {String} id The id of the dialog tab to be activated.
1073 * dialogObj.selectPage( 'tab_1' );
1075 selectPage : function( id )
1077 if ( this._.currentTabId == id )
1080 // Returning true means that the event has been canceled
1081 if ( this.fire( 'selectPage', { page : id, currentPage : this._.currentTabId } ) === true )
1084 // Hide the non-selected tabs and pages.
1085 for ( var i in this._.tabs )
1087 var tab = this._.tabs[i][0],
1088 page = this._.tabs[i][1];
1091 tab.removeClass( 'cke_dialog_tab_selected' );
1094 page.setAttribute( 'aria-hidden', i != id );
1097 var selected = this._.tabs[ id ];
1098 selected[ 0 ].addClass( 'cke_dialog_tab_selected' );
1100 // [IE] an invisible input[type='text'] will enlarge it's width
1101 // if it's value is long when it shows, so we clear it's value
1102 // before it shows and then recover it (#5649)
1103 if ( CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat )
1105 clearOrRecoverTextInputValue( selected[ 1 ] );
1106 selected[ 1 ].show();
1107 setTimeout( function()
1109 clearOrRecoverTextInputValue( selected[ 1 ], 1 );
1113 selected[ 1 ].show();
1115 this._.currentTabId = id;
1116 this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id );
1119 // Dialog state-specific style updates.
1120 updateStyle : function()
1122 // If only a single page shown, a different style is used in the central pane.
1123 this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' );
1127 * Hides a page's tab away from the dialog.
1128 * @param {String} id The page's Id.
1130 * dialog.hidePage( 'tab_3' );
1132 hidePage : function( id )
1134 var tab = this._.tabs[id] && this._.tabs[id][0];
1135 if ( !tab || this._.pageCount == 1 || !tab.isVisible() )
1137 // Switch to other tab first when we're hiding the active tab.
1138 else if ( id == this._.currentTabId )
1139 this.selectPage( getPreviousVisibleTab.call( this ) );
1147 * Unhides a page's tab.
1148 * @param {String} id The page's Id.
1150 * dialog.showPage( 'tab_2' );
1152 showPage : function( id )
1154 var tab = this._.tabs[id] && this._.tabs[id][0];
1163 * Gets the root DOM element of the dialog.
1164 * @returns {CKEDITOR.dom.element} The <span> element containing this dialog.
1166 * var dialogElement = dialogObj.getElement().getFirst();
1167 * dialogElement.setStyle( 'padding', '5px' );
1169 getElement : function()
1171 return this._.element;
1175 * Gets the name of the dialog.
1176 * @returns {String} The name of this dialog.
1178 * var dialogName = dialogObj.getName();
1180 getName : function()
1186 * Gets a dialog UI element object from a dialog page.
1187 * @param {String} pageId id of dialog page.
1188 * @param {String} elementId id of UI element.
1190 * dialogObj.getContentElement( 'tabId', 'elementId' ).setValue( 'Example' );
1191 * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element.
1193 getContentElement : function( pageId, elementId )
1195 var page = this._.contents[ pageId ];
1196 return page && page[ elementId ];
1200 * Gets the value of a dialog UI element.
1201 * @param {String} pageId id of dialog page.
1202 * @param {String} elementId id of UI element.
1204 * alert( dialogObj.getValueOf( 'tabId', 'elementId' ) );
1205 * @returns {Object} The value of the UI element.
1207 getValueOf : function( pageId, elementId )
1209 return this.getContentElement( pageId, elementId ).getValue();
1213 * Sets the value of a dialog UI element.
1214 * @param {String} pageId id of the dialog page.
1215 * @param {String} elementId id of the UI element.
1216 * @param {Object} value The new value of the UI element.
1218 * dialogObj.setValueOf( 'tabId', 'elementId', 'Example' );
1220 setValueOf : function( pageId, elementId, value )
1222 return this.getContentElement( pageId, elementId ).setValue( value );
1226 * Gets the UI element of a button in the dialog's button row.
1227 * @param {String} id The id of the button.
1229 * @returns {CKEDITOR.ui.dialog.button} The button object.
1231 getButton : function( id )
1233 return this._.buttons[ id ];
1237 * Simulates a click to a dialog button in the dialog's button row.
1238 * @param {String} id The id of the button.
1240 * @returns The return value of the dialog's "click" event.
1242 click : function( id )
1244 return this._.buttons[ id ].click();
1248 * Disables a dialog button.
1249 * @param {String} id The id of the button.
1252 disableButton : function( id )
1254 return this._.buttons[ id ].disable();
1258 * Enables a dialog button.
1259 * @param {String} id The id of the button.
1262 enableButton : function( id )
1264 return this._.buttons[ id ].enable();
1268 * Gets the number of pages in the dialog.
1269 * @returns {Number} Page count.
1271 getPageCount : function()
1273 return this._.pageCount;
1277 * Gets the editor instance which opened this dialog.
1278 * @returns {CKEDITOR.editor} Parent editor instances.
1280 getParentEditor : function()
1282 return this._.editor;
1286 * Gets the element that was selected when opening the dialog, if any.
1287 * @returns {CKEDITOR.dom.element} The element that was selected, or null.
1289 getSelectedElement : function()
1291 return this.getParentEditor().getSelection().getSelectedElement();
1295 * Adds element to dialog's focusable list.
1297 * @param {CKEDITOR.dom.element} element
1298 * @param {Number} [index]
1300 addFocusable: function( element, index ) {
1301 if ( typeof index == 'undefined' )
1303 index = this._.focusList.length;
1304 this._.focusList.push( new Focusable( this, element, index ) );
1308 this._.focusList.splice( index, 0, new Focusable( this, element, index ) );
1309 for ( var i = index + 1 ; i < this._.focusList.length ; i++ )
1310 this._.focusList[ i ].focusIndex++;
1315 CKEDITOR.tools.extend( CKEDITOR.dialog,
1317 * @lends CKEDITOR.dialog
1321 * Registers a dialog.
1322 * @param {String} name The dialog's name.
1323 * @param {Function|String} dialogDefinition
1324 * A function returning the dialog's definition, or the URL to the .js file holding the function.
1325 * The function should accept an argument "editor" which is the current editor instance, and
1326 * return an object conforming to {@link CKEDITOR.dialog.definition}.
1327 * @see CKEDITOR.dialog.definition
1329 * // Full sample plugin, which does not only register a dialog window but also adds an item to the context menu.
1330 * // To open the dialog window, choose "Open dialog" in the context menu.
1331 * CKEDITOR.plugins.add( 'myplugin',
1333 * init: function( editor )
1335 * editor.addCommand( 'mydialog',new CKEDITOR.dialogCommand( 'mydialog' ) );
1337 * if ( editor.contextMenu )
1339 * editor.addMenuGroup( 'mygroup', 10 );
1340 * editor.addMenuItem( 'My Dialog',
1342 * label : 'Open dialog',
1343 * command : 'mydialog',
1346 * editor.contextMenu.addListener( function( element )
1348 * return { 'My Dialog' : CKEDITOR.TRISTATE_OFF };
1352 * <strong>CKEDITOR.dialog.add</strong>( 'mydialog', function( api )
1354 * // CKEDITOR.dialog.definition
1355 * var <strong>dialogDefinition</strong> =
1357 * title : 'Sample dialog',
1371 * html : '<p>This is some sample HTML content.</p>'
1374 * type : 'textarea',
1375 * id : 'textareaId',
1382 * buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ],
1383 * onOk : function() {
1384 * // "this" is now a CKEDITOR.dialog object.
1385 * // Accessing dialog elements:
1386 * var textareaObj = this.<strong>getContentElement</strong>( 'tab1', 'textareaId' );
1387 * alert( "You have entered: " + textareaObj.getValue() );
1391 * return dialogDefinition;
1396 * CKEDITOR.replace( 'editor1', { extraPlugins : 'myplugin' } );
1398 add : function( name, dialogDefinition )
1400 // Avoid path registration from multiple instances override definition.
1401 if ( !this._.dialogDefinitions[name]
1402 || typeof dialogDefinition == 'function' )
1403 this._.dialogDefinitions[name] = dialogDefinition;
1406 exists : function( name )
1408 return !!this._.dialogDefinitions[ name ];
1411 getCurrent : function()
1413 return CKEDITOR.dialog._.currentTop;
1417 * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds.
1423 okButton : (function()
1425 var retval = function( editor, override )
1427 override = override || {};
1428 return CKEDITOR.tools.extend( {
1431 label : editor.lang.common.ok,
1432 'class' : 'cke_dialog_ui_button_ok',
1433 onClick : function( evt )
1435 var dialog = evt.data.dialog;
1436 if ( dialog.fire( 'ok', { hide : true } ).hide !== false )
1439 }, override, true );
1441 retval.type = 'button';
1442 retval.override = function( override )
1444 return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); },
1445 { type : 'button' }, true );
1451 * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed.
1457 cancelButton : (function()
1459 var retval = function( editor, override )
1461 override = override || {};
1462 return CKEDITOR.tools.extend( {
1465 label : editor.lang.common.cancel,
1466 'class' : 'cke_dialog_ui_button_cancel',
1467 onClick : function( evt )
1469 var dialog = evt.data.dialog;
1470 if ( dialog.fire( 'cancel', { hide : true } ).hide !== false )
1473 }, override, true );
1475 retval.type = 'button';
1476 retval.override = function( override )
1478 return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); },
1479 { type : 'button' }, true );
1485 * Registers a dialog UI element.
1486 * @param {String} typeName The name of the UI element.
1487 * @param {Function} builder The function to build the UI element.
1490 addUIElement : function( typeName, builder )
1492 this._.uiElementBuilders[ typeName ] = builder;
1498 uiElementBuilders : {},
1500 dialogDefinitions : {},
1504 currentZIndex : null
1507 // "Inherit" (copy actually) from CKEDITOR.event.
1508 CKEDITOR.event.implementOn( CKEDITOR.dialog );
1509 CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true );
1511 var defaultDialogDefinition =
1513 resizable : CKEDITOR.DIALOG_RESIZE_BOTH,
1516 buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ]
1519 // Tool function used to return an item from an array based on its id
1521 var getById = function( array, id, recurse )
1523 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ )
1525 if ( item.id == id )
1527 if ( recurse && item[ recurse ] )
1529 var retval = getById( item[ recurse ], id, recurse ) ;
1537 // Tool function used to add an item into an array.
1538 var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound )
1540 if ( nextSiblingId )
1542 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ )
1544 if ( item.id == nextSiblingId )
1546 array.splice( i, 0, newItem );
1550 if ( recurse && item[ recurse ] )
1552 var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true );
1558 if ( nullIfNotFound )
1562 array.push( newItem );
1566 // Tool function used to remove an item from an array based on its id.
1567 var removeById = function( array, id, recurse )
1569 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ )
1571 if ( item.id == id )
1572 return array.splice( i, 1 );
1573 if ( recurse && item[ recurse ] )
1575 var retval = removeById( item[ recurse ], id, recurse );
1584 * This class is not really part of the API. It is the "definition" property value
1585 * passed to "dialogDefinition" event handlers.
1587 * @name CKEDITOR.dialog.definitionObject
1588 * @extends CKEDITOR.dialog.definition
1590 * CKEDITOR.on( 'dialogDefinition', function( evt )
1592 * var definition = evt.data.definition;
1593 * var content = definition.getContents( 'page1' );
1597 var definitionObject = function( dialog, dialogDefinition )
1599 // TODO : Check if needed.
1600 this.dialog = dialog;
1602 // Transform the contents entries in contentObjects.
1603 var contents = dialogDefinition.contents;
1604 for ( var i = 0, content ; ( content = contents[i] ) ; i++ )
1605 contents[ i ] = content && new contentObject( dialog, content );
1607 CKEDITOR.tools.extend( this, dialogDefinition );
1610 definitionObject.prototype =
1611 /** @lends CKEDITOR.dialog.definitionObject.prototype */
1614 * Gets a content definition.
1615 * @param {String} id The id of the content definition.
1616 * @returns {CKEDITOR.dialog.definition.content} The content definition
1619 getContents : function( id )
1621 return getById( this.contents, id );
1625 * Gets a button definition.
1626 * @param {String} id The id of the button definition.
1627 * @returns {CKEDITOR.dialog.definition.button} The button definition
1630 getButton : function( id )
1632 return getById( this.buttons, id );
1636 * Adds a content definition object under this dialog definition.
1637 * @param {CKEDITOR.dialog.definition.content} contentDefinition The
1638 * content definition.
1639 * @param {String} [nextSiblingId] The id of an existing content
1640 * definition which the new content definition will be inserted
1641 * before. Omit if the new content definition is to be inserted as
1643 * @returns {CKEDITOR.dialog.definition.content} The inserted content
1646 addContents : function( contentDefinition, nextSiblingId )
1648 return addById( this.contents, contentDefinition, nextSiblingId );
1652 * Adds a button definition object under this dialog definition.
1653 * @param {CKEDITOR.dialog.definition.button} buttonDefinition The
1654 * button definition.
1655 * @param {String} [nextSiblingId] The id of an existing button
1656 * definition which the new button definition will be inserted
1657 * before. Omit if the new button definition is to be inserted as
1659 * @returns {CKEDITOR.dialog.definition.button} The inserted button
1662 addButton : function( buttonDefinition, nextSiblingId )
1664 return addById( this.buttons, buttonDefinition, nextSiblingId );
1668 * Removes a content definition from this dialog definition.
1669 * @param {String} id The id of the content definition to be removed.
1670 * @returns {CKEDITOR.dialog.definition.content} The removed content
1673 removeContents : function( id )
1675 removeById( this.contents, id );
1679 * Removes a button definition from the dialog definition.
1680 * @param {String} id The id of the button definition to be removed.
1681 * @returns {CKEDITOR.dialog.definition.button} The removed button
1684 removeButton : function( id )
1686 removeById( this.buttons, id );
1691 * This class is not really part of the API. It is the template of the
1692 * objects representing content pages inside the
1693 * CKEDITOR.dialog.definitionObject.
1695 * @name CKEDITOR.dialog.definition.contentObject
1697 * CKEDITOR.on( 'dialogDefinition', function( evt )
1699 * var definition = evt.data.definition;
1700 * var content = definition.getContents( 'page1' );
1701 * content.remove( 'textInput1' );
1705 function contentObject( dialog, contentDefinition )
1712 CKEDITOR.tools.extend( this, contentDefinition );
1715 contentObject.prototype =
1716 /** @lends CKEDITOR.dialog.definition.contentObject.prototype */
1719 * Gets a UI element definition under the content definition.
1720 * @param {String} id The id of the UI element definition.
1721 * @returns {CKEDITOR.dialog.definition.uiElement}
1723 get : function( id )
1725 return getById( this.elements, id, 'children' );
1729 * Adds a UI element definition to the content definition.
1730 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition The
1731 * UI elemnet definition to be added.
1732 * @param {String} nextSiblingId The id of an existing UI element
1733 * definition which the new UI element definition will be inserted
1734 * before. Omit if the new button definition is to be inserted as
1736 * @returns {CKEDITOR.dialog.definition.uiElement} The element
1737 * definition inserted.
1739 add : function( elementDefinition, nextSiblingId )
1741 return addById( this.elements, elementDefinition, nextSiblingId, 'children' );
1745 * Removes a UI element definition from the content definition.
1746 * @param {String} id The id of the UI element definition to be
1748 * @returns {CKEDITOR.dialog.definition.uiElement} The element
1749 * definition removed.
1752 remove : function( id )
1754 removeById( this.elements, id, 'children' );
1758 function initDragAndDrop( dialog )
1760 var lastCoords = null,
1761 abstractDialogCoords = null,
1762 element = dialog.getElement().getFirst(),
1763 editor = dialog.getParentEditor(),
1764 magnetDistance = editor.config.dialog_magnetDistance,
1765 margins = editor.skin.margins || [ 0, 0, 0, 0 ];
1767 if ( typeof magnetDistance == 'undefined' )
1768 magnetDistance = 20;
1770 function mouseMoveHandler( evt )
1772 var dialogSize = dialog.getSize(),
1773 viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(),
1774 x = evt.data.$.screenX,
1775 y = evt.data.$.screenY,
1776 dx = x - lastCoords.x,
1777 dy = y - lastCoords.y,
1780 lastCoords = { x : x, y : y };
1781 abstractDialogCoords.x += dx;
1782 abstractDialogCoords.y += dy;
1784 if ( abstractDialogCoords.x + margins[3] < magnetDistance )
1785 realX = - margins[3];
1786 else if ( abstractDialogCoords.x - margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance )
1787 realX = viewPaneSize.width - dialogSize.width + ( editor.lang.dir == 'rtl' ? 0 : margins[1] );
1789 realX = abstractDialogCoords.x;
1791 if ( abstractDialogCoords.y + margins[0] < magnetDistance )
1792 realY = - margins[0];
1793 else if ( abstractDialogCoords.y - margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance )
1794 realY = viewPaneSize.height - dialogSize.height + margins[2];
1796 realY = abstractDialogCoords.y;
1798 dialog.move( realX, realY, 1 );
1800 evt.data.preventDefault();
1803 function mouseUpHandler( evt )
1805 CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler );
1806 CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler );
1808 if ( CKEDITOR.env.ie6Compat )
1810 var coverDoc = currentCover.getChild( 0 ).getFrameDocument();
1811 coverDoc.removeListener( 'mousemove', mouseMoveHandler );
1812 coverDoc.removeListener( 'mouseup', mouseUpHandler );
1816 dialog.parts.title.on( 'mousedown', function( evt )
1818 lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY };
1820 CKEDITOR.document.on( 'mousemove', mouseMoveHandler );
1821 CKEDITOR.document.on( 'mouseup', mouseUpHandler );
1822 abstractDialogCoords = dialog.getPosition();
1824 if ( CKEDITOR.env.ie6Compat )
1826 var coverDoc = currentCover.getChild( 0 ).getFrameDocument();
1827 coverDoc.on( 'mousemove', mouseMoveHandler );
1828 coverDoc.on( 'mouseup', mouseUpHandler );
1831 evt.data.preventDefault();
1835 function initResizeHandles( dialog )
1837 var def = dialog.definition,
1838 resizable = def.resizable;
1840 if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE )
1843 var editor = dialog.getParentEditor();
1844 var wrapperWidth, wrapperHeight,
1845 viewSize, origin, startSize,
1848 var mouseDownFn = CKEDITOR.tools.addFunction( function( $event )
1850 startSize = dialog.getSize();
1852 var content = dialog.parts.contents,
1853 iframeDialog = content.$.getElementsByTagName( 'iframe' ).length;
1855 // Shim to help capturing "mousemove" over iframe.
1858 dialogCover = CKEDITOR.dom.element.createFromHtml( '<div class="cke_dialog_resize_cover" style="height: 100%; position: absolute; width: 100%;"></div>' );
1859 content.append( dialogCover );
1862 // Calculate the offset between content and chrome size.
1863 wrapperHeight = startSize.height - dialog.parts.contents.getSize( 'height', ! ( CKEDITOR.env.gecko || CKEDITOR.env.opera || CKEDITOR.env.ie && CKEDITOR.env.quirks ) );
1864 wrapperWidth = startSize.width - dialog.parts.contents.getSize( 'width', 1 );
1866 origin = { x : $event.screenX, y : $event.screenY };
1868 viewSize = CKEDITOR.document.getWindow().getViewPaneSize();
1870 CKEDITOR.document.on( 'mousemove', mouseMoveHandler );
1871 CKEDITOR.document.on( 'mouseup', mouseUpHandler );
1873 if ( CKEDITOR.env.ie6Compat )
1875 var coverDoc = currentCover.getChild( 0 ).getFrameDocument();
1876 coverDoc.on( 'mousemove', mouseMoveHandler );
1877 coverDoc.on( 'mouseup', mouseUpHandler );
1880 $event.preventDefault && $event.preventDefault();
1883 // Prepend the grip to the dialog.
1884 dialog.on( 'load', function()
1887 if ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH )
1888 direction = ' cke_resizer_horizontal';
1889 else if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT )
1890 direction = ' cke_resizer_vertical';
1891 var resizer = CKEDITOR.dom.element.createFromHtml( '<div' +
1892 ' class="cke_resizer' + direction + ' cke_resizer_' + editor.lang.dir + '"' +
1893 ' title="' + CKEDITOR.tools.htmlEncode( editor.lang.resize ) + '"' +
1894 ' onmousedown="CKEDITOR.tools.callFunction(' + mouseDownFn + ', event )"></div>' );
1895 dialog.parts.footer.append( resizer, 1 );
1897 editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( mouseDownFn ); } );
1899 function mouseMoveHandler( evt )
1901 var rtl = editor.lang.dir == 'rtl',
1902 dx = ( evt.data.$.screenX - origin.x ) * ( rtl ? -1 : 1 ),
1903 dy = evt.data.$.screenY - origin.y,
1904 width = startSize.width,
1905 height = startSize.height,
1906 internalWidth = width + dx * ( dialog._.moved ? 1 : 2 ),
1907 internalHeight = height + dy * ( dialog._.moved ? 1 : 2 ),
1908 element = dialog._.element.getFirst(),
1909 right = rtl && element.getComputedStyle( 'right' ),
1910 position = dialog.getPosition();
1912 if ( position.y + internalHeight > viewSize.height )
1913 internalHeight = viewSize.height - position.y;
1915 if ( ( rtl ? right : position.x ) + internalWidth > viewSize.width )
1916 internalWidth = viewSize.width - ( rtl ? right : position.x );
1918 // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL.
1919 if ( ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) )
1920 width = Math.max( def.minWidth || 0, internalWidth - wrapperWidth );
1922 if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT || resizable == CKEDITOR.DIALOG_RESIZE_BOTH )
1923 height = Math.max( def.minHeight || 0, internalHeight - wrapperHeight );
1925 dialog.resize( width, height );
1927 if ( !dialog._.moved )
1930 evt.data.preventDefault();
1933 function mouseUpHandler()
1935 CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler );
1936 CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler );
1940 dialogCover.remove();
1944 if ( CKEDITOR.env.ie6Compat )
1946 var coverDoc = currentCover.getChild( 0 ).getFrameDocument();
1947 coverDoc.removeListener( 'mouseup', mouseUpHandler );
1948 coverDoc.removeListener( 'mousemove', mouseMoveHandler );
1954 // Caching resuable covers and allowing only one cover
1959 function cancelEvent( ev )
1961 ev.data.preventDefault(1);
1964 function showCover( editor )
1966 var win = CKEDITOR.document.getWindow();
1967 var config = editor.config,
1968 backgroundColorStyle = config.dialog_backgroundCoverColor || 'white',
1969 backgroundCoverOpacity = config.dialog_backgroundCoverOpacity,
1970 baseFloatZIndex = config.baseFloatZIndex,
1971 coverKey = CKEDITOR.tools.genKey(
1972 backgroundColorStyle,
1973 backgroundCoverOpacity,
1975 coverElement = covers[ coverKey ];
1977 if ( !coverElement )
1980 '<div tabIndex="-1" style="position: ', ( CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed' ),
1981 '; z-index: ', baseFloatZIndex,
1982 '; top: 0px; left: 0px; ',
1983 ( !CKEDITOR.env.ie6Compat ? 'background-color: ' + backgroundColorStyle : '' ),
1984 '" class="cke_dialog_background_cover">'
1987 if ( CKEDITOR.env.ie6Compat )
1989 // Support for custom document.domain in IE.
1990 var isCustomDomain = CKEDITOR.env.isCustomDomain(),
1991 iframeHtml = '<html><body style=\\\'background-color:' + backgroundColorStyle + ';\\\'></body></html>';
1995 ' hidefocus="true"' +
1996 ' frameborder="0"' +
1997 ' id="cke_dialog_background_iframe"' +
1998 ' src="javascript:' );
2000 html.push( 'void((function(){' +
2001 'document.open();' +
2002 ( isCustomDomain ? 'document.domain=\'' + document.domain + '\';' : '' ) +
2003 'document.write( \'' + iframeHtml + '\' );' +
2004 'document.close();' +
2010 'position:absolute;' +
2015 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)">' +
2019 html.push( '</div>' );
2021 coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) );
2022 coverElement.setOpacity( backgroundCoverOpacity != undefined ? backgroundCoverOpacity : 0.5 );
2024 coverElement.on( 'keydown', cancelEvent );
2025 coverElement.on( 'keypress', cancelEvent );
2026 coverElement.on( 'keyup', cancelEvent );
2028 coverElement.appendTo( CKEDITOR.document.getBody() );
2029 covers[ coverKey ] = coverElement;
2032 coverElement. show();
2034 currentCover = coverElement;
2035 var resizeFunc = function()
2037 var size = win.getViewPaneSize();
2038 coverElement.setStyles(
2040 width : size.width + 'px',
2041 height : size.height + 'px'
2045 var scrollFunc = function()
2047 var pos = win.getScrollPosition(),
2048 cursor = CKEDITOR.dialog._.currentTop;
2049 coverElement.setStyles(
2051 left : pos.x + 'px',
2059 var dialogPos = cursor.getPosition();
2060 cursor.move( dialogPos.x, dialogPos.y );
2061 } while ( ( cursor = cursor._.parentDialog ) );
2065 resizeCover = resizeFunc;
2066 win.on( 'resize', resizeFunc );
2068 // Using Safari/Mac, focus must be kept where it is (#7027)
2069 if ( !( CKEDITOR.env.mac && CKEDITOR.env.webkit ) )
2070 coverElement.focus();
2072 if ( CKEDITOR.env.ie6Compat )
2074 // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll.
2075 // So we need to invent a really funny way to make it work.
2076 var myScrollHandler = function()
2079 arguments.callee.prevScrollHandler.apply( this, arguments );
2081 win.$.setTimeout( function()
2083 myScrollHandler.prevScrollHandler = window.onscroll || function(){};
2084 window.onscroll = myScrollHandler;
2090 function hideCover()
2092 if ( !currentCover )
2095 var win = CKEDITOR.document.getWindow();
2096 currentCover.hide();
2097 win.removeListener( 'resize', resizeCover );
2099 if ( CKEDITOR.env.ie6Compat )
2101 win.$.setTimeout( function()
2103 var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler;
2104 window.onscroll = prevScrollHandler || null;
2110 function removeCovers()
2112 for ( var coverId in covers )
2113 covers[ coverId ].remove();
2117 var accessKeyProcessors = {};
2119 var accessKeyDownHandler = function( evt )
2121 var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey,
2122 alt = evt.data.$.altKey,
2123 shift = evt.data.$.shiftKey,
2124 key = String.fromCharCode( evt.data.$.keyCode ),
2125 keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key];
2127 if ( !keyProcessor || !keyProcessor.length )
2130 keyProcessor = keyProcessor[keyProcessor.length - 1];
2131 keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key );
2132 evt.data.preventDefault();
2135 var accessKeyUpHandler = function( evt )
2137 var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey,
2138 alt = evt.data.$.altKey,
2139 shift = evt.data.$.shiftKey,
2140 key = String.fromCharCode( evt.data.$.keyCode ),
2141 keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key];
2143 if ( !keyProcessor || !keyProcessor.length )
2146 keyProcessor = keyProcessor[keyProcessor.length - 1];
2147 if ( keyProcessor.keyup )
2149 keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key );
2150 evt.data.preventDefault();
2154 var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc )
2156 var procList = accessKeyProcessors[key] || ( accessKeyProcessors[key] = [] );
2158 uiElement : uiElement,
2161 keyup : upFunc || uiElement.accessKeyUp,
2162 keydown : downFunc || uiElement.accessKeyDown
2166 var unregisterAccessKey = function( obj )
2168 for ( var i in accessKeyProcessors )
2170 var list = accessKeyProcessors[i];
2171 for ( var j = list.length - 1 ; j >= 0 ; j-- )
2173 if ( list[j].dialog == obj || list[j].uiElement == obj )
2174 list.splice( j, 1 );
2176 if ( list.length === 0 )
2177 delete accessKeyProcessors[i];
2181 var tabAccessKeyUp = function( dialog, key )
2183 if ( dialog._.accessKeyMap[key] )
2184 dialog.selectPage( dialog._.accessKeyMap[key] );
2187 var tabAccessKeyDown = function( dialog, key )
2192 var preventKeyBubblingKeys = { 27 :1, 13 :1 };
2193 var preventKeyBubbling = function( e )
2195 if ( e.data.getKeystroke() in preventKeyBubblingKeys )
2196 e.data.stopPropagation();
2201 CKEDITOR.ui.dialog =
2204 * The base class of all dialog UI elements.
2206 * @param {CKEDITOR.dialog} dialog Parent dialog object.
2207 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element
2208 * definition. Accepted fields:
2210 * <li><strong>id</strong> (Required) The id of the UI element. See {@link
2211 * CKEDITOR.dialog#getContentElement}</li>
2212 * <li><strong>type</strong> (Required) The type of the UI element. The
2213 * value to this field specifies which UI element class will be used to
2214 * generate the final widget.</li>
2215 * <li><strong>title</strong> (Optional) The popup tooltip for the UI
2217 * <li><strong>hidden</strong> (Optional) A flag that tells if the element
2218 * should be initially visible.</li>
2219 * <li><strong>className</strong> (Optional) Additional CSS class names
2220 * to add to the UI element. Separated by space.</li>
2221 * <li><strong>style</strong> (Optional) Additional CSS inline styles
2222 * to add to the UI element. A semicolon (;) is required after the last
2223 * style declaration.</li>
2224 * <li><strong>accessKey</strong> (Optional) The alphanumeric access key
2225 * for this element. Access keys are automatically prefixed by CTRL.</li>
2226 * <li><strong>on*</strong> (Optional) Any UI element definition field that
2227 * starts with <em>on</em> followed immediately by a capital letter and
2228 * probably more letters is an event handler. Event handlers may be further
2229 * divided into registered event handlers and DOM event handlers. Please
2230 * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and
2231 * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more
2234 * @param {Array} htmlList
2235 * List of HTML code to be added to the dialog's content area.
2236 * @param {Function|String} nodeNameArg
2237 * A function returning a string, or a simple string for the node name for
2238 * the root DOM node. Default is 'div'.
2239 * @param {Function|Object} stylesArg
2240 * A function returning an object, or a simple object for CSS styles applied
2241 * to the DOM node. Default is empty object.
2242 * @param {Function|Object} attributesArg
2243 * A fucntion returning an object, or a simple object for attributes applied
2244 * to the DOM node. Default is empty object.
2245 * @param {Function|String} contentsArg
2246 * A function returning a string, or a simple string for the HTML code inside
2247 * the root DOM node. Default is empty string.
2250 uiElement : function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg )
2252 if ( arguments.length < 4 )
2255 var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div',
2256 html = [ '<', nodeName, ' ' ],
2257 styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {},
2258 attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {},
2259 innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '',
2260 domId = this.domId = attributes.id || CKEDITOR.tools.getNextId() + '_uiElement',
2261 id = this.id = elementDefinition.id,
2264 // Set the id, a unique id is required for getElement() to work.
2265 attributes.id = domId;
2267 // Set the type and definition CSS class names.
2269 if ( elementDefinition.type )
2270 classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1;
2271 if ( elementDefinition.className )
2272 classes[ elementDefinition.className ] = 1;
2273 if ( elementDefinition.disabled )
2274 classes[ 'cke_disabled' ] = 1;
2276 var attributeClasses = ( attributes['class'] && attributes['class'].split ) ? attributes['class'].split( ' ' ) : [];
2277 for ( i = 0 ; i < attributeClasses.length ; i++ )
2279 if ( attributeClasses[i] )
2280 classes[ attributeClasses[i] ] = 1;
2282 var finalClasses = [];
2283 for ( i in classes )
2284 finalClasses.push( i );
2285 attributes['class'] = finalClasses.join( ' ' );
2287 // Set the popup tooltop.
2288 if ( elementDefinition.title )
2289 attributes.title = elementDefinition.title;
2291 // Write the inline CSS styles.
2292 var styleStr = ( elementDefinition.style || '' ).split( ';' );
2294 // Element alignment support.
2295 if ( elementDefinition.align )
2297 var align = elementDefinition.align;
2298 styles[ 'margin-left' ] = align == 'left' ? 0 : 'auto';
2299 styles[ 'margin-right' ] = align == 'right' ? 0 : 'auto';
2303 styleStr.push( i + ':' + styles[i] );
2304 if ( elementDefinition.hidden )
2305 styleStr.push( 'display:none' );
2306 for ( i = styleStr.length - 1 ; i >= 0 ; i-- )
2308 if ( styleStr[i] === '' )
2309 styleStr.splice( i, 1 );
2311 if ( styleStr.length > 0 )
2312 attributes.style = ( attributes.style ? ( attributes.style + '; ' ) : '' ) + styleStr.join( '; ' );
2314 // Write the attributes.
2315 for ( i in attributes )
2316 html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" ');
2318 // Write the content HTML.
2319 html.push( '>', innerHTML, '</', nodeName, '>' );
2321 // Add contents to the parent HTML array.
2322 htmlList.push( html.join( '' ) );
2324 ( this._ || ( this._ = {} ) ).dialog = dialog;
2326 // Override isChanged if it is defined in element definition.
2327 if ( typeof( elementDefinition.isChanged ) == 'boolean' )
2328 this.isChanged = function(){ return elementDefinition.isChanged; };
2329 if ( typeof( elementDefinition.isChanged ) == 'function' )
2330 this.isChanged = elementDefinition.isChanged;
2332 // Overload 'get(set)Value' on definition.
2333 if ( typeof( elementDefinition.setValue ) == 'function' )
2335 this.setValue = CKEDITOR.tools.override( this.setValue, function( org )
2337 return function( val ){ org.call( this, elementDefinition.setValue.call( this, val ) ); };
2341 if ( typeof( elementDefinition.getValue ) == 'function' )
2343 this.getValue = CKEDITOR.tools.override( this.getValue, function( org )
2345 return function(){ return elementDefinition.getValue.call( this, org.call( this ) ); };
2350 CKEDITOR.event.implementOn( this );
2352 this.registerEvents( elementDefinition );
2353 if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey )
2354 registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey );
2357 dialog.on( 'load', function()
2359 var input = me.getInputElement();
2362 var focusClass = me.type in { 'checkbox' : 1, 'ratio' : 1 } && CKEDITOR.env.ie && CKEDITOR.env.version < 8 ? 'cke_dialog_ui_focused' : '';
2363 input.on( 'focus', function()
2365 dialog._.tabBarMode = false;
2366 dialog._.hasFocus = true;
2368 focusClass && this.addClass( focusClass );
2372 input.on( 'blur', function()
2375 focusClass && this.removeClass( focusClass );
2380 // Register the object as a tab focus if it can be included.
2381 if ( this.keyboardFocusable )
2383 this.tabIndex = elementDefinition.tabIndex || 0;
2385 this.focusIndex = dialog._.focusList.push( this ) - 1;
2386 this.on( 'focus', function()
2388 dialog._.currentFocusIndex = me.focusIndex;
2392 // Completes this object with everything we have in the
2394 CKEDITOR.tools.extend( this, elementDefinition );
2398 * Horizontal layout box for dialog UI elements, auto-expends to available width of container.
2400 * @extends CKEDITOR.ui.dialog.uiElement
2401 * @param {CKEDITOR.dialog} dialog
2402 * Parent dialog object.
2403 * @param {Array} childObjList
2404 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this
2406 * @param {Array} childHtmlList
2407 * Array of HTML code that correspond to the HTML output of all the
2408 * objects in childObjList.
2409 * @param {Array} htmlList
2410 * Array of HTML code that this element will output to.
2411 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
2412 * The element definition. Accepted fields:
2414 * <li><strong>widths</strong> (Optional) The widths of child cells.</li>
2415 * <li><strong>height</strong> (Optional) The height of the layout.</li>
2416 * <li><strong>padding</strong> (Optional) The padding width inside child
2418 * <li><strong>align</strong> (Optional) The alignment of the whole layout
2423 hbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition )
2425 if ( arguments.length < 4 )
2428 this._ || ( this._ = {} );
2430 var children = this._.children = childObjList,
2431 widths = elementDefinition && elementDefinition.widths || null,
2432 height = elementDefinition && elementDefinition.height || null,
2436 var innerHTML = function()
2438 var html = [ '<tbody><tr class="cke_dialog_ui_hbox">' ];
2439 for ( i = 0 ; i < childHtmlList.length ; i++ )
2441 var className = 'cke_dialog_ui_hbox_child',
2444 className = 'cke_dialog_ui_hbox_first';
2445 if ( i == childHtmlList.length - 1 )
2446 className = 'cke_dialog_ui_hbox_last';
2447 html.push( '<td class="', className, '" role="presentation" ' );
2451 styles.push( 'width:' + cssLength( widths[i] ) );
2454 styles.push( 'width:' + Math.floor( 100 / childHtmlList.length ) + '%' );
2456 styles.push( 'height:' + cssLength( height ) );
2457 if ( elementDefinition && elementDefinition.padding != undefined )
2458 styles.push( 'padding:' + cssLength( elementDefinition.padding ) );
2459 // In IE Quirks alignment has to be done on table cells. (#7324)
2460 if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && children[ i ].align )
2461 styles.push( 'text-align:' + children[ i ].align );
2462 if ( styles.length > 0 )
2463 html.push( 'style="' + styles.join('; ') + '" ' );
2464 html.push( '>', childHtmlList[i], '</td>' );
2466 html.push( '</tr></tbody>' );
2467 return html.join( '' );
2470 var attribs = { role : 'presentation' };
2471 elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align );
2473 CKEDITOR.ui.dialog.uiElement.call(
2476 elementDefinition || { type : 'hbox' },
2485 * Vertical layout box for dialog UI elements.
2487 * @extends CKEDITOR.ui.dialog.hbox
2488 * @param {CKEDITOR.dialog} dialog
2489 * Parent dialog object.
2490 * @param {Array} childObjList
2491 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this
2493 * @param {Array} childHtmlList
2494 * Array of HTML code that correspond to the HTML output of all the
2495 * objects in childObjList.
2496 * @param {Array} htmlList
2497 * Array of HTML code that this element will output to.
2498 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
2499 * The element definition. Accepted fields:
2501 * <li><strong>width</strong> (Optional) The width of the layout.</li>
2502 * <li><strong>heights</strong> (Optional) The heights of individual cells.
2504 * <li><strong>align</strong> (Optional) The alignment of the layout.</li>
2505 * <li><strong>padding</strong> (Optional) The padding width inside child
2507 * <li><strong>expand</strong> (Optional) Whether the layout should expand
2508 * vertically to fill its container.</li>
2512 vbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition )
2514 if ( arguments.length < 3 )
2517 this._ || ( this._ = {} );
2519 var children = this._.children = childObjList,
2520 width = elementDefinition && elementDefinition.width || null,
2521 heights = elementDefinition && elementDefinition.heights || null;
2523 var innerHTML = function()
2525 var html = [ '<table role="presentation" cellspacing="0" border="0" ' ];
2526 html.push( 'style="' );
2527 if ( elementDefinition && elementDefinition.expand )
2528 html.push( 'height:100%;' );
2529 html.push( 'width:' + cssLength( width || '100%' ), ';' );
2531 html.push( 'align="', CKEDITOR.tools.htmlEncode(
2532 ( elementDefinition && elementDefinition.align ) || ( dialog.getParentEditor().lang.dir == 'ltr' ? 'left' : 'right' ) ), '" ' );
2534 html.push( '><tbody>' );
2535 for ( var i = 0 ; i < childHtmlList.length ; i++ )
2538 html.push( '<tr><td role="presentation" ' );
2540 styles.push( 'width:' + cssLength( width || '100%' ) );
2542 styles.push( 'height:' + cssLength( heights[i] ) );
2543 else if ( elementDefinition && elementDefinition.expand )
2544 styles.push( 'height:' + Math.floor( 100 / childHtmlList.length ) + '%' );
2545 if ( elementDefinition && elementDefinition.padding != undefined )
2546 styles.push( 'padding:' + cssLength( elementDefinition.padding ) );
2547 // In IE Quirks alignment has to be done on table cells. (#7324)
2548 if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && children[ i ].align )
2549 styles.push( 'text-align:' + children[ i ].align );
2550 if ( styles.length > 0 )
2551 html.push( 'style="', styles.join( '; ' ), '" ' );
2552 html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], '</td></tr>' );
2554 html.push( '</tbody></table>' );
2555 return html.join( '' );
2557 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML );
2562 CKEDITOR.ui.dialog.uiElement.prototype =
2565 * Gets the root DOM element of this dialog UI object.
2566 * @returns {CKEDITOR.dom.element} Root DOM element of UI object.
2568 * uiElement.getElement().hide();
2570 getElement : function()
2572 return CKEDITOR.document.getById( this.domId );
2576 * Gets the DOM element that the user inputs values.
2577 * This function is used by setValue(), getValue() and focus(). It should
2578 * be overrided in child classes where the input element isn't the root
2580 * @returns {CKEDITOR.dom.element} The element where the user input values.
2582 * var rawValue = textInput.getInputElement().$.value;
2584 getInputElement : function()
2586 return this.getElement();
2590 * Gets the parent dialog object containing this UI element.
2591 * @returns {CKEDITOR.dialog} Parent dialog object.
2593 * var dialog = uiElement.getDialog();
2595 getDialog : function()
2597 return this._.dialog;
2601 * Sets the value of this dialog UI object.
2602 * @param {Object} value The new value.
2603 * @param {Boolean} noChangeEvent Internal commit, to supress 'change' event on this element.
2604 * @returns {CKEDITOR.dialog.uiElement} The current UI element.
2606 * uiElement.setValue( 'Dingo' );
2608 setValue : function( value, noChangeEvent )
2610 this.getInputElement().setValue( value );
2611 !noChangeEvent && this.fire( 'change', { value : value } );
2616 * Gets the current value of this dialog UI object.
2617 * @returns {Object} The current value.
2619 * var myValue = uiElement.getValue();
2621 getValue : function()
2623 return this.getInputElement().getValue();
2627 * Tells whether the UI object's value has changed.
2628 * @returns {Boolean} true if changed, false if not changed.
2630 * if ( uiElement.isChanged() )
2631 * confirm( 'Value changed! Continue?' );
2633 isChanged : function()
2635 // Override in input classes.
2640 * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods.
2641 * @returns {CKEDITOR.dialog.uiElement} The current UI element.
2643 * focus : function()
2645 * this.selectParentTab();
2646 * // do something else.
2649 selectParentTab : function()
2651 var element = this.getInputElement(),
2654 while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 )
2657 // Some widgets don't have parent tabs (e.g. OK and Cancel buttons).
2661 tabId = cursor.getAttribute( 'name' );
2662 // Avoid duplicate select.
2663 if ( this._.dialog._.currentTabId != tabId )
2664 this._.dialog.selectPage( tabId );
2669 * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page.
2670 * @returns {CKEDITOR.dialog.uiElement} The current UI element.
2672 * uiElement.focus();
2676 this.selectParentTab().getInputElement().focus();
2681 * Registers the on* event handlers defined in the element definition.
2682 * The default behavior of this function is:
2685 * If the on* event is defined in the class's eventProcesors list,
2686 * then the registration is delegated to the corresponding function
2687 * in the eventProcessors list.
2690 * If the on* event is not defined in the eventProcessors list, then
2691 * register the event handler under the corresponding DOM event of
2692 * the UI element's input DOM element (as defined by the return value
2693 * of {@link CKEDITOR.ui.dialog.uiElement#getInputElement}).
2696 * This function is only called at UI element instantiation, but can
2697 * be overridded in child classes if they require more flexibility.
2698 * @param {CKEDITOR.dialog.definition.uiElement} definition The UI element
2700 * @returns {CKEDITOR.dialog.uiElement} The current UI element.
2703 registerEvents : function( definition )
2705 var regex = /^on([A-Z]\w+)/,
2708 var registerDomEvent = function( uiElement, dialog, eventName, func )
2710 dialog.on( 'load', function()
2712 uiElement.getInputElement().on( eventName, func, uiElement );
2716 for ( var i in definition )
2718 if ( !( match = i.match( regex ) ) )
2720 if ( this.eventProcessors[i] )
2721 this.eventProcessors[i].call( this, this._.dialog, definition[i] );
2723 registerDomEvent( this, this._.dialog, match[1].toLowerCase(), definition[i] );
2730 * The event processor list used by
2731 * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element
2732 * instantiation. The default list defines three on* events:
2734 * <li>onLoad - Called when the element's parent dialog opens for the
2736 * <li>onShow - Called whenever the element's parent dialog opens.</li>
2737 * <li>onHide - Called whenever the element's parent dialog closes.</li>
2742 * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick
2743 * // handlers in the UI element's definitions.
2744 * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {},
2745 * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
2746 * { onClick : function( dialog, func ) { this.on( 'click', func ); } },
2747 * true );
2751 onLoad : function( dialog, func )
2753 dialog.on( 'load', func, this );
2756 onShow : function( dialog, func )
2758 dialog.on( 'show', func, this );
2761 onHide : function( dialog, func )
2763 dialog.on( 'hide', func, this );
2768 * The default handler for a UI element's access key down event, which
2769 * tries to put focus to the UI element.<br />
2770 * Can be overridded in child classes for more sophisticaed behavior.
2771 * @param {CKEDITOR.dialog} dialog The parent dialog object.
2772 * @param {String} key The key combination pressed. Since access keys
2773 * are defined to always include the CTRL key, its value should always
2774 * include a 'CTRL+' prefix.
2777 accessKeyDown : function( dialog, key )
2783 * The default handler for a UI element's access key up event, which
2784 * does nothing.<br />
2785 * Can be overridded in child classes for more sophisticated behavior.
2786 * @param {CKEDITOR.dialog} dialog The parent dialog object.
2787 * @param {String} key The key combination pressed. Since access keys
2788 * are defined to always include the CTRL key, its value should always
2789 * include a 'CTRL+' prefix.
2792 accessKeyUp : function( dialog, key )
2797 * Disables a UI element.
2800 disable : function()
2802 var element = this.getElement(),
2803 input = this.getInputElement();
2804 input.setAttribute( 'disabled', 'true' );
2805 element.addClass( 'cke_disabled' );
2809 * Enables a UI element.
2814 var element = this.getElement(),
2815 input = this.getInputElement();
2816 input.removeAttribute( 'disabled' );
2817 element.removeClass( 'cke_disabled' );
2821 * Determines whether an UI element is enabled or not.
2822 * @returns {Boolean} Whether the UI element is enabled.
2825 isEnabled : function()
2827 return !this.getElement().hasClass( 'cke_disabled' );
2831 * Determines whether an UI element is visible or not.
2832 * @returns {Boolean} Whether the UI element is visible.
2835 isVisible : function()
2837 return this.getInputElement().isVisible();
2841 * Determines whether an UI element is focus-able or not.
2842 * Focus-able is defined as being both visible and enabled.
2843 * @returns {Boolean} Whether the UI element can be focused.
2846 isFocusable : function()
2848 if ( !this.isEnabled() || !this.isVisible() )
2854 CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
2856 * @lends CKEDITOR.ui.dialog.hbox.prototype
2860 * Gets a child UI element inside this container.
2861 * @param {Array|Number} indices An array or a single number to indicate the child's
2862 * position in the container's descendant tree. Omit to get all the children in an array.
2863 * @returns {Array|CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container
2864 * if no argument given, or the specified UI element if indices is given.
2866 * var checkbox = hbox.getChild( [0,1] );
2867 * checkbox.setValue( true );
2869 getChild : function( indices )
2871 // If no arguments, return a clone of the children array.
2872 if ( arguments.length < 1 )
2873 return this._.children.concat();
2875 // If indices isn't array, make it one.
2876 if ( !indices.splice )
2877 indices = [ indices ];
2879 // Retrieve the child element according to tree position.
2880 if ( indices.length < 2 )
2881 return this._.children[ indices[0] ];
2883 return ( this._.children[ indices[0] ] && this._.children[ indices[0] ].getChild ) ?
2884 this._.children[ indices[0] ].getChild( indices.slice( 1, indices.length ) ) :
2889 CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox();
2895 var commonBuilder = {
2896 build : function( dialog, elementDefinition, output )
2898 var children = elementDefinition.children,
2902 for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ )
2905 childHtmlList.push( childHtml );
2906 childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) );
2908 return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, childObjList, childHtmlList, output, elementDefinition );
2912 CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder );
2913 CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder );
2917 * Generic dialog command. It opens a specific dialog when executed.
2919 * @augments CKEDITOR.commandDefinition
2920 * @param {string} dialogName The name of the dialog to open when executing
2923 * // Register the "link" command, which opens the "link" dialog.
2924 * editor.addCommand( 'link', <b>new CKEDITOR.dialogCommand( 'link' )</b> );
2926 CKEDITOR.dialogCommand = function( dialogName )
2928 this.dialogName = dialogName;
2931 CKEDITOR.dialogCommand.prototype =
2934 exec : function( editor )
2936 // Special treatment for Opera. (#8031)
2937 CKEDITOR.env.opera ?
2938 CKEDITOR.tools.setTimeout( function() { editor.openDialog( this.dialogName ) }, 0, this )
2939 : editor.openDialog( this.dialogName );
2942 // Dialog commands just open a dialog ui, thus require no undo logic,
2943 // undo support should dedicate to specific dialog implementation.
2946 editorFocus : CKEDITOR.env.ie || CKEDITOR.env.webkit
2951 var notEmptyRegex = /^([a]|[^a])+$/,
2952 integerRegex = /^\d*$/,
2953 numberRegex = /^\d*(?:\.\d+)?$/,
2954 htmlLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|\%)?)?$/,
2955 cssLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|em|ex|in|cm|mm|pt|pc|\%)?)?$/i,
2956 inlineStyleRegex = /^(\s*[\w-]+\s*:\s*[^:;]+(?:;|$))*$/;
2958 CKEDITOR.VALIDATE_OR = 1;
2959 CKEDITOR.VALIDATE_AND = 2;
2961 CKEDITOR.dialog.validate =
2963 functions : function()
2965 var args = arguments;
2969 * It's important for validate functions to be able to accept the value
2970 * as argument in addition to this.getValue(), so that it is possible to
2971 * combine validate functions together to make more sophisticated
2974 var value = this && this.getValue ? this.getValue() : args[ 0 ];
2976 var msg = undefined,
2977 relation = CKEDITOR.VALIDATE_AND,
2980 for ( i = 0 ; i < args.length ; i++ )
2982 if ( typeof( args[i] ) == 'function' )
2983 functions.push( args[i] );
2988 if ( i < args.length && typeof( args[i] ) == 'string' )
2994 if ( i < args.length && typeof( args[i]) == 'number' )
2997 var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false );
2998 for ( i = 0 ; i < functions.length ; i++ )
3000 if ( relation == CKEDITOR.VALIDATE_AND )
3001 passed = passed && functions[i]( value );
3003 passed = passed || functions[i]( value );
3006 return !passed ? msg : true;
3010 regex : function( regex, msg )
3013 * Can be greatly shortened by deriving from functions validator if code size
3014 * turns out to be more important than performance.
3018 var value = this && this.getValue ? this.getValue() : arguments[0];
3019 return !regex.test( value ) ? msg : true;
3023 notEmpty : function( msg )
3025 return this.regex( notEmptyRegex, msg );
3028 integer : function( msg )
3030 return this.regex( integerRegex, msg );
3033 'number' : function( msg )
3035 return this.regex( numberRegex, msg );
3038 'cssLength' : function( msg )
3040 return this.functions( function( val ){ return cssLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg );
3043 'htmlLength' : function( msg )
3045 return this.functions( function( val ){ return htmlLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg );
3048 'inlineStyle' : function( msg )
3050 return this.functions( function( val ){ return inlineStyleRegex.test( CKEDITOR.tools.trim( val ) ); }, msg );
3053 equals : function( value, msg )
3055 return this.functions( function( val ){ return val == value; }, msg );
3058 notEqual : function( value, msg )
3060 return this.functions( function( val ){ return val != value; }, msg );
3064 CKEDITOR.on( 'instanceDestroyed', function( evt )
3066 // Remove dialog cover on last instance destroy.
3067 if ( CKEDITOR.tools.isEmpty( CKEDITOR.instances ) )
3069 var currentTopDialog;
3070 while ( ( currentTopDialog = CKEDITOR.dialog._.currentTop ) )
3071 currentTopDialog.hide();
3075 var dialogs = evt.editor._.storedDialogs;
3076 for ( var name in dialogs )
3077 dialogs[ name ].destroy();
3083 // Extend the CKEDITOR.editor class with dialog specific functions.
3084 CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
3085 /** @lends CKEDITOR.editor.prototype */
3088 * Loads and opens a registered dialog.
3089 * @param {String} dialogName The registered name of the dialog.
3090 * @param {Function} callback The function to be invoked after dialog instance created.
3091 * @see CKEDITOR.dialog.add
3093 * CKEDITOR.instances.editor1.openDialog( 'smiley' );
3094 * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. null if the dialog name is not registered.
3096 openDialog : function( dialogName, callback )
3098 if ( this.mode == 'wysiwyg' && CKEDITOR.env.ie )
3100 var selection = this.getSelection();
3101 selection && selection.lock();
3104 var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ],
3105 dialogSkin = this.skin.dialog;
3107 if ( CKEDITOR.dialog._.currentTop === null )
3110 // If the dialogDefinition is already loaded, open it immediately.
3111 if ( typeof dialogDefinitions == 'function' && dialogSkin._isLoaded )
3113 var storedDialogs = this._.storedDialogs ||
3114 ( this._.storedDialogs = {} );
3116 var dialog = storedDialogs[ dialogName ] ||
3117 ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) );
3119 callback && callback.call( dialog, dialog );
3124 else if ( dialogDefinitions == 'failed' )
3127 throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName + '" failed when loading definition.' );
3132 function onDialogFileLoaded( success )
3134 var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ],
3135 skin = me.skin.dialog;
3137 // Check if both skin part and definition is loaded.
3138 if ( !skin._isLoaded || loadDefinition && typeof success == 'undefined' )
3141 // In case of plugin error, mark it as loading failed.
3142 if ( typeof dialogDefinition != 'function' )
3143 CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed';
3145 me.openDialog( dialogName, callback );
3148 if ( typeof dialogDefinitions == 'string' )
3150 var loadDefinition = 1;
3151 CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), onDialogFileLoaded, null, 0, 1 );
3154 CKEDITOR.skins.load( this, 'dialog', onDialogFileLoaded );
3161 CKEDITOR.plugins.add( 'dialog',
3163 requires : [ 'dialogui' ]
3166 // Dialog related configurations.
3169 * The color of the dialog background cover. It should be a valid CSS color
3171 * @name CKEDITOR.config.dialog_backgroundCoverColor
3175 * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)';
3179 * The opacity of the dialog background cover. It should be a number within the
3181 * @name CKEDITOR.config.dialog_backgroundCoverOpacity
3185 * config.dialog_backgroundCoverOpacity = 0.7;
3189 * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened.
3190 * @name CKEDITOR.config.dialog_startupFocusTab
3194 * config.dialog_startupFocusTab = true;
3198 * The distance of magnetic borders used in moving and resizing dialogs,
3199 * measured in pixels.
3200 * @name CKEDITOR.config.dialog_magnetDistance
3204 * config.dialog_magnetDistance = 30;
3208 * The guideline to follow when generating the dialog buttons. There are 3 possible options:
3210 * <li>'OS' - the buttons will be displayed in the default order of the user's OS;</li>
3211 * <li>'ltr' - for Left-To-Right order;</li>
3212 * <li>'rtl' - for Right-To-Left order.</li>
3214 * @name CKEDITOR.config.dialog_buttonsOrder
3219 * config.dialog_buttonsOrder = 'rtl';
3223 * The dialog contents to removed. It's a string composed by dialog name and tab name with a colon between them.
3224 * Separate each pair with semicolon (see example).
3225 * <b>Note: All names are case-sensitive.</b>
3226 * <b>Note: Be cautious when specifying dialog tabs that are mandatory, like "info", dialog functionality might be broken because of this!</b>
3227 * @name CKEDITOR.config.removeDialogTabs
3232 * config.removeDialogTabs = 'flash:advanced;image:Link';
3236 * Fired when a dialog definition is about to be used to create a dialog into
3237 * an editor instance. This event makes it possible to customize the definition
3238 * before creating it.
3239 * <p>Note that this event is called only the first time a specific dialog is
3240 * opened. Successive openings will use the cached dialog, and this event will
3241 * not get fired.</p>
3242 * @name CKEDITOR#dialogDefinition
3244 * @param {CKEDITOR.dialog.definition} data The dialog defination that
3246 * @param {CKEDITOR.editor} editor The editor instance that will use the
3251 * Fired when a tab is going to be selected in a dialog
3252 * @name CKEDITOR.dialog#selectPage
3254 * @param {String} page The id of the page that it's gonna be selected.
3255 * @param {String} currentPage The id of the current page.
3259 * Fired when the user tries to dismiss a dialog
3260 * @name CKEDITOR.dialog#cancel
3262 * @param {Boolean} hide Whether the event should proceed or not.
3266 * Fired when the user tries to confirm a dialog
3267 * @name CKEDITOR.dialog#ok
3269 * @param {Boolean} hide Whether the event should proceed or not.
3273 * Fired when a dialog is shown
3274 * @name CKEDITOR.dialog#show
3279 * Fired when a dialog is shown
3280 * @name CKEDITOR.editor#dialogShow
3285 * Fired when a dialog is hidden
3286 * @name CKEDITOR.dialog#hide
3291 * Fired when a dialog is hidden
3292 * @name CKEDITOR.editor#dialogHide
3297 * Fired when a dialog is being resized. The event is fired on
3298 * both the 'CKEDITOR.dialog' object and the dialog instance
3299 * since 3.5.3, previously it's available only in the global object.
3300 * @name CKEDITOR.dialog#resize
3303 * @param {CKEDITOR.dialog} dialog The dialog being resized (if
3304 * it's fired on the dialog itself, this parameter isn't sent).
3305 * @param {String} skin The skin name.
3306 * @param {Number} width The new width.
3307 * @param {Number} height The new height.