2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 * @fileOverview The floating dialog plugin.
11 * No resize for this dialog.
14 * @property {Number} [=0]
17 CKEDITOR
.DIALOG_RESIZE_NONE
= 0;
20 * Only allow horizontal resizing for this dialog, disable vertical resizing.
23 * @property {Number} [=1]
26 CKEDITOR
.DIALOG_RESIZE_WIDTH
= 1;
29 * Only allow vertical resizing for this dialog, disable horizontal resizing.
32 * @property {Number} [=2]
35 CKEDITOR
.DIALOG_RESIZE_HEIGHT
= 2;
38 * Allow the dialog to be resized in both directions.
41 * @property {Number} [=3]
44 CKEDITOR
.DIALOG_RESIZE_BOTH
= 3;
47 * Dialog state when idle.
50 * @property {Number} [=1]
53 CKEDITOR
.DIALOG_STATE_IDLE
= 1;
56 * Dialog state when busy.
59 * @property {Number} [=2]
62 CKEDITOR
.DIALOG_STATE_BUSY
= 2;
65 var cssLength
= CKEDITOR
.tools
.cssLength
;
67 function isTabVisible( tabId
) {
68 return !!this._
.tabs
[ tabId
][ 0 ].$.offsetHeight
;
71 function getPreviousVisibleTab() {
72 var tabId
= this._
.currentTabId
,
73 length
= this._
.tabIdList
.length
,
74 tabIndex
= CKEDITOR
.tools
.indexOf( this._
.tabIdList
, tabId
) + length
;
76 for ( var i
= tabIndex
- 1; i
> tabIndex
- length
; i
-- ) {
77 if ( isTabVisible
.call( this, this._
.tabIdList
[ i
% length
] ) )
78 return this._
.tabIdList
[ i
% length
];
84 function getNextVisibleTab() {
85 var tabId
= this._
.currentTabId
,
86 length
= this._
.tabIdList
.length
,
87 tabIndex
= CKEDITOR
.tools
.indexOf( this._
.tabIdList
, tabId
);
89 for ( var i
= tabIndex
+ 1; i
< tabIndex
+ length
; i
++ ) {
90 if ( isTabVisible
.call( this, this._
.tabIdList
[ i
% length
] ) )
91 return this._
.tabIdList
[ i
% length
];
98 function clearOrRecoverTextInputValue( container
, isRecover
) {
99 var inputs
= container
.$.getElementsByTagName( 'input' );
100 for ( var i
= 0, length
= inputs
.length
; i
< length
; i
++ ) {
101 var item
= new CKEDITOR
.dom
.element( inputs
[ i
] );
103 if ( item
.getAttribute( 'type' ).toLowerCase() == 'text' ) {
105 item
.setAttribute( 'value', item
.getCustomData( 'fake_value' ) || '' );
106 item
.removeCustomData( 'fake_value' );
108 item
.setCustomData( 'fake_value', item
.getAttribute( 'value' ) );
109 item
.setAttribute( 'value', '' );
115 // Handle dialog element validation state UI changes.
116 function handleFieldValidated( isValid
, msg
) {
117 var input
= this.getInputElement();
119 isValid
? input
.removeAttribute( 'aria-invalid' ) : input
.setAttribute( 'aria-invalid', true );
128 msg
&& alert( msg
); // jshint ignore:line
130 this.fire( 'validated', { valid: isValid
, msg: msg
} );
133 function resetField() {
134 var input
= this.getInputElement();
135 input
&& input
.removeAttribute( 'aria-invalid' );
138 var templateSource
= '<div class="cke_reset_all {editorId} {editorDialogClass} {hidpi}' +
139 '" dir="{langDir}"' +
140 ' lang="{langCode}"' +
142 ' aria-labelledby="cke_dialog_title_{id}"' +
144 '<table class="cke_dialog ' + CKEDITOR
.env
.cssClass
+ ' cke_{langDir}"' +
145 ' style="position:absolute" role="presentation">' +
146 '<tr><td role="presentation">' +
147 '<div class="cke_dialog_body" role="presentation">' +
148 '<div id="cke_dialog_title_{id}" class="cke_dialog_title" role="presentation"></div>' +
149 '<a id="cke_dialog_close_button_{id}" class="cke_dialog_close_button" href="javascript:void(0)" title="{closeTitle}" role="button"><span class="cke_label">X</span></a>' +
150 '<div id="cke_dialog_tabs_{id}" class="cke_dialog_tabs" role="tablist"></div>' +
151 '<table class="cke_dialog_contents" role="presentation">' +
153 '<td id="cke_dialog_contents_{id}" class="cke_dialog_contents_body" role="presentation"></td>' +
156 '<td id="cke_dialog_footer_{id}" class="cke_dialog_footer" role="presentation"></td>' +
164 function buildDialog( editor
) {
165 var element
= CKEDITOR
.dom
.element
.createFromHtml( CKEDITOR
.addTemplate( 'dialog', templateSource
).output( {
166 id: CKEDITOR
.tools
.getNextNumber(),
168 langDir: editor
.lang
.dir
,
169 langCode: editor
.langCode
,
170 editorDialogClass: 'cke_editor_' + editor
.name
.replace( /\./g, '\\.' ) + '_dialog',
171 closeTitle: editor
.lang
.common
.close
,
172 hidpi: CKEDITOR
.env
.hidpi
? 'cke_hidpi' : ''
175 // TODO: Change this to getById(), so it'll support custom templates.
176 var body
= element
.getChild( [ 0, 0, 0, 0, 0 ] ),
177 title
= body
.getChild( 0 ),
178 close
= body
.getChild( 1 );
180 // Don't allow dragging on dialog (#13184).
181 editor
.plugins
.clipboard
&& CKEDITOR
.plugins
.clipboard
.preventDefaultDropOnElement( body
);
183 // IFrame shim for dialog that masks activeX in IE. (#7619)
184 if ( CKEDITOR
.env
.ie
&& !CKEDITOR
.env
.quirks
&& !CKEDITOR
.env
.edge
) {
185 var src
= 'javascript:void(function(){' + encodeURIComponent( 'document.open();(' + CKEDITOR
.tools
.fixDomain
+ ')();document.close();' ) + '}())', // jshint ignore:line
186 iframe
= CKEDITOR
.dom
.element
.createFromHtml( '<iframe' +
188 ' class="cke_iframe_shim"' +
189 ' src="' + src
+ '"' +
192 iframe
.appendTo( body
.getParent() );
195 // Make the Title and Close Button unselectable.
196 title
.unselectable();
197 close
.unselectable();
202 dialog: element
.getChild( 0 ),
205 tabs: body
.getChild( 2 ),
206 contents: body
.getChild( [ 3, 0, 0, 0 ] ),
207 footer: body
.getChild( [ 3, 0, 1, 0 ] )
213 * This is the base class for runtime dialog objects. An instance of this
214 * class represents a single named dialog for a single editor instance.
216 * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' );
219 * @constructor Creates a dialog class instance.
220 * @param {Object} editor The editor which created the dialog.
221 * @param {String} dialogName The dialog's registered name.
223 CKEDITOR
.dialog = function( editor
, dialogName
) {
224 // Load the dialog definition.
225 var definition
= CKEDITOR
.dialog
._
.dialogDefinitions
[ dialogName
],
226 defaultDefinition
= CKEDITOR
.tools
.clone( defaultDialogDefinition
),
227 buttonsOrder
= editor
.config
.dialog_buttonsOrder
|| 'OS',
228 dir
= editor
.lang
.dir
,
230 i
, processed
, stopPropagation
;
232 if ( ( buttonsOrder
== 'OS' && CKEDITOR
.env
.mac
) || // The buttons in MacOS Apps are in reverse order (#4750)
233 ( buttonsOrder
== 'rtl' && dir
== 'ltr' ) || ( buttonsOrder
== 'ltr' && dir
== 'rtl' ) )
234 defaultDefinition
.buttons
.reverse();
237 // Completes the definition with the default values.
238 definition
= CKEDITOR
.tools
.extend( definition( editor
), defaultDefinition
);
240 // Clone a functionally independent copy for this dialog.
241 definition
= CKEDITOR
.tools
.clone( definition
);
243 // Create a complex definition object, extending it with the API
245 definition
= new definitionObject( this, definition
);
247 var themeBuilt
= buildDialog( editor
);
249 // Initialize some basic parameters.
252 element: themeBuilt
.element
,
254 contentSize: { width: 0, height: 0 },
255 size: { width: 0, height: 0 },
260 // Initialize the tab and page map.
264 currentTabIndex: null,
269 // Initialize the tab order array for input widgets.
271 currentFocusIndex: 0,
275 this.parts
= themeBuilt
.parts
;
277 CKEDITOR
.tools
.setTimeout( function() {
278 editor
.fire( 'ariaWidget', this.parts
.contents
);
281 // Set the startup styles for the dialog, avoiding it enlarging the
282 // page size on the dialog creation.
284 position: CKEDITOR
.env
.ie6Compat
? 'absolute' : 'fixed',
289 startStyles
[ dir
== 'rtl' ? 'right' : 'left' ] = 0;
290 this.parts
.dialog
.setStyles( startStyles
);
293 // Call the CKEDITOR.event constructor to initialize this instance.
294 CKEDITOR
.event
.call( this );
296 // Fire the "dialogDefinition" event, making it possible to customize
297 // the dialog definition.
298 this.definition
= definition
= CKEDITOR
.fire( 'dialogDefinition', {
300 definition: definition
301 }, editor
).definition
;
303 // Cache tabs that should be removed.
304 if ( !( 'removeDialogTabs' in editor
._
) && editor
.config
.removeDialogTabs
) {
305 var removeContents
= editor
.config
.removeDialogTabs
.split( ';' );
307 for ( i
= 0; i
< removeContents
.length
; i
++ ) {
308 var parts
= removeContents
[ i
].split( ':' );
309 if ( parts
.length
== 2 ) {
310 var removeDialogName
= parts
[ 0 ];
311 if ( !tabsToRemove
[ removeDialogName
] )
312 tabsToRemove
[ removeDialogName
] = [];
313 tabsToRemove
[ removeDialogName
].push( parts
[ 1 ] );
316 editor
._
.removeDialogTabs
= tabsToRemove
;
319 // Remove tabs of this dialog.
320 if ( editor
._
.removeDialogTabs
&& ( tabsToRemove
= editor
._
.removeDialogTabs
[ dialogName
] ) ) {
321 for ( i
= 0; i
< tabsToRemove
.length
; i
++ )
322 definition
.removeContents( tabsToRemove
[ i
] );
325 // Initialize load, show, hide, ok and cancel events.
326 if ( definition
.onLoad
)
327 this.on( 'load', definition
.onLoad
);
329 if ( definition
.onShow
)
330 this.on( 'show', definition
.onShow
);
332 if ( definition
.onHide
)
333 this.on( 'hide', definition
.onHide
);
335 if ( definition
.onOk
) {
336 this.on( 'ok', function( evt
) {
337 // Dialog confirm might probably introduce content changes (#5415).
338 editor
.fire( 'saveSnapshot' );
339 setTimeout( function() {
340 editor
.fire( 'saveSnapshot' );
342 if ( definition
.onOk
.call( this, evt
) === false )
343 evt
.data
.hide
= false;
347 // Set default dialog state.
348 this.state
= CKEDITOR
.DIALOG_STATE_IDLE
;
350 if ( definition
.onCancel
) {
351 this.on( 'cancel', function( evt
) {
352 if ( definition
.onCancel
.call( this, evt
) === false )
353 evt
.data
.hide
= false;
359 // Iterates over all items inside all content in the dialog, calling a
360 // function for each of them.
361 var iterContents = function( func
) {
362 var contents
= me
._
.contents
,
365 for ( var i
in contents
) {
366 for ( var j
in contents
[ i
] ) {
367 stop
= func
.call( this, contents
[ i
][ j
] );
374 this.on( 'ok', function( evt
) {
375 iterContents( function( item
) {
376 if ( item
.validate
) {
377 var retval
= item
.validate( this ),
378 invalid
= ( typeof retval
== 'string' ) || retval
=== false;
381 evt
.data
.hide
= false;
385 handleFieldValidated
.call( item
, !invalid
, typeof retval
== 'string' ? retval : undefined );
391 this.on( 'cancel', function( evt
) {
392 iterContents( function( item
) {
393 if ( item
.isChanged() ) {
394 if ( !editor
.config
.dialog_noConfirmCancel
&& !confirm( editor
.lang
.common
.confirmCancel
) ) // jshint ignore:line
395 evt
.data
.hide
= false;
401 this.parts
.close
.on( 'click', function( evt
) {
402 if ( this.fire( 'cancel', { hide: true } ).hide
!== false )
404 evt
.data
.preventDefault();
407 // Sort focus list according to tab order definitions.
408 function setupFocus() {
409 var focusList
= me
._
.focusList
;
410 focusList
.sort( function( a
, b
) {
411 // Mimics browser tab order logics;
412 if ( a
.tabIndex
!= b
.tabIndex
)
413 return b
.tabIndex
- a
.tabIndex
;
414 // Sort is not stable in some browsers,
415 // fall-back the comparator to 'focusIndex';
417 return a
.focusIndex
- b
.focusIndex
;
420 var size
= focusList
.length
;
421 for ( var i
= 0; i
< size
; i
++ )
422 focusList
[ i
].focusIndex
= i
;
425 // Expects 1 or -1 as an offset, meaning direction of the offset change.
426 function changeFocus( offset
) {
427 var focusList
= me
._
.focusList
;
428 offset
= offset
|| 0;
430 if ( focusList
.length
< 1 )
433 var startIndex
= me
._
.currentFocusIndex
;
435 if ( me
._
.tabBarMode
&& offset
< 0 ) {
436 // If we are in tab mode, we need to mimic that we started tabbing back from the first
437 // focusList (so it will go to the last one).
441 // Trigger the 'blur' event of any input element before anything,
442 // since certain UI updates may depend on it.
444 focusList
[ startIndex
].getInputElement().$.blur();
447 var currentIndex
= startIndex
,
448 hasTabs
= me
._
.pageCount
> 1;
451 currentIndex
= currentIndex
+ offset
;
453 if ( hasTabs
&& !me
._
.tabBarMode
&& ( currentIndex
== focusList
.length
|| currentIndex
== -1 ) ) {
454 // If the dialog was not in tab mode, then focus the first tab (#13027).
455 me
._
.tabBarMode
= true;
456 me
._
.tabs
[ me
._
.currentTabId
][ 0 ].focus();
457 me
._
.currentFocusIndex
= -1;
459 // Early return, in order to avoid accessing focusList[ -1 ].
463 currentIndex
= ( currentIndex
+ focusList
.length
) % focusList
.length
;
465 if ( currentIndex
== startIndex
) {
468 } while ( offset
&& !focusList
[ currentIndex
].isFocusable() );
470 focusList
[ currentIndex
].focus();
472 // Select whole field content.
473 if ( focusList
[ currentIndex
].type
== 'text' )
474 focusList
[ currentIndex
].select();
477 this.changeFocus
= changeFocus
;
480 function keydownHandler( evt
) {
481 // If I'm not the top dialog, ignore.
482 if ( me
!= CKEDITOR
.dialog
._
.currentTop
)
485 var keystroke
= evt
.data
.getKeystroke(),
486 rtl
= editor
.lang
.dir
== 'rtl',
487 arrowKeys
= [ 37, 38, 39, 40 ],
490 processed
= stopPropagation
= 0;
492 if ( keystroke
== 9 || keystroke
== CKEDITOR
.SHIFT
+ 9 ) {
493 var shiftPressed
= ( keystroke
== CKEDITOR
.SHIFT
+ 9 );
494 changeFocus( shiftPressed
? -1 : 1 );
496 } else if ( keystroke
== CKEDITOR
.ALT
+ 121 && !me
._
.tabBarMode
&& me
.getPageCount() > 1 ) {
497 // Alt-F10 puts focus into the current tab item in the tab bar.
498 me
._
.tabBarMode
= true;
499 me
._
.tabs
[ me
._
.currentTabId
][ 0 ].focus();
500 me
._
.currentFocusIndex
= -1;
502 } else if ( CKEDITOR
.tools
.indexOf( arrowKeys
, keystroke
) != -1 && me
._
.tabBarMode
) {
503 // Array with key codes that activate previous tab.
505 // Depending on the lang dir: right or left key
507 // Top/bot arrow: actually for both cases it's the same.
510 nextId
= CKEDITOR
.tools
.indexOf( prevKeyCodes
, keystroke
) != -1 ? getPreviousVisibleTab
.call( me
) : getNextVisibleTab
.call( me
);
512 me
.selectPage( nextId
);
513 me
._
.tabs
[ nextId
][ 0 ].focus();
515 } else if ( ( keystroke
== 13 || keystroke
== 32 ) && me
._
.tabBarMode
) {
516 this.selectPage( this._
.currentTabId
);
517 this._
.tabBarMode
= false;
518 this._
.currentFocusIndex
= -1;
522 // If user presses enter key in a text box, it implies clicking OK for the dialog.
523 else if ( keystroke
== 13 /*ENTER*/ ) {
524 // Don't do that for a target that handles ENTER.
525 var target
= evt
.data
.getTarget();
526 if ( !target
.is( 'a', 'button', 'select', 'textarea' ) && ( !target
.is( 'input' ) || target
.$.type
!= 'button' ) ) {
527 button
= this.getButton( 'ok' );
528 button
&& CKEDITOR
.tools
.setTimeout( button
.click
, 0, button
);
531 stopPropagation
= 1; // Always block the propagation (#4269)
532 } else if ( keystroke
== 27 /*ESC*/ ) {
533 button
= this.getButton( 'cancel' );
535 // If there's a Cancel button, click it, else just fire the cancel event and hide the dialog.
537 CKEDITOR
.tools
.setTimeout( button
.click
, 0, button
);
539 if ( this.fire( 'cancel', { hide: true } ).hide
!== false )
542 stopPropagation
= 1; // Always block the propagation (#4269)
547 keypressHandler( evt
);
550 function keypressHandler( evt
) {
552 evt
.data
.preventDefault( 1 );
553 else if ( stopPropagation
)
554 evt
.data
.stopPropagation();
557 var dialogElement
= this._
.element
;
559 editor
.focusManager
.add( dialogElement
, 1 );
561 // Add the dialog keyboard handlers.
562 this.on( 'show', function() {
563 dialogElement
.on( 'keydown', keydownHandler
, this );
565 // Some browsers instead, don't cancel key events in the keydown, but in the
566 // keypress. So we must do a longer trip in those cases. (#4531,#8985)
567 if ( CKEDITOR
.env
.gecko
)
568 dialogElement
.on( 'keypress', keypressHandler
, this );
571 this.on( 'hide', function() {
572 dialogElement
.removeListener( 'keydown', keydownHandler
);
573 if ( CKEDITOR
.env
.gecko
)
574 dialogElement
.removeListener( 'keypress', keypressHandler
);
576 // Reset fields state when closing dialog.
577 iterContents( function( item
) {
578 resetField
.apply( item
);
581 this.on( 'iframeAdded', function( evt
) {
582 var doc
= new CKEDITOR
.dom
.document( evt
.data
.iframe
.$.contentWindow
.document
);
583 doc
.on( 'keydown', keydownHandler
, this, null, 0 );
586 // Auto-focus logic in dialog.
587 this.on( 'show', function() {
588 // Setup tabIndex on showing the dialog instead of on loading
589 // to allow dynamic tab order happen in dialog definition.
592 var hasTabs
= me
._
.pageCount
> 1;
594 if ( editor
.config
.dialog_startupFocusTab
&& hasTabs
) {
595 me
._
.tabBarMode
= true;
596 me
._
.tabs
[ me
._
.currentTabId
][ 0 ].focus();
597 me
._
.currentFocusIndex
= -1;
598 } else if ( !this._
.hasFocus
) {
599 // http://dev.ckeditor.com/ticket/13114#comment:4.
600 this._
.currentFocusIndex
= hasTabs
? -1 : this._
.focusList
.length
- 1;
602 // Decide where to put the initial focus.
603 if ( definition
.onFocus
) {
604 var initialFocus
= definition
.onFocus
.call( this );
605 // Focus the field that the user specified.
606 initialFocus
&& initialFocus
.focus();
608 // Focus the first field in layout order.
613 }, this, null, 0xffffffff );
615 // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661).
616 // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken.
617 if ( CKEDITOR
.env
.ie6Compat
) {
618 this.on( 'load', function() {
619 var outer
= this.getElement(),
620 inner
= outer
.getFirst();
622 inner
.appendTo( outer
);
626 initDragAndDrop( this );
627 initResizeHandles( this );
630 ( new CKEDITOR
.dom
.text( definition
.title
, CKEDITOR
.document
) ).appendTo( this.parts
.title
);
632 // Insert the tabs and contents.
633 for ( i
= 0; i
< definition
.contents
.length
; i
++ ) {
634 var page
= definition
.contents
[ i
];
635 page
&& this.addPage( page
);
638 this.parts
.tabs
.on( 'click', function( evt
) {
639 var target
= evt
.data
.getTarget();
640 // If we aren't inside a tab, bail out.
641 if ( target
.hasClass( 'cke_dialog_tab' ) ) {
642 // Get the ID of the tab, without the 'cke_' prefix and the unique number suffix.
643 var id
= target
.$.id
;
644 this.selectPage( id
.substring( 4, id
.lastIndexOf( '_' ) ) );
646 if ( this._
.tabBarMode
) {
647 this._
.tabBarMode
= false;
648 this._
.currentFocusIndex
= -1;
651 evt
.data
.preventDefault();
656 var buttonsHtml
= [],
657 buttons
= CKEDITOR
.dialog
._
.uiElementBuilders
.hbox
.build( this, {
659 className: 'cke_dialog_footer_buttons',
661 children: definition
.buttons
662 }, buttonsHtml
).getChild();
663 this.parts
.footer
.setHtml( buttonsHtml
.join( '' ) );
665 for ( i
= 0; i
< buttons
.length
; i
++ )
666 this._
.buttons
[ buttons
[ i
].id
] = buttons
[ i
];
669 * Current state of the dialog. Use the {@link #setState} method to update it.
670 * See the {@link #event-state} event to know more.
673 * @property {Number} [state=CKEDITOR.DIALOG_STATE_IDLE]
677 // Focusable interface. Use it via dialog.addFocusable.
678 function Focusable( dialog
, element
, index
) {
679 this.element
= element
;
680 this.focusIndex
= index
;
681 // TODO: support tabIndex for focusables.
683 this.isFocusable = function() {
684 return !element
.getAttribute( 'disabled' ) && element
.isVisible();
686 this.focus = function() {
687 dialog
._
.currentFocusIndex
= this.focusIndex
;
688 this.element
.focus();
691 element
.on( 'keydown', function( e
) {
692 if ( e
.data
.getKeystroke() in { 32: 1, 13: 1 } )
693 this.fire( 'click' );
695 element
.on( 'focus', function() {
696 this.fire( 'mouseover' );
698 element
.on( 'blur', function() {
699 this.fire( 'mouseout' );
703 // Re-layout the dialog on window resize.
704 function resizeWithWindow( dialog
) {
705 var win
= CKEDITOR
.document
.getWindow();
706 function resizeHandler() {
709 win
.on( 'resize', resizeHandler
);
710 dialog
.on( 'hide', function() {
711 win
.removeListener( 'resize', resizeHandler
);
715 CKEDITOR
.dialog
.prototype = {
716 destroy: function() {
718 this._
.element
.remove();
722 * Resizes the dialog.
724 * dialogObj.resize( 800, 640 );
727 * @param {Number} width The width of the dialog in pixels.
728 * @param {Number} height The height of the dialog in pixels.
730 resize: ( function() {
731 return function( width
, height
) {
732 if ( this._
.contentSize
&& this._
.contentSize
.width
== width
&& this._
.contentSize
.height
== height
)
735 CKEDITOR
.dialog
.fire( 'resize', {
741 this.fire( 'resize', {
746 var contents
= this.parts
.contents
;
747 contents
.setStyles( {
749 height: height
+ 'px'
752 // Update dialog position when dimension get changed in RTL.
753 if ( this._
.editor
.lang
.dir
== 'rtl' && this._
.position
)
754 this._
.position
.x
= CKEDITOR
.document
.getWindow().getViewPaneSize().width
- this._
.contentSize
.width
- parseInt( this._
.element
.getFirst().getStyle( 'right' ), 10 );
756 this._
.contentSize
= { width: width
, height: height
};
761 * Gets the current size of the dialog in pixels.
763 * var width = dialogObj.getSize().width;
766 * @returns {Number} return.width
767 * @returns {Number} return.height
769 getSize: function() {
770 var element
= this._
.element
.getFirst();
771 return { width: element
.$.offsetWidth
|| 0, height: element
.$.offsetHeight
|| 0 };
775 * Moves the dialog to an `(x, y)` coordinate relative to the window.
777 * dialogObj.move( 10, 40 );
780 * @param {Number} x The target x-coordinate.
781 * @param {Number} y The target y-coordinate.
782 * @param {Boolean} save Flag indicate whether the dialog position should be remembered on next open up.
784 move: function( x
, y
, save
) {
786 // The dialog may be fixed positioned or absolute positioned. Ask the
787 // browser what is the current situation first.
788 var element
= this._
.element
.getFirst(), rtl
= this._
.editor
.lang
.dir
== 'rtl';
789 var isFixed
= element
.getComputedStyle( 'position' ) == 'fixed';
791 // (#8888) In some cases of a very small viewport, dialog is incorrectly
792 // positioned in IE7. It also happens that it remains sticky and user cannot
793 // scroll down/up to reveal dialog's content below/above the viewport; this is
795 // The only way to fix this is to move mouse out of the browser and
796 // go back to see that dialog position is automagically fixed. No events,
797 // no style change - pure magic. This is a IE7 rendering issue, which can be
798 // fixed with dummy style redraw on each move.
799 if ( CKEDITOR
.env
.ie
)
800 element
.setStyle( 'zoom', '100%' );
802 if ( isFixed
&& this._
.position
&& this._
.position
.x
== x
&& this._
.position
.y
== y
)
805 // Save the current position.
806 this._
.position
= { x: x
, y: y
};
808 // If not fixed positioned, add scroll position to the coordinates.
810 var scrollPosition
= CKEDITOR
.document
.getWindow().getScrollPosition();
811 x
+= scrollPosition
.x
;
812 y
+= scrollPosition
.y
;
815 // Translate coordinate for RTL.
817 var dialogSize
= this.getSize(), viewPaneSize
= CKEDITOR
.document
.getWindow().getViewPaneSize();
818 x
= viewPaneSize
.width
- dialogSize
.width
- x
;
821 var styles
= { 'top': ( y
> 0 ? y : 0 ) + 'px' };
822 styles
[ rtl
? 'right' : 'left' ] = ( x
> 0 ? x : 0 ) + 'px';
824 element
.setStyles( styles
);
826 save
&& ( this._
.moved
= 1 );
830 * Gets the dialog's position in the window.
832 * var dialogX = dialogObj.getPosition().x;
835 * @returns {Number} return.x
836 * @returns {Number} return.y
838 getPosition: function() {
839 return CKEDITOR
.tools
.extend( {}, this._
.position
);
843 * Shows the dialog box.
848 // Insert the dialog's element to the root document.
849 var element
= this._
.element
;
850 var definition
= this.definition
;
851 if ( !( element
.getParent() && element
.getParent().equals( CKEDITOR
.document
.getBody() ) ) )
852 element
.appendTo( CKEDITOR
.document
.getBody() );
854 element
.setStyle( 'display', 'block' );
856 // First, set the dialog to an appropriate size.
858 this._
.contentSize
&& this._
.contentSize
.width
|| definition
.width
|| definition
.minWidth
,
859 this._
.contentSize
&& this._
.contentSize
.height
|| definition
.height
|| definition
.minHeight
862 // Reset all inputs back to their default value.
865 // Select the first tab by default.
866 this.selectPage( this.definition
.contents
[ 0 ].id
);
869 if ( CKEDITOR
.dialog
._
.currentZIndex
=== null )
870 CKEDITOR
.dialog
._
.currentZIndex
= this._
.editor
.config
.baseFloatZIndex
;
871 this._
.element
.getFirst().setStyle( 'z-index', CKEDITOR
.dialog
._
.currentZIndex
+= 10 );
873 // Maintain the dialog ordering and dialog cover.
874 if ( CKEDITOR
.dialog
._
.currentTop
=== null ) {
875 CKEDITOR
.dialog
._
.currentTop
= this;
876 this._
.parentDialog
= null;
877 showCover( this._
.editor
);
880 this._
.parentDialog
= CKEDITOR
.dialog
._
.currentTop
;
881 var parentElement
= this._
.parentDialog
.getElement().getFirst();
882 parentElement
.$.style
.zIndex
-= Math
.floor( this._
.editor
.config
.baseFloatZIndex
/ 2 );
883 CKEDITOR
.dialog
._
.currentTop
= this;
886 element
.on( 'keydown', accessKeyDownHandler
);
887 element
.on( 'keyup', accessKeyUpHandler
);
889 // Reset the hasFocus state.
890 this._
.hasFocus
= false;
892 for ( var i
in definition
.contents
) {
893 if ( !definition
.contents
[ i
] )
896 var content
= definition
.contents
[ i
],
897 tab
= this._
.tabs
[ content
.id
],
898 requiredContent
= content
.requiredContent
,
904 for ( var j
in this._
.contents
[ content
.id
] ) {
905 var elem
= this._
.contents
[ content
.id
][ j
];
907 if ( elem
.type
== 'hbox' || elem
.type
== 'vbox' || !elem
.getInputElement() )
910 if ( elem
.requiredContent
&& !this._
.editor
.activeFilter
.check( elem
.requiredContent
) )
918 if ( !enableElements
|| ( requiredContent
&& !this._
.editor
.activeFilter
.check( requiredContent
) ) )
919 tab
[ 0 ].addClass( 'cke_dialog_tab_disabled' );
921 tab
[ 0 ].removeClass( 'cke_dialog_tab_disabled' );
924 CKEDITOR
.tools
.setTimeout( function() {
926 resizeWithWindow( this );
928 this.parts
.dialog
.setStyle( 'visibility', '' );
930 // Execute onLoad for the first show.
931 this.fireOnce( 'load', {} );
932 CKEDITOR
.ui
.fire( 'ready', this );
934 this.fire( 'show', {} );
935 this._
.editor
.fire( 'dialogShow', this );
937 if ( !this._
.parentDialog
)
938 this._
.editor
.focusManager
.lock();
940 // Save the initial values of the dialog.
941 this.foreach( function( contentObj
) {
942 contentObj
.setInitValue
&& contentObj
.setInitValue();
949 * Rearrange the dialog to its previous position or the middle of the window.
954 var el
= this.parts
.dialog
;
955 var dialogSize
= this.getSize();
956 var win
= CKEDITOR
.document
.getWindow(),
957 viewSize
= win
.getViewPaneSize();
959 var posX
= ( viewSize
.width
- dialogSize
.width
) / 2,
960 posY
= ( viewSize
.height
- dialogSize
.height
) / 2;
962 // Switch to absolute position when viewport is smaller than dialog size.
963 if ( !CKEDITOR
.env
.ie6Compat
) {
964 if ( dialogSize
.height
+ ( posY
> 0 ? posY : 0 ) > viewSize
.height
|| dialogSize
.width
+ ( posX
> 0 ? posX : 0 ) > viewSize
.width
) {
965 el
.setStyle( 'position', 'absolute' );
967 el
.setStyle( 'position', 'fixed' );
971 this.move( this._
.moved
? this._
.position
.x : posX
, this._
.moved
? this._
.position
.y : posY
);
975 * Executes a function for each UI element.
977 * @param {Function} fn Function to execute for each UI element.
978 * @returns {CKEDITOR.dialog} The current dialog object.
980 foreach: function( fn
) {
981 for ( var i
in this._
.contents
) {
982 for ( var j
in this._
.contents
[ i
] ) {
983 fn
.call( this, this._
.contents
[i
][j
] );
991 * Resets all input values in the dialog.
998 reset: ( function() {
999 var fn = function( widget
) {
1011 * Calls the {@link CKEDITOR.dialog.definition.uiElement#setup} method of each
1012 * of the UI elements, with the arguments passed through it.
1013 * It is usually being called when the dialog is opened, to put the initial value inside the field.
1015 * dialogObj.setupContent();
1017 * var timestamp = ( new Date() ).valueOf();
1018 * dialogObj.setupContent( timestamp );
1020 setupContent: function() {
1021 var args
= arguments
;
1022 this.foreach( function( widget
) {
1024 widget
.setup
.apply( widget
, args
);
1029 * Calls the {@link CKEDITOR.dialog.definition.uiElement#commit} method of each
1030 * of the UI elements, with the arguments passed through it.
1031 * It is usually being called when the user confirms the dialog, to process the values.
1033 * dialogObj.commitContent();
1035 * var timestamp = ( new Date() ).valueOf();
1036 * dialogObj.commitContent( timestamp );
1038 commitContent: function() {
1039 var args
= arguments
;
1040 this.foreach( function( widget
) {
1041 // Make sure IE triggers "change" event on last focused input before closing the dialog. (#7915)
1042 if ( CKEDITOR
.env
.ie
&& this._
.currentFocusIndex
== widget
.focusIndex
)
1043 widget
.getInputElement().$.blur();
1045 if ( widget
.commit
)
1046 widget
.commit
.apply( widget
, args
);
1051 * Hides the dialog box.
1056 if ( !this.parts
.dialog
.isVisible() )
1059 this.fire( 'hide', {} );
1060 this._
.editor
.fire( 'dialogHide', this );
1061 // Reset the tab page.
1062 this.selectPage( this._
.tabIdList
[ 0 ] );
1063 var element
= this._
.element
;
1064 element
.setStyle( 'display', 'none' );
1065 this.parts
.dialog
.setStyle( 'visibility', 'hidden' );
1066 // Unregister all access keys associated with this dialog.
1067 unregisterAccessKey( this );
1069 // Close any child(top) dialogs first.
1070 while ( CKEDITOR
.dialog
._
.currentTop
!= this )
1071 CKEDITOR
.dialog
._
.currentTop
.hide();
1073 // Maintain dialog ordering and remove cover if needed.
1074 if ( !this._
.parentDialog
)
1075 hideCover( this._
.editor
);
1077 var parentElement
= this._
.parentDialog
.getElement().getFirst();
1078 parentElement
.setStyle( 'z-index', parseInt( parentElement
.$.style
.zIndex
, 10 ) + Math
.floor( this._
.editor
.config
.baseFloatZIndex
/ 2 ) );
1080 CKEDITOR
.dialog
._
.currentTop
= this._
.parentDialog
;
1082 // Deduct or clear the z-index.
1083 if ( !this._
.parentDialog
) {
1084 CKEDITOR
.dialog
._
.currentZIndex
= null;
1086 // Remove access key handlers.
1087 element
.removeListener( 'keydown', accessKeyDownHandler
);
1088 element
.removeListener( 'keyup', accessKeyUpHandler
);
1090 var editor
= this._
.editor
;
1093 // Give a while before unlock, waiting for focus to return to the editable. (#172)
1094 setTimeout( function() {
1095 editor
.focusManager
.unlock();
1097 // Fixed iOS focus issue (#12381).
1098 // Keep in mind that editor.focus() does not work in this case.
1099 if ( CKEDITOR
.env
.iOS
) {
1100 editor
.window
.focus();
1105 CKEDITOR
.dialog
._
.currentZIndex
-= 10;
1108 delete this._
.parentDialog
;
1109 // Reset the initial values of the dialog.
1110 this.foreach( function( contentObj
) {
1111 contentObj
.resetInitValue
&& contentObj
.resetInitValue();
1114 // Reset dialog state back to IDLE, if busy (#13213).
1115 this.setState( CKEDITOR
.DIALOG_STATE_IDLE
);
1119 * Adds a tabbed page into the dialog.
1121 * @param {Object} contents Content definition.
1123 addPage: function( contents
) {
1124 if ( contents
.requiredContent
&& !this._
.editor
.filter
.check( contents
.requiredContent
) )
1128 titleHtml
= contents
.label
? ' title="' + CKEDITOR
.tools
.htmlEncode( contents
.label
) + '"' : '',
1129 vbox
= CKEDITOR
.dialog
._
.uiElementBuilders
.vbox
.build( this, {
1131 className: 'cke_dialog_page_contents',
1132 children: contents
.elements
,
1133 expand: !!contents
.expand
,
1134 padding: contents
.padding
,
1135 style: contents
.style
|| 'width: 100%;'
1138 var contentMap
= this._
.contents
[ contents
.id
] = {},
1140 children
= vbox
.getChild(),
1143 while ( ( cursor
= children
.shift() ) ) {
1144 // Count all allowed fields.
1145 if ( !cursor
.notAllowed
&& cursor
.type
!= 'hbox' && cursor
.type
!= 'vbox' )
1148 contentMap
[ cursor
.id
] = cursor
;
1149 if ( typeof cursor
.getChild
== 'function' )
1150 children
.push
.apply( children
, cursor
.getChild() );
1153 // If all fields are disabled (because they are not allowed) hide this tab.
1154 if ( !enabledFields
)
1155 contents
.hidden
= true;
1157 // Create the HTML for the tab and the content block.
1158 var page
= CKEDITOR
.dom
.element
.createFromHtml( pageHtml
.join( '' ) );
1159 page
.setAttribute( 'role', 'tabpanel' );
1161 var env
= CKEDITOR
.env
;
1162 var tabId
= 'cke_' + contents
.id
+ '_' + CKEDITOR
.tools
.getNextNumber(),
1163 tab
= CKEDITOR
.dom
.element
.createFromHtml( [
1164 '<a class="cke_dialog_tab"',
1165 ( this._
.pageCount
> 0 ? ' cke_last' : 'cke_first' ),
1167 ( !!contents
.hidden
? ' style="display:none"' : '' ),
1168 ' id="', tabId
, '"',
1169 env
.gecko
&& !env
.hc
? '' : ' href="javascript:void(0)"',
1171 ' hidefocus="true"',
1177 page
.setAttribute( 'aria-labelledby', tabId
);
1179 // Take records for the tabs and elements created.
1180 this._
.tabs
[ contents
.id
] = [ tab
, page
];
1181 this._
.tabIdList
.push( contents
.id
);
1182 !contents
.hidden
&& this._
.pageCount
++;
1183 this._
.lastTab
= tab
;
1186 // Attach the DOM nodes.
1188 page
.setAttribute( 'name', contents
.id
);
1189 page
.appendTo( this.parts
.contents
);
1192 this.parts
.tabs
.append( tab
);
1194 // Add access key handlers if access key is defined.
1195 if ( contents
.accessKey
) {
1196 registerAccessKey( this, this, 'CTRL+' + contents
.accessKey
, tabAccessKeyDown
, tabAccessKeyUp
);
1197 this._
.accessKeyMap
[ 'CTRL+' + contents
.accessKey
] = contents
.id
;
1202 * Activates a tab page in the dialog by its id.
1204 * dialogObj.selectPage( 'tab_1' );
1206 * @param {String} id The id of the dialog tab to be activated.
1208 selectPage: function( id
) {
1209 if ( this._
.currentTabId
== id
)
1212 if ( this._
.tabs
[ id
][ 0 ].hasClass( 'cke_dialog_tab_disabled' ) )
1215 // If event was canceled - do nothing.
1216 if ( this.fire( 'selectPage', { page: id
, currentPage: this._
.currentTabId
} ) === false )
1219 // Hide the non-selected tabs and pages.
1220 for ( var i
in this._
.tabs
) {
1221 var tab
= this._
.tabs
[ i
][ 0 ],
1222 page
= this._
.tabs
[ i
][ 1 ];
1224 tab
.removeClass( 'cke_dialog_tab_selected' );
1227 page
.setAttribute( 'aria-hidden', i
!= id
);
1230 var selected
= this._
.tabs
[ id
];
1231 selected
[ 0 ].addClass( 'cke_dialog_tab_selected' );
1233 // [IE] an invisible input[type='text'] will enlarge it's width
1234 // if it's value is long when it shows, so we clear it's value
1235 // before it shows and then recover it (#5649)
1236 if ( CKEDITOR
.env
.ie6Compat
|| CKEDITOR
.env
.ie7Compat
) {
1237 clearOrRecoverTextInputValue( selected
[ 1 ] );
1238 selected
[ 1 ].show();
1239 setTimeout( function() {
1240 clearOrRecoverTextInputValue( selected
[ 1 ], 1 );
1243 selected
[ 1 ].show();
1246 this._
.currentTabId
= id
;
1247 this._
.currentTabIndex
= CKEDITOR
.tools
.indexOf( this._
.tabIdList
, id
);
1251 * Dialog state-specific style updates.
1253 updateStyle: function() {
1254 // If only a single page shown, a different style is used in the central pane.
1255 this.parts
.dialog
[ ( this._
.pageCount
=== 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' );
1259 * Hides a page's tab away from the dialog.
1261 * dialog.hidePage( 'tab_3' );
1263 * @param {String} id The page's Id.
1265 hidePage: function( id
) {
1266 var tab
= this._
.tabs
[ id
] && this._
.tabs
[ id
][ 0 ];
1267 if ( !tab
|| this._
.pageCount
== 1 || !tab
.isVisible() )
1269 // Switch to other tab first when we're hiding the active tab.
1270 else if ( id
== this._
.currentTabId
)
1271 this.selectPage( getPreviousVisibleTab
.call( this ) );
1279 * Unhides a page's tab.
1281 * dialog.showPage( 'tab_2' );
1283 * @param {String} id The page's Id.
1285 showPage: function( id
) {
1286 var tab
= this._
.tabs
[ id
] && this._
.tabs
[ id
][ 0 ];
1295 * Gets the root DOM element of the dialog.
1297 * var dialogElement = dialogObj.getElement().getFirst();
1298 * dialogElement.setStyle( 'padding', '5px' );
1300 * @returns {CKEDITOR.dom.element} The `<span>` element containing this dialog.
1302 getElement: function() {
1303 return this._
.element
;
1307 * Gets the name of the dialog.
1309 * var dialogName = dialogObj.getName();
1311 * @returns {String} The name of this dialog.
1313 getName: function() {
1318 * Gets a dialog UI element object from a dialog page.
1320 * dialogObj.getContentElement( 'tabId', 'elementId' ).setValue( 'Example' );
1322 * @param {String} pageId id of dialog page.
1323 * @param {String} elementId id of UI element.
1324 * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element.
1326 getContentElement: function( pageId
, elementId
) {
1327 var page
= this._
.contents
[ pageId
];
1328 return page
&& page
[ elementId
];
1332 * Gets the value of a dialog UI element.
1334 * alert( dialogObj.getValueOf( 'tabId', 'elementId' ) );
1336 * @param {String} pageId id of dialog page.
1337 * @param {String} elementId id of UI element.
1338 * @returns {Object} The value of the UI element.
1340 getValueOf: function( pageId
, elementId
) {
1341 return this.getContentElement( pageId
, elementId
).getValue();
1345 * Sets the value of a dialog UI element.
1347 * dialogObj.setValueOf( 'tabId', 'elementId', 'Example' );
1349 * @param {String} pageId id of the dialog page.
1350 * @param {String} elementId id of the UI element.
1351 * @param {Object} value The new value of the UI element.
1353 setValueOf: function( pageId
, elementId
, value
) {
1354 return this.getContentElement( pageId
, elementId
).setValue( value
);
1358 * Gets the UI element of a button in the dialog's button row.
1360 * @returns {CKEDITOR.ui.dialog.button} The button object.
1362 * @param {String} id The id of the button.
1364 getButton: function( id
) {
1365 return this._
.buttons
[ id
];
1369 * Simulates a click to a dialog button in the dialog's button row.
1371 * @returns The return value of the dialog's `click` event.
1373 * @param {String} id The id of the button.
1375 click: function( id
) {
1376 return this._
.buttons
[ id
].click();
1380 * Disables a dialog button.
1382 * @param {String} id The id of the button.
1384 disableButton: function( id
) {
1385 return this._
.buttons
[ id
].disable();
1389 * Enables a dialog button.
1391 * @param {String} id The id of the button.
1393 enableButton: function( id
) {
1394 return this._
.buttons
[ id
].enable();
1398 * Gets the number of pages in the dialog.
1400 * @returns {Number} Page count.
1402 getPageCount: function() {
1403 return this._
.pageCount
;
1407 * Gets the editor instance which opened this dialog.
1409 * @returns {CKEDITOR.editor} Parent editor instances.
1411 getParentEditor: function() {
1412 return this._
.editor
;
1416 * Gets the element that was selected when opening the dialog, if any.
1418 * @returns {CKEDITOR.dom.element} The element that was selected, or `null`.
1420 getSelectedElement: function() {
1421 return this.getParentEditor().getSelection().getSelectedElement();
1425 * Adds element to dialog's focusable list.
1427 * @param {CKEDITOR.dom.element} element
1428 * @param {Number} [index]
1430 addFocusable: function( element
, index
) {
1431 if ( typeof index
== 'undefined' ) {
1432 index
= this._
.focusList
.length
;
1433 this._
.focusList
.push( new Focusable( this, element
, index
) );
1435 this._
.focusList
.splice( index
, 0, new Focusable( this, element
, index
) );
1436 for ( var i
= index
+ 1; i
< this._
.focusList
.length
; i
++ )
1437 this._
.focusList
[ i
].focusIndex
++;
1442 * Sets the dialog {@link #property-state}.
1445 * @param {Number} state Either {@link CKEDITOR#DIALOG_STATE_IDLE} or {@link CKEDITOR#DIALOG_STATE_BUSY}.
1447 setState: function( state
) {
1448 var oldState
= this.state
;
1450 if ( oldState
== state
) {
1456 if ( state
== CKEDITOR
.DIALOG_STATE_BUSY
) {
1457 // Insert the spinner on demand.
1458 if ( !this.parts
.spinner
) {
1459 var dir
= this.getParentEditor().lang
.dir
,
1462 'class': 'cke_dialog_spinner'
1465 'float': dir
== 'rtl' ? 'right' : 'left'
1469 spinnerDef
.styles
[ 'margin-' + ( dir
== 'rtl' ? 'left' : 'right' ) ] = '8px';
1471 this.parts
.spinner
= CKEDITOR
.document
.createElement( 'div', spinnerDef
);
1473 this.parts
.spinner
.setHtml( '⌛' );
1474 this.parts
.spinner
.appendTo( this.parts
.title
, 1 );
1477 // Finally, show the spinner.
1478 this.parts
.spinner
.show();
1480 this.getButton( 'ok' ).disable();
1481 } else if ( state
== CKEDITOR
.DIALOG_STATE_IDLE
) {
1482 // Hide the spinner. But don't do anything if there is no spinner yet.
1483 this.parts
.spinner
&& this.parts
.spinner
.hide();
1485 this.getButton( 'ok' ).enable();
1488 this.fire( 'state', state
);
1492 CKEDITOR
.tools
.extend( CKEDITOR
.dialog
, {
1494 * Registers a dialog.
1496 * // Full sample plugin, which does not only register a dialog window but also adds an item to the context menu.
1497 * // To open the dialog window, choose "Open dialog" in the context menu.
1498 * CKEDITOR.plugins.add( 'myplugin', {
1499 * init: function( editor ) {
1500 * editor.addCommand( 'mydialog',new CKEDITOR.dialogCommand( 'mydialog' ) );
1502 * if ( editor.contextMenu ) {
1503 * editor.addMenuGroup( 'mygroup', 10 );
1504 * editor.addMenuItem( 'My Dialog', {
1505 * label: 'Open dialog',
1506 * command: 'mydialog',
1509 * editor.contextMenu.addListener( function( element ) {
1510 * return { 'My Dialog': CKEDITOR.TRISTATE_OFF };
1514 * CKEDITOR.dialog.add( 'mydialog', function( api ) {
1515 * // CKEDITOR.dialog.definition
1516 * var dialogDefinition = {
1517 * title: 'Sample dialog',
1530 * html: '<p>This is some sample HTML content.</p>'
1541 * buttons: [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ],
1542 * onOk: function() {
1543 * // "this" is now a CKEDITOR.dialog object.
1544 * // Accessing dialog elements:
1545 * var textareaObj = this.getContentElement( 'tab1', 'textareaId' );
1546 * alert( "You have entered: " + textareaObj.getValue() );
1550 * return dialogDefinition;
1555 * CKEDITOR.replace( 'editor1', { extraPlugins: 'myplugin' } );
1558 * @param {String} name The dialog's name.
1559 * @param {Function/String} dialogDefinition
1560 * A function returning the dialog's definition, or the URL to the `.js` file holding the function.
1561 * The function should accept an argument `editor` which is the current editor instance, and
1562 * return an object conforming to {@link CKEDITOR.dialog.definition}.
1563 * @see CKEDITOR.dialog.definition
1565 add: function( name
, dialogDefinition
) {
1566 // Avoid path registration from multiple instances override definition.
1567 if ( !this._
.dialogDefinitions
[ name
] || typeof dialogDefinition
== 'function' )
1568 this._
.dialogDefinitions
[ name
] = dialogDefinition
;
1575 exists: function( name
) {
1576 return !!this._
.dialogDefinitions
[ name
];
1583 getCurrent: function() {
1584 return CKEDITOR
.dialog
._
.currentTop
;
1588 * Check whether tab wasn't removed by {@link CKEDITOR.config#removeDialogTabs}.
1592 * @param {CKEDITOR.editor} editor
1593 * @param {String} dialogName
1594 * @param {String} tabName
1595 * @returns {Boolean}
1597 isTabEnabled: function( editor
, dialogName
, tabName
) {
1598 var cfg
= editor
.config
.removeDialogTabs
;
1600 return !( cfg
&& cfg
.match( new RegExp( '(?:^|;)' + dialogName
+ ':' + tabName
+ '(?:$|;)', 'i' ) ) );
1604 * The default OK button for dialogs. Fires the `ok` event and closes the dialog if the event succeeds.
1609 okButton: ( function() {
1610 var retval = function( editor
, override
) {
1611 override
= override
|| {};
1612 return CKEDITOR
.tools
.extend( {
1615 label: editor
.lang
.common
.ok
,
1616 'class': 'cke_dialog_ui_button_ok',
1617 onClick: function( evt
) {
1618 var dialog
= evt
.data
.dialog
;
1619 if ( dialog
.fire( 'ok', { hide: true } ).hide
!== false )
1622 }, override
, true );
1624 retval
.type
= 'button';
1625 retval
.override = function( override
) {
1626 return CKEDITOR
.tools
.extend( function( editor
) {
1627 return retval( editor
, override
);
1628 }, { type: 'button' }, true );
1634 * The default cancel button for dialogs. Fires the `cancel` event and
1635 * closes the dialog if no UI element value changed.
1640 cancelButton: ( function() {
1641 var retval = function( editor
, override
) {
1642 override
= override
|| {};
1643 return CKEDITOR
.tools
.extend( {
1646 label: editor
.lang
.common
.cancel
,
1647 'class': 'cke_dialog_ui_button_cancel',
1648 onClick: function( evt
) {
1649 var dialog
= evt
.data
.dialog
;
1650 if ( dialog
.fire( 'cancel', { hide: true } ).hide
!== false )
1653 }, override
, true );
1655 retval
.type
= 'button';
1656 retval
.override = function( override
) {
1657 return CKEDITOR
.tools
.extend( function( editor
) {
1658 return retval( editor
, override
);
1659 }, { type: 'button' }, true );
1665 * Registers a dialog UI element.
1668 * @param {String} typeName The name of the UI element.
1669 * @param {Function} builder The function to build the UI element.
1671 addUIElement: function( typeName
, builder
) {
1672 this._
.uiElementBuilders
[ typeName
] = builder
;
1676 CKEDITOR
.dialog
._
= {
1677 uiElementBuilders: {},
1679 dialogDefinitions: {},
1686 // "Inherit" (copy actually) from CKEDITOR.event.
1687 CKEDITOR
.event
.implementOn( CKEDITOR
.dialog
);
1688 CKEDITOR
.event
.implementOn( CKEDITOR
.dialog
.prototype );
1690 var defaultDialogDefinition
= {
1691 resizable: CKEDITOR
.DIALOG_RESIZE_BOTH
,
1694 buttons: [ CKEDITOR
.dialog
.okButton
, CKEDITOR
.dialog
.cancelButton
]
1697 // Tool function used to return an item from an array based on its id
1699 var getById = function( array
, id
, recurse
) {
1700 for ( var i
= 0, item
;
1701 ( item
= array
[ i
] ); i
++ ) {
1702 if ( item
.id
== id
)
1704 if ( recurse
&& item
[ recurse
] ) {
1705 var retval
= getById( item
[ recurse
], id
, recurse
);
1713 // Tool function used to add an item into an array.
1714 var addById = function( array
, newItem
, nextSiblingId
, recurse
, nullIfNotFound
) {
1715 if ( nextSiblingId
) {
1716 for ( var i
= 0, item
;
1717 ( item
= array
[ i
] ); i
++ ) {
1718 if ( item
.id
== nextSiblingId
) {
1719 array
.splice( i
, 0, newItem
);
1723 if ( recurse
&& item
[ recurse
] ) {
1724 var retval
= addById( item
[ recurse
], newItem
, nextSiblingId
, recurse
, true );
1730 if ( nullIfNotFound
)
1734 array
.push( newItem
);
1738 // Tool function used to remove an item from an array based on its id.
1739 var removeById = function( array
, id
, recurse
) {
1740 for ( var i
= 0, item
;
1741 ( item
= array
[ i
] ); i
++ ) {
1742 if ( item
.id
== id
)
1743 return array
.splice( i
, 1 );
1744 if ( recurse
&& item
[ recurse
] ) {
1745 var retval
= removeById( item
[ recurse
], id
, recurse
);
1754 * This class is not really part of the API. It is the `definition` property value
1755 * passed to `dialogDefinition` event handlers.
1757 * CKEDITOR.on( 'dialogDefinition', function( evt ) {
1758 * var definition = evt.data.definition;
1759 * var content = definition.getContents( 'page1' );
1764 * @class CKEDITOR.dialog.definitionObject
1765 * @extends CKEDITOR.dialog.definition
1766 * @constructor Creates a definitionObject class instance.
1768 var definitionObject = function( dialog
, dialogDefinition
) {
1769 // TODO : Check if needed.
1770 this.dialog
= dialog
;
1772 // Transform the contents entries in contentObjects.
1773 var contents
= dialogDefinition
.contents
;
1774 for ( var i
= 0, content
;
1775 ( content
= contents
[ i
] ); i
++ )
1776 contents
[ i
] = content
&& new contentObject( dialog
, content
);
1778 CKEDITOR
.tools
.extend( this, dialogDefinition
);
1781 definitionObject
.prototype = {
1783 * Gets a content definition.
1785 * @param {String} id The id of the content definition.
1786 * @returns {CKEDITOR.dialog.definition.content} The content definition matching id.
1788 getContents: function( id
) {
1789 return getById( this.contents
, id
);
1793 * Gets a button definition.
1795 * @param {String} id The id of the button definition.
1796 * @returns {CKEDITOR.dialog.definition.button} The button definition matching id.
1798 getButton: function( id
) {
1799 return getById( this.buttons
, id
);
1803 * Adds a content definition object under this dialog definition.
1805 * @param {CKEDITOR.dialog.definition.content} contentDefinition The
1806 * content definition.
1807 * @param {String} [nextSiblingId] The id of an existing content
1808 * definition which the new content definition will be inserted
1809 * before. Omit if the new content definition is to be inserted as
1811 * @returns {CKEDITOR.dialog.definition.content} The inserted content definition.
1813 addContents: function( contentDefinition
, nextSiblingId
) {
1814 return addById( this.contents
, contentDefinition
, nextSiblingId
);
1818 * Adds a button definition object under this dialog definition.
1820 * @param {CKEDITOR.dialog.definition.button} buttonDefinition The
1821 * button definition.
1822 * @param {String} [nextSiblingId] The id of an existing button
1823 * definition which the new button definition will be inserted
1824 * before. Omit if the new button definition is to be inserted as
1826 * @returns {CKEDITOR.dialog.definition.button} The inserted button definition.
1828 addButton: function( buttonDefinition
, nextSiblingId
) {
1829 return addById( this.buttons
, buttonDefinition
, nextSiblingId
);
1833 * Removes a content definition from this dialog definition.
1835 * @param {String} id The id of the content definition to be removed.
1836 * @returns {CKEDITOR.dialog.definition.content} The removed content definition.
1838 removeContents: function( id
) {
1839 removeById( this.contents
, id
);
1843 * Removes a button definition from the dialog definition.
1845 * @param {String} id The id of the button definition to be removed.
1846 * @returns {CKEDITOR.dialog.definition.button} The removed button definition.
1848 removeButton: function( id
) {
1849 removeById( this.buttons
, id
);
1854 * This class is not really part of the API. It is the template of the
1855 * objects representing content pages inside the
1856 * CKEDITOR.dialog.definitionObject.
1858 * CKEDITOR.on( 'dialogDefinition', function( evt ) {
1859 * var definition = evt.data.definition;
1860 * var content = definition.getContents( 'page1' );
1861 * content.remove( 'textInput1' );
1866 * @class CKEDITOR.dialog.definition.contentObject
1867 * @constructor Creates a contentObject class instance.
1869 function contentObject( dialog
, contentDefinition
) {
1874 CKEDITOR
.tools
.extend( this, contentDefinition
);
1877 contentObject
.prototype = {
1879 * Gets a UI element definition under the content definition.
1881 * @param {String} id The id of the UI element definition.
1882 * @returns {CKEDITOR.dialog.definition.uiElement}
1884 get: function( id
) {
1885 return getById( this.elements
, id
, 'children' );
1889 * Adds a UI element definition to the content definition.
1891 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition The
1892 * UI elemnet definition to be added.
1893 * @param {String} nextSiblingId The id of an existing UI element
1894 * definition which the new UI element definition will be inserted
1895 * before. Omit if the new button definition is to be inserted as
1897 * @returns {CKEDITOR.dialog.definition.uiElement} The element definition inserted.
1899 add: function( elementDefinition
, nextSiblingId
) {
1900 return addById( this.elements
, elementDefinition
, nextSiblingId
, 'children' );
1904 * Removes a UI element definition from the content definition.
1906 * @param {String} id The id of the UI element definition to be removed.
1907 * @returns {CKEDITOR.dialog.definition.uiElement} The element definition removed.
1909 remove: function( id
) {
1910 removeById( this.elements
, id
, 'children' );
1914 function initDragAndDrop( dialog
) {
1915 var lastCoords
= null,
1916 abstractDialogCoords
= null,
1917 editor
= dialog
.getParentEditor(),
1918 magnetDistance
= editor
.config
.dialog_magnetDistance
,
1919 margins
= CKEDITOR
.skin
.margins
|| [ 0, 0, 0, 0 ];
1921 if ( typeof magnetDistance
== 'undefined' )
1922 magnetDistance
= 20;
1924 function mouseMoveHandler( evt
) {
1925 var dialogSize
= dialog
.getSize(),
1926 viewPaneSize
= CKEDITOR
.document
.getWindow().getViewPaneSize(),
1927 x
= evt
.data
.$.screenX
,
1928 y
= evt
.data
.$.screenY
,
1929 dx
= x
- lastCoords
.x
,
1930 dy
= y
- lastCoords
.y
,
1933 lastCoords
= { x: x
, y: y
};
1934 abstractDialogCoords
.x
+= dx
;
1935 abstractDialogCoords
.y
+= dy
;
1937 if ( abstractDialogCoords
.x
+ margins
[ 3 ] < magnetDistance
)
1938 realX
= -margins
[ 3 ];
1939 else if ( abstractDialogCoords
.x
- margins
[ 1 ] > viewPaneSize
.width
- dialogSize
.width
- magnetDistance
)
1940 realX
= viewPaneSize
.width
- dialogSize
.width
+ ( editor
.lang
.dir
== 'rtl' ? 0 : margins
[ 1 ] );
1942 realX
= abstractDialogCoords
.x
;
1944 if ( abstractDialogCoords
.y
+ margins
[ 0 ] < magnetDistance
)
1945 realY
= -margins
[ 0 ];
1946 else if ( abstractDialogCoords
.y
- margins
[ 2 ] > viewPaneSize
.height
- dialogSize
.height
- magnetDistance
)
1947 realY
= viewPaneSize
.height
- dialogSize
.height
+ margins
[ 2 ];
1949 realY
= abstractDialogCoords
.y
;
1951 dialog
.move( realX
, realY
, 1 );
1953 evt
.data
.preventDefault();
1956 function mouseUpHandler() {
1957 CKEDITOR
.document
.removeListener( 'mousemove', mouseMoveHandler
);
1958 CKEDITOR
.document
.removeListener( 'mouseup', mouseUpHandler
);
1960 if ( CKEDITOR
.env
.ie6Compat
) {
1961 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
1962 coverDoc
.removeListener( 'mousemove', mouseMoveHandler
);
1963 coverDoc
.removeListener( 'mouseup', mouseUpHandler
);
1967 dialog
.parts
.title
.on( 'mousedown', function( evt
) {
1968 lastCoords
= { x: evt
.data
.$.screenX
, y: evt
.data
.$.screenY
};
1970 CKEDITOR
.document
.on( 'mousemove', mouseMoveHandler
);
1971 CKEDITOR
.document
.on( 'mouseup', mouseUpHandler
);
1972 abstractDialogCoords
= dialog
.getPosition();
1974 if ( CKEDITOR
.env
.ie6Compat
) {
1975 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
1976 coverDoc
.on( 'mousemove', mouseMoveHandler
);
1977 coverDoc
.on( 'mouseup', mouseUpHandler
);
1980 evt
.data
.preventDefault();
1984 function initResizeHandles( dialog
) {
1985 var def
= dialog
.definition
,
1986 resizable
= def
.resizable
;
1988 if ( resizable
== CKEDITOR
.DIALOG_RESIZE_NONE
)
1991 var editor
= dialog
.getParentEditor();
1992 var wrapperWidth
, wrapperHeight
, viewSize
, origin
, startSize
, dialogCover
;
1994 var mouseDownFn
= CKEDITOR
.tools
.addFunction( function( $event
) {
1995 startSize
= dialog
.getSize();
1997 var content
= dialog
.parts
.contents
,
1998 iframeDialog
= content
.$.getElementsByTagName( 'iframe' ).length
;
2000 // Shim to help capturing "mousemove" over iframe.
2001 if ( iframeDialog
) {
2002 dialogCover
= CKEDITOR
.dom
.element
.createFromHtml( '<div class="cke_dialog_resize_cover" style="height: 100%; position: absolute; width: 100%;"></div>' );
2003 content
.append( dialogCover
);
2006 // Calculate the offset between content and chrome size.
2007 wrapperHeight
= startSize
.height
- dialog
.parts
.contents
.getSize( 'height', !( CKEDITOR
.env
.gecko
|| CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
) );
2008 wrapperWidth
= startSize
.width
- dialog
.parts
.contents
.getSize( 'width', 1 );
2010 origin
= { x: $event
.screenX
, y: $event
.screenY
};
2012 viewSize
= CKEDITOR
.document
.getWindow().getViewPaneSize();
2014 CKEDITOR
.document
.on( 'mousemove', mouseMoveHandler
);
2015 CKEDITOR
.document
.on( 'mouseup', mouseUpHandler
);
2017 if ( CKEDITOR
.env
.ie6Compat
) {
2018 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
2019 coverDoc
.on( 'mousemove', mouseMoveHandler
);
2020 coverDoc
.on( 'mouseup', mouseUpHandler
);
2023 $event
.preventDefault
&& $event
.preventDefault();
2026 // Prepend the grip to the dialog.
2027 dialog
.on( 'load', function() {
2029 if ( resizable
== CKEDITOR
.DIALOG_RESIZE_WIDTH
)
2030 direction
= ' cke_resizer_horizontal';
2031 else if ( resizable
== CKEDITOR
.DIALOG_RESIZE_HEIGHT
)
2032 direction
= ' cke_resizer_vertical';
2033 var resizer
= CKEDITOR
.dom
.element
.createFromHtml(
2035 ' class="cke_resizer' + direction
+ ' cke_resizer_' + editor
.lang
.dir
+ '"' +
2036 ' title="' + CKEDITOR
.tools
.htmlEncode( editor
.lang
.common
.resize
) + '"' +
2037 ' onmousedown="CKEDITOR.tools.callFunction(' + mouseDownFn
+ ', event )">' +
2038 // BLACK LOWER RIGHT TRIANGLE (ltr)
2039 // BLACK LOWER LEFT TRIANGLE (rtl)
2040 ( editor
.lang
.dir
== 'ltr' ? '\u25E2' : '\u25E3' ) +
2042 dialog
.parts
.footer
.append( resizer
, 1 );
2044 editor
.on( 'destroy', function() {
2045 CKEDITOR
.tools
.removeFunction( mouseDownFn
);
2048 function mouseMoveHandler( evt
) {
2049 var rtl
= editor
.lang
.dir
== 'rtl',
2050 dx
= ( evt
.data
.$.screenX
- origin
.x
) * ( rtl
? -1 : 1 ),
2051 dy
= evt
.data
.$.screenY
- origin
.y
,
2052 width
= startSize
.width
,
2053 height
= startSize
.height
,
2054 internalWidth
= width
+ dx
* ( dialog
._
.moved
? 1 : 2 ),
2055 internalHeight
= height
+ dy
* ( dialog
._
.moved
? 1 : 2 ),
2056 element
= dialog
._
.element
.getFirst(),
2057 right
= rtl
&& element
.getComputedStyle( 'right' ),
2058 position
= dialog
.getPosition();
2060 if ( position
.y
+ internalHeight
> viewSize
.height
)
2061 internalHeight
= viewSize
.height
- position
.y
;
2063 if ( ( rtl
? right : position
.x
) + internalWidth
> viewSize
.width
)
2064 internalWidth
= viewSize
.width
- ( rtl
? right : position
.x
);
2066 // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL.
2067 if ( ( resizable
== CKEDITOR
.DIALOG_RESIZE_WIDTH
|| resizable
== CKEDITOR
.DIALOG_RESIZE_BOTH
) )
2068 width
= Math
.max( def
.minWidth
|| 0, internalWidth
- wrapperWidth
);
2070 if ( resizable
== CKEDITOR
.DIALOG_RESIZE_HEIGHT
|| resizable
== CKEDITOR
.DIALOG_RESIZE_BOTH
)
2071 height
= Math
.max( def
.minHeight
|| 0, internalHeight
- wrapperHeight
);
2073 dialog
.resize( width
, height
);
2075 if ( !dialog
._
.moved
)
2078 evt
.data
.preventDefault();
2081 function mouseUpHandler() {
2082 CKEDITOR
.document
.removeListener( 'mouseup', mouseUpHandler
);
2083 CKEDITOR
.document
.removeListener( 'mousemove', mouseMoveHandler
);
2085 if ( dialogCover
) {
2086 dialogCover
.remove();
2090 if ( CKEDITOR
.env
.ie6Compat
) {
2091 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
2092 coverDoc
.removeListener( 'mouseup', mouseUpHandler
);
2093 coverDoc
.removeListener( 'mousemove', mouseMoveHandler
);
2099 // Caching resuable covers and allowing only one cover
2104 function cancelEvent( ev
) {
2105 ev
.data
.preventDefault( 1 );
2108 function showCover( editor
) {
2109 var win
= CKEDITOR
.document
.getWindow();
2110 var config
= editor
.config
,
2111 backgroundColorStyle
= config
.dialog_backgroundCoverColor
|| 'white',
2112 backgroundCoverOpacity
= config
.dialog_backgroundCoverOpacity
,
2113 baseFloatZIndex
= config
.baseFloatZIndex
,
2114 coverKey
= CKEDITOR
.tools
.genKey( backgroundColorStyle
, backgroundCoverOpacity
, baseFloatZIndex
),
2115 coverElement
= covers
[ coverKey
];
2117 if ( !coverElement
) {
2119 '<div tabIndex="-1" style="position: ', ( CKEDITOR
.env
.ie6Compat
? 'absolute' : 'fixed' ),
2120 '; z-index: ', baseFloatZIndex
,
2121 '; top: 0px; left: 0px; ',
2122 ( !CKEDITOR
.env
.ie6Compat
? 'background-color: ' + backgroundColorStyle : '' ),
2123 '" class="cke_dialog_background_cover">'
2126 if ( CKEDITOR
.env
.ie6Compat
) {
2127 // Support for custom document.domain in IE.
2128 var iframeHtml
= '<html><body style=\\\'background-color:' + backgroundColorStyle
+ ';\\\'></body></html>';
2130 html
.push( '<iframe' +
2131 ' hidefocus="true"' +
2132 ' frameborder="0"' +
2133 ' id="cke_dialog_background_iframe"' +
2134 ' src="javascript:' );
2136 html
.push( 'void((function(){' + encodeURIComponent(
2137 'document.open();' +
2138 // Support for custom document.domain in IE.
2139 '(' + CKEDITOR
.tools
.fixDomain
+ ')();' +
2140 'document.write( \'' + iframeHtml
+ '\' );' +
2146 'position:absolute;' +
2151 'filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0)">' +
2155 html
.push( '</div>' );
2157 coverElement
= CKEDITOR
.dom
.element
.createFromHtml( html
.join( '' ) );
2158 coverElement
.setOpacity( backgroundCoverOpacity
!== undefined ? backgroundCoverOpacity : 0.5 );
2160 coverElement
.on( 'keydown', cancelEvent
);
2161 coverElement
.on( 'keypress', cancelEvent
);
2162 coverElement
.on( 'keyup', cancelEvent
);
2164 coverElement
.appendTo( CKEDITOR
.document
.getBody() );
2165 covers
[ coverKey
] = coverElement
;
2167 coverElement
.show();
2170 // Makes the dialog cover a focus holder as well.
2171 editor
.focusManager
.add( coverElement
);
2173 currentCover
= coverElement
;
2174 var resizeFunc = function() {
2175 var size
= win
.getViewPaneSize();
2176 coverElement
.setStyles( {
2177 width: size
.width
+ 'px',
2178 height: size
.height
+ 'px'
2182 var scrollFunc = function() {
2183 var pos
= win
.getScrollPosition(),
2184 cursor
= CKEDITOR
.dialog
._
.currentTop
;
2185 coverElement
.setStyles( {
2192 var dialogPos
= cursor
.getPosition();
2193 cursor
.move( dialogPos
.x
, dialogPos
.y
);
2194 } while ( ( cursor
= cursor
._
.parentDialog
) );
2198 resizeCover
= resizeFunc
;
2199 win
.on( 'resize', resizeFunc
);
2201 // Using Safari/Mac, focus must be kept where it is (#7027)
2202 if ( !( CKEDITOR
.env
.mac
&& CKEDITOR
.env
.webkit
) )
2203 coverElement
.focus();
2205 if ( CKEDITOR
.env
.ie6Compat
) {
2206 // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll.
2207 // So we need to invent a really funny way to make it work.
2208 var myScrollHandler = function() {
2210 arguments
.callee
.prevScrollHandler
.apply( this, arguments
);
2212 win
.$.setTimeout( function() {
2213 myScrollHandler
.prevScrollHandler
= window
.onscroll
||
2215 window
.onscroll
= myScrollHandler
;
2221 function hideCover( editor
) {
2222 if ( !currentCover
)
2225 editor
.focusManager
.remove( currentCover
);
2226 var win
= CKEDITOR
.document
.getWindow();
2227 currentCover
.hide();
2228 win
.removeListener( 'resize', resizeCover
);
2230 if ( CKEDITOR
.env
.ie6Compat
) {
2231 win
.$.setTimeout( function() {
2232 var prevScrollHandler
= window
.onscroll
&& window
.onscroll
.prevScrollHandler
;
2233 window
.onscroll
= prevScrollHandler
|| null;
2239 function removeCovers() {
2240 for ( var coverId
in covers
)
2241 covers
[ coverId
].remove();
2245 var accessKeyProcessors
= {};
2247 var accessKeyDownHandler = function( evt
) {
2248 var ctrl
= evt
.data
.$.ctrlKey
|| evt
.data
.$.metaKey
,
2249 alt
= evt
.data
.$.altKey
,
2250 shift
= evt
.data
.$.shiftKey
,
2251 key
= String
.fromCharCode( evt
.data
.$.keyCode
),
2252 keyProcessor
= accessKeyProcessors
[ ( ctrl
? 'CTRL+' : '' ) + ( alt
? 'ALT+' : '' ) + ( shift
? 'SHIFT+' : '' ) + key
];
2254 if ( !keyProcessor
|| !keyProcessor
.length
)
2257 keyProcessor
= keyProcessor
[ keyProcessor
.length
- 1 ];
2258 keyProcessor
.keydown
&& keyProcessor
.keydown
.call( keyProcessor
.uiElement
, keyProcessor
.dialog
, keyProcessor
.key
);
2259 evt
.data
.preventDefault();
2262 var accessKeyUpHandler = function( evt
) {
2263 var ctrl
= evt
.data
.$.ctrlKey
|| evt
.data
.$.metaKey
,
2264 alt
= evt
.data
.$.altKey
,
2265 shift
= evt
.data
.$.shiftKey
,
2266 key
= String
.fromCharCode( evt
.data
.$.keyCode
),
2267 keyProcessor
= accessKeyProcessors
[ ( ctrl
? 'CTRL+' : '' ) + ( alt
? 'ALT+' : '' ) + ( shift
? 'SHIFT+' : '' ) + key
];
2269 if ( !keyProcessor
|| !keyProcessor
.length
)
2272 keyProcessor
= keyProcessor
[ keyProcessor
.length
- 1 ];
2273 if ( keyProcessor
.keyup
) {
2274 keyProcessor
.keyup
.call( keyProcessor
.uiElement
, keyProcessor
.dialog
, keyProcessor
.key
);
2275 evt
.data
.preventDefault();
2279 var registerAccessKey = function( uiElement
, dialog
, key
, downFunc
, upFunc
) {
2280 var procList
= accessKeyProcessors
[ key
] || ( accessKeyProcessors
[ key
] = [] );
2282 uiElement: uiElement
,
2285 keyup: upFunc
|| uiElement
.accessKeyUp
,
2286 keydown: downFunc
|| uiElement
.accessKeyDown
2290 var unregisterAccessKey = function( obj
) {
2291 for ( var i
in accessKeyProcessors
) {
2292 var list
= accessKeyProcessors
[ i
];
2293 for ( var j
= list
.length
- 1; j
>= 0; j
-- ) {
2294 if ( list
[ j
].dialog
== obj
|| list
[ j
].uiElement
== obj
)
2295 list
.splice( j
, 1 );
2297 if ( list
.length
=== 0 )
2298 delete accessKeyProcessors
[ i
];
2302 var tabAccessKeyUp = function( dialog
, key
) {
2303 if ( dialog
._
.accessKeyMap
[ key
] )
2304 dialog
.selectPage( dialog
._
.accessKeyMap
[ key
] );
2307 var tabAccessKeyDown = function() {};
2310 CKEDITOR
.ui
.dialog
= {
2312 * The base class of all dialog UI elements.
2314 * @class CKEDITOR.ui.dialog.uiElement
2315 * @constructor Creates a uiElement class instance.
2316 * @param {CKEDITOR.dialog} dialog Parent dialog object.
2317 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element
2322 * * `id` (Required) The id of the UI element. See {@link CKEDITOR.dialog#getContentElement}.
2323 * * `type` (Required) The type of the UI element. The
2324 * value to this field specifies which UI element class will be used to
2325 * generate the final widget.
2326 * * `title` (Optional) The popup tooltip for the UI
2328 * * `hidden` (Optional) A flag that tells if the element
2329 * should be initially visible.
2330 * * `className` (Optional) Additional CSS class names
2331 * to add to the UI element. Separated by space.
2332 * * `style` (Optional) Additional CSS inline styles
2333 * to add to the UI element. A semicolon (;) is required after the last
2334 * style declaration.
2335 * * `accessKey` (Optional) The alphanumeric access key
2336 * for this element. Access keys are automatically prefixed by CTRL.
2337 * * `on*` (Optional) Any UI element definition field that
2338 * starts with `on` followed immediately by a capital letter and
2339 * probably more letters is an event handler. Event handlers may be further
2340 * divided into registered event handlers and DOM event handlers. Please
2341 * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and
2342 * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more information.
2344 * @param {Array} htmlList
2345 * List of HTML code to be added to the dialog's content area.
2346 * @param {Function/String} [nodeNameArg='div']
2347 * A function returning a string, or a simple string for the node name for
2348 * the root DOM node.
2349 * @param {Function/Object} [stylesArg={}]
2350 * A function returning an object, or a simple object for CSS styles applied
2352 * @param {Function/Object} [attributesArg={}]
2353 * A fucntion returning an object, or a simple object for attributes applied
2355 * @param {Function/String} [contentsArg='']
2356 * A function returning a string, or a simple string for the HTML code inside
2357 * the root DOM node. Default is empty string.
2359 uiElement: function( dialog
, elementDefinition
, htmlList
, nodeNameArg
, stylesArg
, attributesArg
, contentsArg
) {
2360 if ( arguments
.length
< 4 )
2363 var nodeName
= ( nodeNameArg
.call
? nodeNameArg( elementDefinition
) : nodeNameArg
) || 'div',
2364 html
= [ '<', nodeName
, ' ' ],
2365 styles
= ( stylesArg
&& stylesArg
.call
? stylesArg( elementDefinition
) : stylesArg
) || {},
2366 attributes
= ( attributesArg
&& attributesArg
.call
? attributesArg( elementDefinition
) : attributesArg
) || {},
2367 innerHTML
= ( contentsArg
&& contentsArg
.call
? contentsArg
.call( this, dialog
, elementDefinition
) : contentsArg
) || '',
2368 domId
= this.domId
= attributes
.id
|| CKEDITOR
.tools
.getNextId() + '_uiElement',
2371 if ( elementDefinition
.requiredContent
&& !dialog
.getParentEditor().filter
.check( elementDefinition
.requiredContent
) ) {
2372 styles
.display
= 'none';
2373 this.notAllowed
= true;
2376 // Set the id, a unique id is required for getElement() to work.
2377 attributes
.id
= domId
;
2379 // Set the type and definition CSS class names.
2381 if ( elementDefinition
.type
)
2382 classes
[ 'cke_dialog_ui_' + elementDefinition
.type
] = 1;
2383 if ( elementDefinition
.className
)
2384 classes
[ elementDefinition
.className
] = 1;
2385 if ( elementDefinition
.disabled
)
2386 classes
.cke_disabled
= 1;
2388 var attributeClasses
= ( attributes
[ 'class' ] && attributes
[ 'class' ].split
) ? attributes
[ 'class' ].split( ' ' ) : [];
2389 for ( i
= 0; i
< attributeClasses
.length
; i
++ ) {
2390 if ( attributeClasses
[ i
] )
2391 classes
[ attributeClasses
[ i
] ] = 1;
2393 var finalClasses
= [];
2394 for ( i
in classes
)
2395 finalClasses
.push( i
);
2396 attributes
[ 'class' ] = finalClasses
.join( ' ' );
2398 // Set the popup tooltop.
2399 if ( elementDefinition
.title
)
2400 attributes
.title
= elementDefinition
.title
;
2402 // Write the inline CSS styles.
2403 var styleStr
= ( elementDefinition
.style
|| '' ).split( ';' );
2405 // Element alignment support.
2406 if ( elementDefinition
.align
) {
2407 var align
= elementDefinition
.align
;
2408 styles
[ 'margin-left' ] = align
== 'left' ? 0 : 'auto';
2409 styles
[ 'margin-right' ] = align
== 'right' ? 0 : 'auto';
2413 styleStr
.push( i
+ ':' + styles
[ i
] );
2414 if ( elementDefinition
.hidden
)
2415 styleStr
.push( 'display:none' );
2416 for ( i
= styleStr
.length
- 1; i
>= 0; i
-- ) {
2417 if ( styleStr
[ i
] === '' )
2418 styleStr
.splice( i
, 1 );
2420 if ( styleStr
.length
> 0 )
2421 attributes
.style
= ( attributes
.style
? ( attributes
.style
+ '; ' ) : '' ) + styleStr
.join( '; ' );
2423 // Write the attributes.
2424 for ( i
in attributes
)
2425 html
.push( i
+ '="' + CKEDITOR
.tools
.htmlEncode( attributes
[ i
] ) + '" ' );
2427 // Write the content HTML.
2428 html
.push( '>', innerHTML
, '</', nodeName
, '>' );
2430 // Add contents to the parent HTML array.
2431 htmlList
.push( html
.join( '' ) );
2433 ( this._
|| ( this._
= {} ) ).dialog
= dialog
;
2435 // Override isChanged if it is defined in element definition.
2436 if ( typeof elementDefinition
.isChanged
== 'boolean' )
2437 this.isChanged = function() {
2438 return elementDefinition
.isChanged
;
2440 if ( typeof elementDefinition
.isChanged
== 'function' )
2441 this.isChanged
= elementDefinition
.isChanged
;
2443 // Overload 'get(set)Value' on definition.
2444 if ( typeof elementDefinition
.setValue
== 'function' ) {
2445 this.setValue
= CKEDITOR
.tools
.override( this.setValue
, function( org
) {
2446 return function( val
) {
2447 org
.call( this, elementDefinition
.setValue
.call( this, val
) );
2452 if ( typeof elementDefinition
.getValue
== 'function' ) {
2453 this.getValue
= CKEDITOR
.tools
.override( this.getValue
, function( org
) {
2455 return elementDefinition
.getValue
.call( this, org
.call( this ) );
2461 CKEDITOR
.event
.implementOn( this );
2463 this.registerEvents( elementDefinition
);
2464 if ( this.accessKeyUp
&& this.accessKeyDown
&& elementDefinition
.accessKey
)
2465 registerAccessKey( this, dialog
, 'CTRL+' + elementDefinition
.accessKey
);
2468 dialog
.on( 'load', function() {
2469 var input
= me
.getInputElement();
2471 var focusClass
= me
.type
in { 'checkbox': 1, 'ratio': 1 } && CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 8 ? 'cke_dialog_ui_focused' : '';
2472 input
.on( 'focus', function() {
2473 dialog
._
.tabBarMode
= false;
2474 dialog
._
.hasFocus
= true;
2476 focusClass
&& this.addClass( focusClass
);
2480 input
.on( 'blur', function() {
2482 focusClass
&& this.removeClass( focusClass
);
2487 // Completes this object with everything we have in the
2489 CKEDITOR
.tools
.extend( this, elementDefinition
);
2491 // Register the object as a tab focus if it can be included.
2492 if ( this.keyboardFocusable
) {
2493 this.tabIndex
= elementDefinition
.tabIndex
|| 0;
2495 this.focusIndex
= dialog
._
.focusList
.push( this ) - 1;
2496 this.on( 'focus', function() {
2497 dialog
._
.currentFocusIndex
= me
.focusIndex
;
2503 * Horizontal layout box for dialog UI elements, auto-expends to available width of container.
2505 * @class CKEDITOR.ui.dialog.hbox
2506 * @extends CKEDITOR.ui.dialog.uiElement
2507 * @constructor Creates a hbox class instance.
2508 * @param {CKEDITOR.dialog} dialog Parent dialog object.
2509 * @param {Array} childObjList
2510 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container.
2511 * @param {Array} childHtmlList
2512 * Array of HTML code that correspond to the HTML output of all the
2513 * objects in childObjList.
2514 * @param {Array} htmlList
2515 * Array of HTML code that this element will output to.
2516 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
2517 * The element definition. Accepted fields:
2519 * * `widths` (Optional) The widths of child cells.
2520 * * `height` (Optional) The height of the layout.
2521 * * `padding` (Optional) The padding width inside child cells.
2522 * * `align` (Optional) The alignment of the whole layout.
2524 hbox: function( dialog
, childObjList
, childHtmlList
, htmlList
, elementDefinition
) {
2525 if ( arguments
.length
< 4 )
2528 this._
|| ( this._
= {} );
2530 var children
= this._
.children
= childObjList
,
2531 widths
= elementDefinition
&& elementDefinition
.widths
|| null,
2532 height
= elementDefinition
&& elementDefinition
.height
|| null,
2536 var innerHTML = function() {
2537 var html
= [ '<tbody><tr class="cke_dialog_ui_hbox">' ];
2538 for ( i
= 0; i
< childHtmlList
.length
; i
++ ) {
2539 var className
= 'cke_dialog_ui_hbox_child',
2542 className
= 'cke_dialog_ui_hbox_first';
2544 if ( i
== childHtmlList
.length
- 1 ) {
2545 className
= 'cke_dialog_ui_hbox_last';
2548 html
.push( '<td class="', className
, '" role="presentation" ' );
2550 if ( widths
[ i
] ) {
2551 styles
.push( 'width:' + cssLength( widths
[i
] ) );
2554 styles
.push( 'width:' + Math
.floor( 100 / childHtmlList
.length
) + '%' );
2557 styles
.push( 'height:' + cssLength( height
) );
2559 if ( elementDefinition
&& elementDefinition
.padding
!== undefined ) {
2560 styles
.push( 'padding:' + cssLength( elementDefinition
.padding
) );
2562 // In IE Quirks alignment has to be done on table cells. (#7324)
2563 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
&& children
[ i
].align
) {
2564 styles
.push( 'text-align:' + children
[ i
].align
);
2566 if ( styles
.length
> 0 ) {
2567 html
.push( 'style="' + styles
.join( '; ' ) + '" ' );
2569 html
.push( '>', childHtmlList
[ i
], '</td>' );
2571 html
.push( '</tr></tbody>' );
2572 return html
.join( '' );
2575 var attribs
= { role: 'presentation' };
2576 elementDefinition
&& elementDefinition
.align
&& ( attribs
.align
= elementDefinition
.align
);
2578 CKEDITOR
.ui
.dialog
.uiElement
.call( this, dialog
, elementDefinition
|| { type: 'hbox' }, htmlList
, 'table', styles
, attribs
, innerHTML
);
2582 * Vertical layout box for dialog UI elements.
2584 * @class CKEDITOR.ui.dialog.vbox
2585 * @extends CKEDITOR.ui.dialog.hbox
2586 * @constructor Creates a vbox class instance.
2587 * @param {CKEDITOR.dialog} dialog Parent dialog object.
2588 * @param {Array} childObjList
2589 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container.
2590 * @param {Array} childHtmlList
2591 * Array of HTML code that correspond to the HTML output of all the
2592 * objects in childObjList.
2593 * @param {Array} htmlList Array of HTML code that this element will output to.
2594 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
2595 * The element definition. Accepted fields:
2597 * * `width` (Optional) The width of the layout.
2598 * * `heights` (Optional) The heights of individual cells.
2599 * * `align` (Optional) The alignment of the layout.
2600 * * `padding` (Optional) The padding width inside child cells.
2601 * * `expand` (Optional) Whether the layout should expand
2602 * vertically to fill its container.
2604 vbox: function( dialog
, childObjList
, childHtmlList
, htmlList
, elementDefinition
) {
2605 if ( arguments
.length
< 3 )
2608 this._
|| ( this._
= {} );
2610 var children
= this._
.children
= childObjList
,
2611 width
= elementDefinition
&& elementDefinition
.width
|| null,
2612 heights
= elementDefinition
&& elementDefinition
.heights
|| null;
2614 var innerHTML = function() {
2615 var html
= [ '<table role="presentation" cellspacing="0" border="0" ' ];
2616 html
.push( 'style="' );
2617 if ( elementDefinition
&& elementDefinition
.expand
)
2618 html
.push( 'height:100%;' );
2619 html
.push( 'width:' + cssLength( width
|| '100%' ), ';' );
2621 // (#10123) Temp fix for dialog broken layout in latest webkit.
2622 if ( CKEDITOR
.env
.webkit
)
2623 html
.push( 'float:none;' );
2626 html
.push( 'align="', CKEDITOR
.tools
.htmlEncode(
2627 ( elementDefinition
&& elementDefinition
.align
) || ( dialog
.getParentEditor().lang
.dir
== 'ltr' ? 'left' : 'right' ) ), '" ' );
2629 html
.push( '><tbody>' );
2630 for ( var i
= 0; i
< childHtmlList
.length
; i
++ ) {
2632 html
.push( '<tr><td role="presentation" ' );
2634 styles
.push( 'width:' + cssLength( width
|| '100%' ) );
2636 styles
.push( 'height:' + cssLength( heights
[ i
] ) );
2637 else if ( elementDefinition
&& elementDefinition
.expand
)
2638 styles
.push( 'height:' + Math
.floor( 100 / childHtmlList
.length
) + '%' );
2639 if ( elementDefinition
&& elementDefinition
.padding
!== undefined )
2640 styles
.push( 'padding:' + cssLength( elementDefinition
.padding
) );
2641 // In IE Quirks alignment has to be done on table cells. (#7324)
2642 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
&& children
[ i
].align
)
2643 styles
.push( 'text-align:' + children
[ i
].align
);
2644 if ( styles
.length
> 0 )
2645 html
.push( 'style="', styles
.join( '; ' ), '" ' );
2646 html
.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList
[ i
], '</td></tr>' );
2648 html
.push( '</tbody></table>' );
2649 return html
.join( '' );
2651 CKEDITOR
.ui
.dialog
.uiElement
.call( this, dialog
, elementDefinition
|| { type: 'vbox' }, htmlList
, 'div', null, { role: 'presentation' }, innerHTML
);
2656 /** @class CKEDITOR.ui.dialog.uiElement */
2657 CKEDITOR
.ui
.dialog
.uiElement
.prototype = {
2659 * Gets the root DOM element of this dialog UI object.
2661 * uiElement.getElement().hide();
2663 * @returns {CKEDITOR.dom.element} Root DOM element of UI object.
2665 getElement: function() {
2666 return CKEDITOR
.document
.getById( this.domId
);
2670 * Gets the DOM element that the user inputs values.
2672 * This function is used by {@link #setValue}, {@link #getValue} and {@link #focus}. It should
2673 * be overrided in child classes where the input element isn't the root
2676 * var rawValue = textInput.getInputElement().$.value;
2678 * @returns {CKEDITOR.dom.element} The element where the user input values.
2680 getInputElement: function() {
2681 return this.getElement();
2685 * Gets the parent dialog object containing this UI element.
2687 * var dialog = uiElement.getDialog();
2689 * @returns {CKEDITOR.dialog} Parent dialog object.
2691 getDialog: function() {
2692 return this._
.dialog
;
2696 * Sets the value of this dialog UI object.
2698 * uiElement.setValue( 'Dingo' );
2701 * @param {Object} value The new value.
2702 * @param {Boolean} noChangeEvent Internal commit, to supress `change` event on this element.
2704 setValue: function( value
, noChangeEvent
) {
2705 this.getInputElement().setValue( value
);
2706 !noChangeEvent
&& this.fire( 'change', { value: value
} );
2711 * Gets the current value of this dialog UI object.
2713 * var myValue = uiElement.getValue();
2715 * @returns {Object} The current value.
2717 getValue: function() {
2718 return this.getInputElement().getValue();
2722 * Tells whether the UI object's value has changed.
2724 * if ( uiElement.isChanged() )
2725 * confirm( 'Value changed! Continue?' );
2727 * @returns {Boolean} `true` if changed, `false` if not changed.
2729 isChanged: function() {
2730 // Override in input classes.
2735 * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods.
2737 * focus : function() {
2738 * this.selectParentTab();
2739 * // do something else.
2744 selectParentTab: function() {
2745 var element
= this.getInputElement(),
2748 while ( ( cursor
= cursor
.getParent() ) && cursor
.$.className
.search( 'cke_dialog_page_contents' ) == -1 ) {
2752 // Some widgets don't have parent tabs (e.g. OK and Cancel buttons).
2756 tabId
= cursor
.getAttribute( 'name' );
2757 // Avoid duplicate select.
2758 if ( this._
.dialog
._
.currentTabId
!= tabId
)
2759 this._
.dialog
.selectPage( tabId
);
2764 * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page.
2766 * uiElement.focus();
2771 this.selectParentTab().getInputElement().focus();
2776 * Registers the `on*` event handlers defined in the element definition.
2778 * The default behavior of this function is:
2780 * 1. If the on* event is defined in the class's eventProcesors list,
2781 * then the registration is delegated to the corresponding function
2782 * in the eventProcessors list.
2783 * 2. If the on* event is not defined in the eventProcessors list, then
2784 * register the event handler under the corresponding DOM event of
2785 * the UI element's input DOM element (as defined by the return value
2786 * of {@link #getInputElement}).
2788 * This function is only called at UI element instantiation, but can
2789 * be overridded in child classes if they require more flexibility.
2792 * @param {CKEDITOR.dialog.definition.uiElement} definition The UI element
2795 registerEvents: function( definition
) {
2796 var regex
= /^on([A-Z]\w+)/,
2799 var registerDomEvent = function( uiElement
, dialog
, eventName
, func
) {
2800 dialog
.on( 'load', function() {
2801 uiElement
.getInputElement().on( eventName
, func
, uiElement
);
2805 for ( var i
in definition
) {
2806 if ( !( match
= i
.match( regex
) ) )
2808 if ( this.eventProcessors
[ i
] )
2809 this.eventProcessors
[ i
].call( this, this._
.dialog
, definition
[ i
] );
2811 registerDomEvent( this, this._
.dialog
, match
[ 1 ].toLowerCase(), definition
[ i
] );
2818 * The event processor list used by
2819 * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element
2820 * instantiation. The default list defines three `on*` events:
2822 * 1. `onLoad` - Called when the element's parent dialog opens for the
2824 * 2. `onShow` - Called whenever the element's parent dialog opens.
2825 * 3. `onHide` - Called whenever the element's parent dialog closes.
2827 * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick
2828 * // handlers in the UI element's definitions.
2829 * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {},
2830 * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
2831 * { onClick : function( dialog, func ) { this.on( 'click', func ); } },
2835 * @property {Object}
2838 onLoad: function( dialog
, func
) {
2839 dialog
.on( 'load', func
, this );
2842 onShow: function( dialog
, func
) {
2843 dialog
.on( 'show', func
, this );
2846 onHide: function( dialog
, func
) {
2847 dialog
.on( 'hide', func
, this );
2852 * The default handler for a UI element's access key down event, which
2853 * tries to put focus to the UI element.
2855 * Can be overridded in child classes for more sophisticaed behavior.
2857 * @param {CKEDITOR.dialog} dialog The parent dialog object.
2858 * @param {String} key The key combination pressed. Since access keys
2859 * are defined to always include the `CTRL` key, its value should always
2860 * include a `'CTRL+'` prefix.
2862 accessKeyDown: function() {
2867 * The default handler for a UI element's access key up event, which
2870 * Can be overridded in child classes for more sophisticated behavior.
2872 * @param {CKEDITOR.dialog} dialog The parent dialog object.
2873 * @param {String} key The key combination pressed. Since access keys
2874 * are defined to always include the `CTRL` key, its value should always
2875 * include a `'CTRL+'` prefix.
2877 accessKeyUp: function() {},
2880 * Disables a UI element.
2882 disable: function() {
2883 var element
= this.getElement(),
2884 input
= this.getInputElement();
2885 input
.setAttribute( 'disabled', 'true' );
2886 element
.addClass( 'cke_disabled' );
2890 * Enables a UI element.
2892 enable: function() {
2893 var element
= this.getElement(),
2894 input
= this.getInputElement();
2895 input
.removeAttribute( 'disabled' );
2896 element
.removeClass( 'cke_disabled' );
2900 * Determines whether an UI element is enabled or not.
2902 * @returns {Boolean} Whether the UI element is enabled.
2904 isEnabled: function() {
2905 return !this.getElement().hasClass( 'cke_disabled' );
2909 * Determines whether an UI element is visible or not.
2911 * @returns {Boolean} Whether the UI element is visible.
2913 isVisible: function() {
2914 return this.getInputElement().isVisible();
2918 * Determines whether an UI element is focus-able or not.
2919 * Focus-able is defined as being both visible and enabled.
2921 * @returns {Boolean} Whether the UI element can be focused.
2923 isFocusable: function() {
2924 if ( !this.isEnabled() || !this.isVisible() )
2930 /** @class CKEDITOR.ui.dialog.hbox */
2931 CKEDITOR
.ui
.dialog
.hbox
.prototype = CKEDITOR
.tools
.extend( new CKEDITOR
.ui
.dialog
.uiElement(), {
2933 * Gets a child UI element inside this container.
2935 * var checkbox = hbox.getChild( [0,1] );
2936 * checkbox.setValue( true );
2938 * @param {Array/Number} indices An array or a single number to indicate the child's
2939 * position in the container's descendant tree. Omit to get all the children in an array.
2940 * @returns {Array/CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container
2941 * if no argument given, or the specified UI element if indices is given.
2943 getChild: function( indices
) {
2944 // If no arguments, return a clone of the children array.
2945 if ( arguments
.length
< 1 )
2946 return this._
.children
.concat();
2948 // If indices isn't array, make it one.
2949 if ( !indices
.splice
)
2950 indices
= [ indices
];
2952 // Retrieve the child element according to tree position.
2953 if ( indices
.length
< 2 )
2954 return this._
.children
[ indices
[ 0 ] ];
2956 return ( this._
.children
[ indices
[ 0 ] ] && this._
.children
[ indices
[ 0 ] ].getChild
) ? this._
.children
[ indices
[ 0 ] ].getChild( indices
.slice( 1, indices
.length
) ) : null;
2960 CKEDITOR
.ui
.dialog
.vbox
.prototype = new CKEDITOR
.ui
.dialog
.hbox();
2963 var commonBuilder
= {
2964 build: function( dialog
, elementDefinition
, output
) {
2965 var children
= elementDefinition
.children
,
2970 ( i
< children
.length
&& ( child
= children
[ i
] ) ); i
++ ) {
2972 childHtmlList
.push( childHtml
);
2973 childObjList
.push( CKEDITOR
.dialog
._
.uiElementBuilders
[ child
.type
].build( dialog
, child
, childHtml
) );
2975 return new CKEDITOR
.ui
.dialog
[ elementDefinition
.type
]( dialog
, childObjList
, childHtmlList
, output
, elementDefinition
);
2979 CKEDITOR
.dialog
.addUIElement( 'hbox', commonBuilder
);
2980 CKEDITOR
.dialog
.addUIElement( 'vbox', commonBuilder
);
2984 * Generic dialog command. It opens a specific dialog when executed.
2986 * // Register the "link" command, which opens the "link" dialog.
2987 * editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) );
2990 * @constructor Creates a dialogCommand class instance.
2991 * @extends CKEDITOR.commandDefinition
2992 * @param {String} dialogName The name of the dialog to open when executing
2994 * @param {Object} [ext] Additional command definition's properties.
2996 CKEDITOR
.dialogCommand = function( dialogName
, ext
) {
2997 this.dialogName
= dialogName
;
2998 CKEDITOR
.tools
.extend( this, ext
, true );
3001 CKEDITOR
.dialogCommand
.prototype = {
3002 exec: function( editor
) {
3003 editor
.openDialog( this.dialogName
);
3006 // Dialog commands just open a dialog ui, thus require no undo logic,
3007 // undo support should dedicate to specific dialog implementation.
3014 var notEmptyRegex
= /^([a]|[^a])+$/,
3015 integerRegex
= /^\d*$/,
3016 numberRegex
= /^\d*(?:\.\d+)?$/,
3017 htmlLengthRegex
= /^(((\d*(\.\d+))|(\d*))(px|\%)?)?$/,
3018 cssLengthRegex
= /^(((\d*(\.\d+))|(\d*))(px|em|ex|in|cm|mm|pt|pc|\%)?)?$/i,
3019 inlineStyleRegex
= /^(\s*[\w-]+\s*:\s*[^:;]+(?:;|$))*$/;
3021 CKEDITOR
.VALIDATE_OR
= 1;
3022 CKEDITOR
.VALIDATE_AND
= 2;
3024 CKEDITOR
.dialog
.validate
= {
3025 functions: function() {
3026 var args
= arguments
;
3029 * It's important for validate functions to be able to accept the value
3030 * as argument in addition to this.getValue(), so that it is possible to
3031 * combine validate functions together to make more sophisticated
3034 var value
= this && this.getValue
? this.getValue() : args
[ 0 ];
3037 relation
= CKEDITOR
.VALIDATE_AND
,
3041 for ( i
= 0; i
< args
.length
; i
++ ) {
3042 if ( typeof args
[ i
] == 'function' )
3043 functions
.push( args
[ i
] );
3048 if ( i
< args
.length
&& typeof args
[ i
] == 'string' ) {
3053 if ( i
< args
.length
&& typeof args
[ i
] == 'number' )
3054 relation
= args
[ i
];
3056 var passed
= ( relation
== CKEDITOR
.VALIDATE_AND
? true : false );
3057 for ( i
= 0; i
< functions
.length
; i
++ ) {
3058 if ( relation
== CKEDITOR
.VALIDATE_AND
)
3059 passed
= passed
&& functions
[ i
]( value
);
3061 passed
= passed
|| functions
[ i
]( value
);
3064 return !passed
? msg : true;
3068 regex: function( regex
, msg
) {
3070 * Can be greatly shortened by deriving from functions validator if code size
3071 * turns out to be more important than performance.
3074 var value
= this && this.getValue
? this.getValue() : arguments
[ 0 ];
3075 return !regex
.test( value
) ? msg : true;
3079 notEmpty: function( msg
) {
3080 return this.regex( notEmptyRegex
, msg
);
3083 integer: function( msg
) {
3084 return this.regex( integerRegex
, msg
);
3087 'number': function( msg
) {
3088 return this.regex( numberRegex
, msg
);
3091 'cssLength': function( msg
) {
3092 return this.functions( function( val
) {
3093 return cssLengthRegex
.test( CKEDITOR
.tools
.trim( val
) );
3097 'htmlLength': function( msg
) {
3098 return this.functions( function( val
) {
3099 return htmlLengthRegex
.test( CKEDITOR
.tools
.trim( val
) );
3103 'inlineStyle': function( msg
) {
3104 return this.functions( function( val
) {
3105 return inlineStyleRegex
.test( CKEDITOR
.tools
.trim( val
) );
3109 equals: function( value
, msg
) {
3110 return this.functions( function( val
) {
3111 return val
== value
;
3115 notEqual: function( value
, msg
) {
3116 return this.functions( function( val
) {
3117 return val
!= value
;
3122 CKEDITOR
.on( 'instanceDestroyed', function( evt
) {
3123 // Remove dialog cover on last instance destroy.
3124 if ( CKEDITOR
.tools
.isEmpty( CKEDITOR
.instances
) ) {
3125 var currentTopDialog
;
3126 while ( ( currentTopDialog
= CKEDITOR
.dialog
._
.currentTop
) )
3127 currentTopDialog
.hide();
3131 var dialogs
= evt
.editor
._
.storedDialogs
;
3132 for ( var name
in dialogs
)
3133 dialogs
[ name
].destroy();
3139 // Extend the CKEDITOR.editor class with dialog specific functions.
3140 CKEDITOR
.tools
.extend( CKEDITOR
.editor
.prototype, {
3142 * Loads and opens a registered dialog.
3144 * CKEDITOR.instances.editor1.openDialog( 'smiley' );
3146 * @member CKEDITOR.editor
3147 * @param {String} dialogName The registered name of the dialog.
3148 * @param {Function} callback The function to be invoked after dialog instance created.
3149 * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed.
3150 * `null` if the dialog name is not registered.
3151 * @see CKEDITOR.dialog#add
3153 openDialog: function( dialogName
, callback
) {
3154 var dialog
= null, dialogDefinitions
= CKEDITOR
.dialog
._
.dialogDefinitions
[ dialogName
];
3156 if ( CKEDITOR
.dialog
._
.currentTop
=== null )
3159 // If the dialogDefinition is already loaded, open it immediately.
3160 if ( typeof dialogDefinitions
== 'function' ) {
3161 var storedDialogs
= this._
.storedDialogs
|| ( this._
.storedDialogs
= {} );
3163 dialog
= storedDialogs
[ dialogName
] || ( storedDialogs
[ dialogName
] = new CKEDITOR
.dialog( this, dialogName
) );
3165 callback
&& callback
.call( dialog
, dialog
);
3168 } else if ( dialogDefinitions
== 'failed' ) {
3170 throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName
+ '" failed when loading definition.' );
3171 } else if ( typeof dialogDefinitions
== 'string' ) {
3173 CKEDITOR
.scriptLoader
.load( CKEDITOR
.getUrl( dialogDefinitions
),
3175 var dialogDefinition
= CKEDITOR
.dialog
._
.dialogDefinitions
[ dialogName
];
3176 // In case of plugin error, mark it as loading failed.
3177 if ( typeof dialogDefinition
!= 'function' )
3178 CKEDITOR
.dialog
._
.dialogDefinitions
[ dialogName
] = 'failed';
3180 this.openDialog( dialogName
, callback
);
3184 CKEDITOR
.skin
.loadPart( 'dialog' );
3191 CKEDITOR
.plugins
.add( 'dialog', {
3192 requires: 'dialogui',
3193 init: function( editor
) {
3194 editor
.on( 'doubleclick', function( evt
) {
3195 if ( evt
.data
.dialog
)
3196 editor
.openDialog( evt
.data
.dialog
);
3197 }, null, null, 999 );
3201 // Dialog related configurations.
3204 * The color of the dialog background cover. It should be a valid CSS color string.
3206 * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)';
3208 * @cfg {String} [dialog_backgroundCoverColor='white']
3209 * @member CKEDITOR.config
3213 * The opacity of the dialog background cover. It should be a number within the
3214 * range `[0.0, 1.0]`.
3216 * config.dialog_backgroundCoverOpacity = 0.7;
3218 * @cfg {Number} [dialog_backgroundCoverOpacity=0.5]
3219 * @member CKEDITOR.config
3223 * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened.
3225 * config.dialog_startupFocusTab = true;
3227 * @cfg {Boolean} [dialog_startupFocusTab=false]
3228 * @member CKEDITOR.config
3232 * The distance of magnetic borders used in moving and resizing dialogs,
3233 * measured in pixels.
3235 * config.dialog_magnetDistance = 30;
3237 * @cfg {Number} [dialog_magnetDistance=20]
3238 * @member CKEDITOR.config
3242 * The guideline to follow when generating the dialog buttons. There are 3 possible options:
3244 * * `'OS'` - the buttons will be displayed in the default order of the user's OS;
3245 * * `'ltr'` - for Left-To-Right order;
3246 * * `'rtl'` - for Right-To-Left order.
3250 * config.dialog_buttonsOrder = 'rtl';
3253 * @cfg {String} [dialog_buttonsOrder='OS']
3254 * @member CKEDITOR.config
3258 * The dialog contents to removed. It's a string composed by dialog name and tab name with a colon between them.
3260 * Separate each pair with semicolon (see example).
3262 * **Note:** All names are case-sensitive.
3264 * **Note:** Be cautious when specifying dialog tabs that are mandatory,
3265 * like `'info'`, dialog functionality might be broken because of this!
3267 * config.removeDialogTabs = 'flash:advanced;image:Link';
3270 * @cfg {String} [removeDialogTabs='']
3271 * @member CKEDITOR.config
3275 * Tells if user should not be asked to confirm close, if any dialog field was modified.
3276 * By default it is set to `false` meaning that the confirmation dialog will be shown.
3278 * config.dialog_noConfirmCancel = true;
3281 * @cfg {Boolean} [dialog_noConfirmCancel=false]
3282 * @member CKEDITOR.config
3286 * Event fired when a dialog definition is about to be used to create a dialog into
3287 * an editor instance. This event makes it possible to customize the definition
3288 * before creating it.
3290 * Note that this event is called only the first time a specific dialog is
3291 * opened. Successive openings will use the cached dialog, and this event will
3294 * @event dialogDefinition
3296 * @param {CKEDITOR.dialog.definition} data The dialog defination that
3298 * @param {CKEDITOR.editor} editor The editor instance that will use the dialog.
3302 * Event fired when a tab is going to be selected in a dialog.
3305 * @member CKEDITOR.dialog
3307 * @param {String} data.page The id of the page that it's gonna be selected.
3308 * @param {String} data.currentPage The id of the current page.
3312 * Event fired when the user tries to dismiss a dialog.
3315 * @member CKEDITOR.dialog
3317 * @param {Boolean} data.hide Whether the event should proceed or not.
3321 * Event fired when the user tries to confirm a dialog.
3324 * @member CKEDITOR.dialog
3326 * @param {Boolean} data.hide Whether the event should proceed or not.
3330 * Event fired when a dialog is shown.
3333 * @member CKEDITOR.dialog
3337 * Event fired when a dialog is shown.
3340 * @member CKEDITOR.editor
3341 * @param {CKEDITOR.editor} editor This editor instance.
3342 * @param {CKEDITOR.dialog} data The opened dialog instance.
3346 * Event fired when a dialog is hidden.
3349 * @member CKEDITOR.dialog
3353 * Event fired when a dialog is hidden.
3356 * @member CKEDITOR.editor
3357 * @param {CKEDITOR.editor} editor This editor instance.
3358 * @param {CKEDITOR.dialog} data The hidden dialog instance.
3362 * Event fired when a dialog is being resized. The event is fired on
3363 * both the {@link CKEDITOR.dialog} object and the dialog instance
3364 * since 3.5.3, previously it was only available in the global object.
3368 * @member CKEDITOR.dialog
3370 * @param {CKEDITOR.dialog} data.dialog The dialog being resized (if
3371 * it is fired on the dialog itself, this parameter is not sent).
3372 * @param {String} data.skin The skin name.
3373 * @param {Number} data.width The new width.
3374 * @param {Number} data.height The new height.
3378 * Event fired when a dialog is being resized. The event is fired on
3379 * both the {@link CKEDITOR.dialog} object and the dialog instance
3380 * since 3.5.3, previously it was only available in the global object.
3384 * @member CKEDITOR.dialog
3386 * @param {Number} data.width The new width.
3387 * @param {Number} data.height The new height.
3391 * Event fired when the dialog state changes, usually by {@link CKEDITOR.dialog#setState}.
3395 * @member CKEDITOR.dialog
3397 * @param {Number} data The new state. Either {@link CKEDITOR#DIALOG_STATE_IDLE} or {@link CKEDITOR#DIALOG_STATE_BUSY}.