2 * @license Copyright (c) 2003-2017, 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 (http://dev.ckeditor.com/ticket/13184).
181 editor
.plugins
.clipboard
&& CKEDITOR
.plugins
.clipboard
.preventDefaultDropOnElement( body
);
183 // IFrame shim for dialog that masks activeX in IE. (http://dev.ckeditor.com/ticket/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 (http://dev.ckeditor.com/ticket/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 (http://dev.ckeditor.com/ticket/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 (http://dev.ckeditor.com/ticket/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 (http://dev.ckeditor.com/ticket/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 (http://dev.ckeditor.com/ticket/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. (http://dev.ckeditor.com/ticket/4531,http://dev.ckeditor.com/ticket/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 (http://dev.ckeditor.com/ticket/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 // (http://dev.ckeditor.com/ticket/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 // Selects the first tab if no tab is already selected.
866 if ( this._
.currentTabId
=== null ) {
867 this.selectPage( this.definition
.contents
[ 0 ].id
);
871 if ( CKEDITOR
.dialog
._
.currentZIndex
=== null )
872 CKEDITOR
.dialog
._
.currentZIndex
= this._
.editor
.config
.baseFloatZIndex
;
873 this._
.element
.getFirst().setStyle( 'z-index', CKEDITOR
.dialog
._
.currentZIndex
+= 10 );
875 // Maintain the dialog ordering and dialog cover.
876 if ( CKEDITOR
.dialog
._
.currentTop
=== null ) {
877 CKEDITOR
.dialog
._
.currentTop
= this;
878 this._
.parentDialog
= null;
879 showCover( this._
.editor
);
882 this._
.parentDialog
= CKEDITOR
.dialog
._
.currentTop
;
883 var parentElement
= this._
.parentDialog
.getElement().getFirst();
884 parentElement
.$.style
.zIndex
-= Math
.floor( this._
.editor
.config
.baseFloatZIndex
/ 2 );
885 CKEDITOR
.dialog
._
.currentTop
= this;
888 element
.on( 'keydown', accessKeyDownHandler
);
889 element
.on( 'keyup', accessKeyUpHandler
);
891 // Reset the hasFocus state.
892 this._
.hasFocus
= false;
894 for ( var i
in definition
.contents
) {
895 if ( !definition
.contents
[ i
] )
898 var content
= definition
.contents
[ i
],
899 tab
= this._
.tabs
[ content
.id
],
900 requiredContent
= content
.requiredContent
,
906 for ( var j
in this._
.contents
[ content
.id
] ) {
907 var elem
= this._
.contents
[ content
.id
][ j
];
909 if ( elem
.type
== 'hbox' || elem
.type
== 'vbox' || !elem
.getInputElement() )
912 if ( elem
.requiredContent
&& !this._
.editor
.activeFilter
.check( elem
.requiredContent
) )
920 if ( !enableElements
|| ( requiredContent
&& !this._
.editor
.activeFilter
.check( requiredContent
) ) )
921 tab
[ 0 ].addClass( 'cke_dialog_tab_disabled' );
923 tab
[ 0 ].removeClass( 'cke_dialog_tab_disabled' );
926 CKEDITOR
.tools
.setTimeout( function() {
928 resizeWithWindow( this );
930 this.parts
.dialog
.setStyle( 'visibility', '' );
932 // Execute onLoad for the first show.
933 this.fireOnce( 'load', {} );
934 CKEDITOR
.ui
.fire( 'ready', this );
936 this.fire( 'show', {} );
937 this._
.editor
.fire( 'dialogShow', this );
939 if ( !this._
.parentDialog
)
940 this._
.editor
.focusManager
.lock();
942 // Save the initial values of the dialog.
943 this.foreach( function( contentObj
) {
944 contentObj
.setInitValue
&& contentObj
.setInitValue();
951 * Rearrange the dialog to its previous position or the middle of the window.
956 var el
= this.parts
.dialog
;
957 var dialogSize
= this.getSize();
958 var win
= CKEDITOR
.document
.getWindow(),
959 viewSize
= win
.getViewPaneSize();
961 var posX
= ( viewSize
.width
- dialogSize
.width
) / 2,
962 posY
= ( viewSize
.height
- dialogSize
.height
) / 2;
964 // Switch to absolute position when viewport is smaller than dialog size.
965 if ( !CKEDITOR
.env
.ie6Compat
) {
966 if ( dialogSize
.height
+ ( posY
> 0 ? posY : 0 ) > viewSize
.height
|| dialogSize
.width
+ ( posX
> 0 ? posX : 0 ) > viewSize
.width
) {
967 el
.setStyle( 'position', 'absolute' );
969 el
.setStyle( 'position', 'fixed' );
973 this.move( this._
.moved
? this._
.position
.x : posX
, this._
.moved
? this._
.position
.y : posY
);
977 * Executes a function for each UI element.
979 * @param {Function} fn Function to execute for each UI element.
980 * @returns {CKEDITOR.dialog} The current dialog object.
982 foreach: function( fn
) {
983 for ( var i
in this._
.contents
) {
984 for ( var j
in this._
.contents
[ i
] ) {
985 fn
.call( this, this._
.contents
[i
][j
] );
993 * Resets all input values in the dialog.
1000 reset: ( function() {
1001 var fn = function( widget
) {
1013 * Calls the {@link CKEDITOR.dialog.definition.uiElement#setup} method of each
1014 * of the UI elements, with the arguments passed through it.
1015 * It is usually being called when the dialog is opened, to put the initial value inside the field.
1017 * dialogObj.setupContent();
1019 * var timestamp = ( new Date() ).valueOf();
1020 * dialogObj.setupContent( timestamp );
1022 setupContent: function() {
1023 var args
= arguments
;
1024 this.foreach( function( widget
) {
1026 widget
.setup
.apply( widget
, args
);
1031 * Calls the {@link CKEDITOR.dialog.definition.uiElement#commit} method of each
1032 * of the UI elements, with the arguments passed through it.
1033 * It is usually being called when the user confirms the dialog, to process the values.
1035 * dialogObj.commitContent();
1037 * var timestamp = ( new Date() ).valueOf();
1038 * dialogObj.commitContent( timestamp );
1040 commitContent: function() {
1041 var args
= arguments
;
1042 this.foreach( function( widget
) {
1043 // Make sure IE triggers "change" event on last focused input before closing the dialog. (http://dev.ckeditor.com/ticket/7915)
1044 if ( CKEDITOR
.env
.ie
&& this._
.currentFocusIndex
== widget
.focusIndex
)
1045 widget
.getInputElement().$.blur();
1047 if ( widget
.commit
)
1048 widget
.commit
.apply( widget
, args
);
1053 * Hides the dialog box.
1058 if ( !this.parts
.dialog
.isVisible() )
1061 this.fire( 'hide', {} );
1062 this._
.editor
.fire( 'dialogHide', this );
1063 // Reset the tab page.
1064 this.selectPage( this._
.tabIdList
[ 0 ] );
1065 var element
= this._
.element
;
1066 element
.setStyle( 'display', 'none' );
1067 this.parts
.dialog
.setStyle( 'visibility', 'hidden' );
1068 // Unregister all access keys associated with this dialog.
1069 unregisterAccessKey( this );
1071 // Close any child(top) dialogs first.
1072 while ( CKEDITOR
.dialog
._
.currentTop
!= this )
1073 CKEDITOR
.dialog
._
.currentTop
.hide();
1075 // Maintain dialog ordering and remove cover if needed.
1076 if ( !this._
.parentDialog
)
1077 hideCover( this._
.editor
);
1079 var parentElement
= this._
.parentDialog
.getElement().getFirst();
1080 parentElement
.setStyle( 'z-index', parseInt( parentElement
.$.style
.zIndex
, 10 ) + Math
.floor( this._
.editor
.config
.baseFloatZIndex
/ 2 ) );
1082 CKEDITOR
.dialog
._
.currentTop
= this._
.parentDialog
;
1084 // Deduct or clear the z-index.
1085 if ( !this._
.parentDialog
) {
1086 CKEDITOR
.dialog
._
.currentZIndex
= null;
1088 // Remove access key handlers.
1089 element
.removeListener( 'keydown', accessKeyDownHandler
);
1090 element
.removeListener( 'keyup', accessKeyUpHandler
);
1092 var editor
= this._
.editor
;
1095 // Give a while before unlock, waiting for focus to return to the editable. (http://dev.ckeditor.com/ticket/172)
1096 setTimeout( function() {
1097 editor
.focusManager
.unlock();
1099 // Fixed iOS focus issue (http://dev.ckeditor.com/ticket/12381).
1100 // Keep in mind that editor.focus() does not work in this case.
1101 if ( CKEDITOR
.env
.iOS
) {
1102 editor
.window
.focus();
1107 CKEDITOR
.dialog
._
.currentZIndex
-= 10;
1110 delete this._
.parentDialog
;
1111 // Reset the initial values of the dialog.
1112 this.foreach( function( contentObj
) {
1113 contentObj
.resetInitValue
&& contentObj
.resetInitValue();
1116 // Reset dialog state back to IDLE, if busy (http://dev.ckeditor.com/ticket/13213).
1117 this.setState( CKEDITOR
.DIALOG_STATE_IDLE
);
1121 * Adds a tabbed page into the dialog.
1123 * @param {Object} contents Content definition.
1125 addPage: function( contents
) {
1126 if ( contents
.requiredContent
&& !this._
.editor
.filter
.check( contents
.requiredContent
) )
1130 titleHtml
= contents
.label
? ' title="' + CKEDITOR
.tools
.htmlEncode( contents
.label
) + '"' : '',
1131 vbox
= CKEDITOR
.dialog
._
.uiElementBuilders
.vbox
.build( this, {
1133 className: 'cke_dialog_page_contents',
1134 children: contents
.elements
,
1135 expand: !!contents
.expand
,
1136 padding: contents
.padding
,
1137 style: contents
.style
|| 'width: 100%;'
1140 var contentMap
= this._
.contents
[ contents
.id
] = {},
1142 children
= vbox
.getChild(),
1145 while ( ( cursor
= children
.shift() ) ) {
1146 // Count all allowed fields.
1147 if ( !cursor
.notAllowed
&& cursor
.type
!= 'hbox' && cursor
.type
!= 'vbox' )
1150 contentMap
[ cursor
.id
] = cursor
;
1151 if ( typeof cursor
.getChild
== 'function' )
1152 children
.push
.apply( children
, cursor
.getChild() );
1155 // If all fields are disabled (because they are not allowed) hide this tab.
1156 if ( !enabledFields
)
1157 contents
.hidden
= true;
1159 // Create the HTML for the tab and the content block.
1160 var page
= CKEDITOR
.dom
.element
.createFromHtml( pageHtml
.join( '' ) );
1161 page
.setAttribute( 'role', 'tabpanel' );
1163 var env
= CKEDITOR
.env
;
1164 var tabId
= 'cke_' + contents
.id
+ '_' + CKEDITOR
.tools
.getNextNumber(),
1165 tab
= CKEDITOR
.dom
.element
.createFromHtml( [
1166 '<a class="cke_dialog_tab"',
1167 ( this._
.pageCount
> 0 ? ' cke_last' : 'cke_first' ),
1169 ( !!contents
.hidden
? ' style="display:none"' : '' ),
1170 ' id="', tabId
, '"',
1171 env
.gecko
&& !env
.hc
? '' : ' href="javascript:void(0)"',
1173 ' hidefocus="true"',
1179 page
.setAttribute( 'aria-labelledby', tabId
);
1181 // Take records for the tabs and elements created.
1182 this._
.tabs
[ contents
.id
] = [ tab
, page
];
1183 this._
.tabIdList
.push( contents
.id
);
1184 !contents
.hidden
&& this._
.pageCount
++;
1185 this._
.lastTab
= tab
;
1188 // Attach the DOM nodes.
1190 page
.setAttribute( 'name', contents
.id
);
1191 page
.appendTo( this.parts
.contents
);
1194 this.parts
.tabs
.append( tab
);
1196 // Add access key handlers if access key is defined.
1197 if ( contents
.accessKey
) {
1198 registerAccessKey( this, this, 'CTRL+' + contents
.accessKey
, tabAccessKeyDown
, tabAccessKeyUp
);
1199 this._
.accessKeyMap
[ 'CTRL+' + contents
.accessKey
] = contents
.id
;
1204 * Activates a tab page in the dialog by its id.
1206 * dialogObj.selectPage( 'tab_1' );
1208 * @param {String} id The id of the dialog tab to be activated.
1210 selectPage: function( id
) {
1211 if ( this._
.currentTabId
== id
)
1214 if ( this._
.tabs
[ id
][ 0 ].hasClass( 'cke_dialog_tab_disabled' ) )
1217 // If event was canceled - do nothing.
1218 if ( this.fire( 'selectPage', { page: id
, currentPage: this._
.currentTabId
} ) === false )
1221 // Hide the non-selected tabs and pages.
1222 for ( var i
in this._
.tabs
) {
1223 var tab
= this._
.tabs
[ i
][ 0 ],
1224 page
= this._
.tabs
[ i
][ 1 ];
1226 tab
.removeClass( 'cke_dialog_tab_selected' );
1229 page
.setAttribute( 'aria-hidden', i
!= id
);
1232 var selected
= this._
.tabs
[ id
];
1233 selected
[ 0 ].addClass( 'cke_dialog_tab_selected' );
1235 // [IE] an invisible input[type='text'] will enlarge it's width
1236 // if it's value is long when it shows, so we clear it's value
1237 // before it shows and then recover it (http://dev.ckeditor.com/ticket/5649)
1238 if ( CKEDITOR
.env
.ie6Compat
|| CKEDITOR
.env
.ie7Compat
) {
1239 clearOrRecoverTextInputValue( selected
[ 1 ] );
1240 selected
[ 1 ].show();
1241 setTimeout( function() {
1242 clearOrRecoverTextInputValue( selected
[ 1 ], 1 );
1245 selected
[ 1 ].show();
1248 this._
.currentTabId
= id
;
1249 this._
.currentTabIndex
= CKEDITOR
.tools
.indexOf( this._
.tabIdList
, id
);
1253 * Dialog state-specific style updates.
1255 updateStyle: function() {
1256 // If only a single page shown, a different style is used in the central pane.
1257 this.parts
.dialog
[ ( this._
.pageCount
=== 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' );
1261 * Hides a page's tab away from the dialog.
1263 * dialog.hidePage( 'tab_3' );
1265 * @param {String} id The page's Id.
1267 hidePage: function( id
) {
1268 var tab
= this._
.tabs
[ id
] && this._
.tabs
[ id
][ 0 ];
1269 if ( !tab
|| this._
.pageCount
== 1 || !tab
.isVisible() )
1271 // Switch to other tab first when we're hiding the active tab.
1272 else if ( id
== this._
.currentTabId
)
1273 this.selectPage( getPreviousVisibleTab
.call( this ) );
1281 * Unhides a page's tab.
1283 * dialog.showPage( 'tab_2' );
1285 * @param {String} id The page's Id.
1287 showPage: function( id
) {
1288 var tab
= this._
.tabs
[ id
] && this._
.tabs
[ id
][ 0 ];
1297 * Gets the root DOM element of the dialog.
1299 * var dialogElement = dialogObj.getElement().getFirst();
1300 * dialogElement.setStyle( 'padding', '5px' );
1302 * @returns {CKEDITOR.dom.element} The `<span>` element containing this dialog.
1304 getElement: function() {
1305 return this._
.element
;
1309 * Gets the name of the dialog.
1311 * var dialogName = dialogObj.getName();
1313 * @returns {String} The name of this dialog.
1315 getName: function() {
1320 * Gets a dialog UI element object from a dialog page.
1322 * dialogObj.getContentElement( 'tabId', 'elementId' ).setValue( 'Example' );
1324 * @param {String} pageId id of dialog page.
1325 * @param {String} elementId id of UI element.
1326 * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element.
1328 getContentElement: function( pageId
, elementId
) {
1329 var page
= this._
.contents
[ pageId
];
1330 return page
&& page
[ elementId
];
1334 * Gets the value of a dialog UI element.
1336 * alert( dialogObj.getValueOf( 'tabId', 'elementId' ) );
1338 * @param {String} pageId id of dialog page.
1339 * @param {String} elementId id of UI element.
1340 * @returns {Object} The value of the UI element.
1342 getValueOf: function( pageId
, elementId
) {
1343 return this.getContentElement( pageId
, elementId
).getValue();
1347 * Sets the value of a dialog UI element.
1349 * dialogObj.setValueOf( 'tabId', 'elementId', 'Example' );
1351 * @param {String} pageId id of the dialog page.
1352 * @param {String} elementId id of the UI element.
1353 * @param {Object} value The new value of the UI element.
1355 setValueOf: function( pageId
, elementId
, value
) {
1356 return this.getContentElement( pageId
, elementId
).setValue( value
);
1360 * Gets the UI element of a button in the dialog's button row.
1362 * @returns {CKEDITOR.ui.dialog.button} The button object.
1364 * @param {String} id The id of the button.
1366 getButton: function( id
) {
1367 return this._
.buttons
[ id
];
1371 * Simulates a click to a dialog button in the dialog's button row.
1373 * @returns The return value of the dialog's `click` event.
1375 * @param {String} id The id of the button.
1377 click: function( id
) {
1378 return this._
.buttons
[ id
].click();
1382 * Disables a dialog button.
1384 * @param {String} id The id of the button.
1386 disableButton: function( id
) {
1387 return this._
.buttons
[ id
].disable();
1391 * Enables a dialog button.
1393 * @param {String} id The id of the button.
1395 enableButton: function( id
) {
1396 return this._
.buttons
[ id
].enable();
1400 * Gets the number of pages in the dialog.
1402 * @returns {Number} Page count.
1404 getPageCount: function() {
1405 return this._
.pageCount
;
1409 * Gets the editor instance which opened this dialog.
1411 * @returns {CKEDITOR.editor} Parent editor instances.
1413 getParentEditor: function() {
1414 return this._
.editor
;
1418 * Gets the element that was selected when opening the dialog, if any.
1420 * @returns {CKEDITOR.dom.element} The element that was selected, or `null`.
1422 getSelectedElement: function() {
1423 return this.getParentEditor().getSelection().getSelectedElement();
1427 * Adds element to dialog's focusable list.
1429 * @param {CKEDITOR.dom.element} element
1430 * @param {Number} [index]
1432 addFocusable: function( element
, index
) {
1433 if ( typeof index
== 'undefined' ) {
1434 index
= this._
.focusList
.length
;
1435 this._
.focusList
.push( new Focusable( this, element
, index
) );
1437 this._
.focusList
.splice( index
, 0, new Focusable( this, element
, index
) );
1438 for ( var i
= index
+ 1; i
< this._
.focusList
.length
; i
++ )
1439 this._
.focusList
[ i
].focusIndex
++;
1444 * Sets the dialog {@link #property-state}.
1447 * @param {Number} state Either {@link CKEDITOR#DIALOG_STATE_IDLE} or {@link CKEDITOR#DIALOG_STATE_BUSY}.
1449 setState: function( state
) {
1450 var oldState
= this.state
;
1452 if ( oldState
== state
) {
1458 if ( state
== CKEDITOR
.DIALOG_STATE_BUSY
) {
1459 // Insert the spinner on demand.
1460 if ( !this.parts
.spinner
) {
1461 var dir
= this.getParentEditor().lang
.dir
,
1464 'class': 'cke_dialog_spinner'
1467 'float': dir
== 'rtl' ? 'right' : 'left'
1471 spinnerDef
.styles
[ 'margin-' + ( dir
== 'rtl' ? 'left' : 'right' ) ] = '8px';
1473 this.parts
.spinner
= CKEDITOR
.document
.createElement( 'div', spinnerDef
);
1475 this.parts
.spinner
.setHtml( '⌛' );
1476 this.parts
.spinner
.appendTo( this.parts
.title
, 1 );
1479 // Finally, show the spinner.
1480 this.parts
.spinner
.show();
1482 this.getButton( 'ok' ).disable();
1483 } else if ( state
== CKEDITOR
.DIALOG_STATE_IDLE
) {
1484 // Hide the spinner. But don't do anything if there is no spinner yet.
1485 this.parts
.spinner
&& this.parts
.spinner
.hide();
1487 this.getButton( 'ok' ).enable();
1490 this.fire( 'state', state
);
1494 CKEDITOR
.tools
.extend( CKEDITOR
.dialog
, {
1496 * Registers a dialog.
1498 * // Full sample plugin, which does not only register a dialog window but also adds an item to the context menu.
1499 * // To open the dialog window, choose "Open dialog" in the context menu.
1500 * CKEDITOR.plugins.add( 'myplugin', {
1501 * init: function( editor ) {
1502 * editor.addCommand( 'mydialog',new CKEDITOR.dialogCommand( 'mydialog' ) );
1504 * if ( editor.contextMenu ) {
1505 * editor.addMenuGroup( 'mygroup', 10 );
1506 * editor.addMenuItem( 'My Dialog', {
1507 * label: 'Open dialog',
1508 * command: 'mydialog',
1511 * editor.contextMenu.addListener( function( element ) {
1512 * return { 'My Dialog': CKEDITOR.TRISTATE_OFF };
1516 * CKEDITOR.dialog.add( 'mydialog', function( api ) {
1517 * // CKEDITOR.dialog.definition
1518 * var dialogDefinition = {
1519 * title: 'Sample dialog',
1532 * html: '<p>This is some sample HTML content.</p>'
1543 * buttons: [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ],
1544 * onOk: function() {
1545 * // "this" is now a CKEDITOR.dialog object.
1546 * // Accessing dialog elements:
1547 * var textareaObj = this.getContentElement( 'tab1', 'textareaId' );
1548 * alert( "You have entered: " + textareaObj.getValue() );
1552 * return dialogDefinition;
1557 * CKEDITOR.replace( 'editor1', { extraPlugins: 'myplugin' } );
1560 * @param {String} name The dialog's name.
1561 * @param {Function/String} dialogDefinition
1562 * A function returning the dialog's definition, or the URL to the `.js` file holding the function.
1563 * The function should accept an argument `editor` which is the current editor instance, and
1564 * return an object conforming to {@link CKEDITOR.dialog.definition}.
1565 * @see CKEDITOR.dialog.definition
1567 add: function( name
, dialogDefinition
) {
1568 // Avoid path registration from multiple instances override definition.
1569 if ( !this._
.dialogDefinitions
[ name
] || typeof dialogDefinition
== 'function' )
1570 this._
.dialogDefinitions
[ name
] = dialogDefinition
;
1577 exists: function( name
) {
1578 return !!this._
.dialogDefinitions
[ name
];
1585 getCurrent: function() {
1586 return CKEDITOR
.dialog
._
.currentTop
;
1590 * Check whether tab wasn't removed by {@link CKEDITOR.config#removeDialogTabs}.
1594 * @param {CKEDITOR.editor} editor
1595 * @param {String} dialogName
1596 * @param {String} tabName
1597 * @returns {Boolean}
1599 isTabEnabled: function( editor
, dialogName
, tabName
) {
1600 var cfg
= editor
.config
.removeDialogTabs
;
1602 return !( cfg
&& cfg
.match( new RegExp( '(?:^|;)' + dialogName
+ ':' + tabName
+ '(?:$|;)', 'i' ) ) );
1606 * The default OK button for dialogs. Fires the `ok` event and closes the dialog if the event succeeds.
1611 okButton: ( function() {
1612 var retval = function( editor
, override
) {
1613 override
= override
|| {};
1614 return CKEDITOR
.tools
.extend( {
1617 label: editor
.lang
.common
.ok
,
1618 'class': 'cke_dialog_ui_button_ok',
1619 onClick: function( evt
) {
1620 var dialog
= evt
.data
.dialog
;
1621 if ( dialog
.fire( 'ok', { hide: true } ).hide
!== false )
1624 }, override
, true );
1626 retval
.type
= 'button';
1627 retval
.override = function( override
) {
1628 return CKEDITOR
.tools
.extend( function( editor
) {
1629 return retval( editor
, override
);
1630 }, { type: 'button' }, true );
1636 * The default cancel button for dialogs. Fires the `cancel` event and
1637 * closes the dialog if no UI element value changed.
1642 cancelButton: ( function() {
1643 var retval = function( editor
, override
) {
1644 override
= override
|| {};
1645 return CKEDITOR
.tools
.extend( {
1648 label: editor
.lang
.common
.cancel
,
1649 'class': 'cke_dialog_ui_button_cancel',
1650 onClick: function( evt
) {
1651 var dialog
= evt
.data
.dialog
;
1652 if ( dialog
.fire( 'cancel', { hide: true } ).hide
!== false )
1655 }, override
, true );
1657 retval
.type
= 'button';
1658 retval
.override = function( override
) {
1659 return CKEDITOR
.tools
.extend( function( editor
) {
1660 return retval( editor
, override
);
1661 }, { type: 'button' }, true );
1667 * Registers a dialog UI element.
1670 * @param {String} typeName The name of the UI element.
1671 * @param {Function} builder The function to build the UI element.
1673 addUIElement: function( typeName
, builder
) {
1674 this._
.uiElementBuilders
[ typeName
] = builder
;
1678 CKEDITOR
.dialog
._
= {
1679 uiElementBuilders: {},
1681 dialogDefinitions: {},
1688 // "Inherit" (copy actually) from CKEDITOR.event.
1689 CKEDITOR
.event
.implementOn( CKEDITOR
.dialog
);
1690 CKEDITOR
.event
.implementOn( CKEDITOR
.dialog
.prototype );
1692 var defaultDialogDefinition
= {
1693 resizable: CKEDITOR
.DIALOG_RESIZE_BOTH
,
1696 buttons: [ CKEDITOR
.dialog
.okButton
, CKEDITOR
.dialog
.cancelButton
]
1699 // Tool function used to return an item from an array based on its id
1701 var getById = function( array
, id
, recurse
) {
1702 for ( var i
= 0, item
;
1703 ( item
= array
[ i
] ); i
++ ) {
1704 if ( item
.id
== id
)
1706 if ( recurse
&& item
[ recurse
] ) {
1707 var retval
= getById( item
[ recurse
], id
, recurse
);
1715 // Tool function used to add an item into an array.
1716 var addById = function( array
, newItem
, nextSiblingId
, recurse
, nullIfNotFound
) {
1717 if ( nextSiblingId
) {
1718 for ( var i
= 0, item
;
1719 ( item
= array
[ i
] ); i
++ ) {
1720 if ( item
.id
== nextSiblingId
) {
1721 array
.splice( i
, 0, newItem
);
1725 if ( recurse
&& item
[ recurse
] ) {
1726 var retval
= addById( item
[ recurse
], newItem
, nextSiblingId
, recurse
, true );
1732 if ( nullIfNotFound
)
1736 array
.push( newItem
);
1740 // Tool function used to remove an item from an array based on its id.
1741 var removeById = function( array
, id
, recurse
) {
1742 for ( var i
= 0, item
;
1743 ( item
= array
[ i
] ); i
++ ) {
1744 if ( item
.id
== id
)
1745 return array
.splice( i
, 1 );
1746 if ( recurse
&& item
[ recurse
] ) {
1747 var retval
= removeById( item
[ recurse
], id
, recurse
);
1756 * This class is not really part of the API. It is the `definition` property value
1757 * passed to `dialogDefinition` event handlers.
1759 * CKEDITOR.on( 'dialogDefinition', function( evt ) {
1760 * var definition = evt.data.definition;
1761 * var content = definition.getContents( 'page1' );
1766 * @class CKEDITOR.dialog.definitionObject
1767 * @extends CKEDITOR.dialog.definition
1768 * @constructor Creates a definitionObject class instance.
1770 var definitionObject = function( dialog
, dialogDefinition
) {
1771 // TODO : Check if needed.
1772 this.dialog
= dialog
;
1774 // Transform the contents entries in contentObjects.
1775 var contents
= dialogDefinition
.contents
;
1776 for ( var i
= 0, content
;
1777 ( content
= contents
[ i
] ); i
++ )
1778 contents
[ i
] = content
&& new contentObject( dialog
, content
);
1780 CKEDITOR
.tools
.extend( this, dialogDefinition
);
1783 definitionObject
.prototype = {
1785 * Gets a content definition.
1787 * @param {String} id The id of the content definition.
1788 * @returns {CKEDITOR.dialog.definition.content} The content definition matching id.
1790 getContents: function( id
) {
1791 return getById( this.contents
, id
);
1795 * Gets a button definition.
1797 * @param {String} id The id of the button definition.
1798 * @returns {CKEDITOR.dialog.definition.button} The button definition matching id.
1800 getButton: function( id
) {
1801 return getById( this.buttons
, id
);
1805 * Adds a content definition object under this dialog definition.
1807 * @param {CKEDITOR.dialog.definition.content} contentDefinition The
1808 * content definition.
1809 * @param {String} [nextSiblingId] The id of an existing content
1810 * definition which the new content definition will be inserted
1811 * before. Omit if the new content definition is to be inserted as
1813 * @returns {CKEDITOR.dialog.definition.content} The inserted content definition.
1815 addContents: function( contentDefinition
, nextSiblingId
) {
1816 return addById( this.contents
, contentDefinition
, nextSiblingId
);
1820 * Adds a button definition object under this dialog definition.
1822 * @param {CKEDITOR.dialog.definition.button} buttonDefinition The
1823 * button definition.
1824 * @param {String} [nextSiblingId] The id of an existing button
1825 * definition which the new button definition will be inserted
1826 * before. Omit if the new button definition is to be inserted as
1828 * @returns {CKEDITOR.dialog.definition.button} The inserted button definition.
1830 addButton: function( buttonDefinition
, nextSiblingId
) {
1831 return addById( this.buttons
, buttonDefinition
, nextSiblingId
);
1835 * Removes a content definition from this dialog definition.
1837 * @param {String} id The id of the content definition to be removed.
1838 * @returns {CKEDITOR.dialog.definition.content} The removed content definition.
1840 removeContents: function( id
) {
1841 removeById( this.contents
, id
);
1845 * Removes a button definition from the dialog definition.
1847 * @param {String} id The id of the button definition to be removed.
1848 * @returns {CKEDITOR.dialog.definition.button} The removed button definition.
1850 removeButton: function( id
) {
1851 removeById( this.buttons
, id
);
1856 * This class is not really part of the API. It is the template of the
1857 * objects representing content pages inside the
1858 * CKEDITOR.dialog.definitionObject.
1860 * CKEDITOR.on( 'dialogDefinition', function( evt ) {
1861 * var definition = evt.data.definition;
1862 * var content = definition.getContents( 'page1' );
1863 * content.remove( 'textInput1' );
1868 * @class CKEDITOR.dialog.definition.contentObject
1869 * @constructor Creates a contentObject class instance.
1871 function contentObject( dialog
, contentDefinition
) {
1876 CKEDITOR
.tools
.extend( this, contentDefinition
);
1879 contentObject
.prototype = {
1881 * Gets a UI element definition under the content definition.
1883 * @param {String} id The id of the UI element definition.
1884 * @returns {CKEDITOR.dialog.definition.uiElement}
1886 get: function( id
) {
1887 return getById( this.elements
, id
, 'children' );
1891 * Adds a UI element definition to the content definition.
1893 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition The
1894 * UI elemnet definition to be added.
1895 * @param {String} nextSiblingId The id of an existing UI element
1896 * definition which the new UI element definition will be inserted
1897 * before. Omit if the new button definition is to be inserted as
1899 * @returns {CKEDITOR.dialog.definition.uiElement} The element definition inserted.
1901 add: function( elementDefinition
, nextSiblingId
) {
1902 return addById( this.elements
, elementDefinition
, nextSiblingId
, 'children' );
1906 * Removes a UI element definition from the content definition.
1908 * @param {String} id The id of the UI element definition to be removed.
1909 * @returns {CKEDITOR.dialog.definition.uiElement} The element definition removed.
1911 remove: function( id
) {
1912 removeById( this.elements
, id
, 'children' );
1916 function initDragAndDrop( dialog
) {
1917 var lastCoords
= null,
1918 abstractDialogCoords
= null,
1919 editor
= dialog
.getParentEditor(),
1920 magnetDistance
= editor
.config
.dialog_magnetDistance
,
1921 margins
= CKEDITOR
.skin
.margins
|| [ 0, 0, 0, 0 ];
1923 if ( typeof magnetDistance
== 'undefined' )
1924 magnetDistance
= 20;
1926 function mouseMoveHandler( evt
) {
1927 var dialogSize
= dialog
.getSize(),
1928 viewPaneSize
= CKEDITOR
.document
.getWindow().getViewPaneSize(),
1929 x
= evt
.data
.$.screenX
,
1930 y
= evt
.data
.$.screenY
,
1931 dx
= x
- lastCoords
.x
,
1932 dy
= y
- lastCoords
.y
,
1935 lastCoords
= { x: x
, y: y
};
1936 abstractDialogCoords
.x
+= dx
;
1937 abstractDialogCoords
.y
+= dy
;
1939 if ( abstractDialogCoords
.x
+ margins
[ 3 ] < magnetDistance
)
1940 realX
= -margins
[ 3 ];
1941 else if ( abstractDialogCoords
.x
- margins
[ 1 ] > viewPaneSize
.width
- dialogSize
.width
- magnetDistance
)
1942 realX
= viewPaneSize
.width
- dialogSize
.width
+ ( editor
.lang
.dir
== 'rtl' ? 0 : margins
[ 1 ] );
1944 realX
= abstractDialogCoords
.x
;
1946 if ( abstractDialogCoords
.y
+ margins
[ 0 ] < magnetDistance
)
1947 realY
= -margins
[ 0 ];
1948 else if ( abstractDialogCoords
.y
- margins
[ 2 ] > viewPaneSize
.height
- dialogSize
.height
- magnetDistance
)
1949 realY
= viewPaneSize
.height
- dialogSize
.height
+ margins
[ 2 ];
1951 realY
= abstractDialogCoords
.y
;
1953 dialog
.move( realX
, realY
, 1 );
1955 evt
.data
.preventDefault();
1958 function mouseUpHandler() {
1959 CKEDITOR
.document
.removeListener( 'mousemove', mouseMoveHandler
);
1960 CKEDITOR
.document
.removeListener( 'mouseup', mouseUpHandler
);
1962 if ( CKEDITOR
.env
.ie6Compat
) {
1963 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
1964 coverDoc
.removeListener( 'mousemove', mouseMoveHandler
);
1965 coverDoc
.removeListener( 'mouseup', mouseUpHandler
);
1969 dialog
.parts
.title
.on( 'mousedown', function( evt
) {
1970 lastCoords
= { x: evt
.data
.$.screenX
, y: evt
.data
.$.screenY
};
1972 CKEDITOR
.document
.on( 'mousemove', mouseMoveHandler
);
1973 CKEDITOR
.document
.on( 'mouseup', mouseUpHandler
);
1974 abstractDialogCoords
= dialog
.getPosition();
1976 if ( CKEDITOR
.env
.ie6Compat
) {
1977 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
1978 coverDoc
.on( 'mousemove', mouseMoveHandler
);
1979 coverDoc
.on( 'mouseup', mouseUpHandler
);
1982 evt
.data
.preventDefault();
1986 function initResizeHandles( dialog
) {
1987 var def
= dialog
.definition
,
1988 resizable
= def
.resizable
;
1990 if ( resizable
== CKEDITOR
.DIALOG_RESIZE_NONE
)
1993 var editor
= dialog
.getParentEditor();
1994 var wrapperWidth
, wrapperHeight
, viewSize
, origin
, startSize
, dialogCover
;
1996 var mouseDownFn
= CKEDITOR
.tools
.addFunction( function( $event
) {
1997 startSize
= dialog
.getSize();
1999 var content
= dialog
.parts
.contents
,
2000 iframeDialog
= content
.$.getElementsByTagName( 'iframe' ).length
;
2002 // Shim to help capturing "mousemove" over iframe.
2003 if ( iframeDialog
) {
2004 dialogCover
= CKEDITOR
.dom
.element
.createFromHtml( '<div class="cke_dialog_resize_cover" style="height: 100%; position: absolute; width: 100%;"></div>' );
2005 content
.append( dialogCover
);
2008 // Calculate the offset between content and chrome size.
2009 wrapperHeight
= startSize
.height
- dialog
.parts
.contents
.getSize( 'height', !( CKEDITOR
.env
.gecko
|| CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
) );
2010 wrapperWidth
= startSize
.width
- dialog
.parts
.contents
.getSize( 'width', 1 );
2012 origin
= { x: $event
.screenX
, y: $event
.screenY
};
2014 viewSize
= CKEDITOR
.document
.getWindow().getViewPaneSize();
2016 CKEDITOR
.document
.on( 'mousemove', mouseMoveHandler
);
2017 CKEDITOR
.document
.on( 'mouseup', mouseUpHandler
);
2019 if ( CKEDITOR
.env
.ie6Compat
) {
2020 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
2021 coverDoc
.on( 'mousemove', mouseMoveHandler
);
2022 coverDoc
.on( 'mouseup', mouseUpHandler
);
2025 $event
.preventDefault
&& $event
.preventDefault();
2028 // Prepend the grip to the dialog.
2029 dialog
.on( 'load', function() {
2031 if ( resizable
== CKEDITOR
.DIALOG_RESIZE_WIDTH
)
2032 direction
= ' cke_resizer_horizontal';
2033 else if ( resizable
== CKEDITOR
.DIALOG_RESIZE_HEIGHT
)
2034 direction
= ' cke_resizer_vertical';
2035 var resizer
= CKEDITOR
.dom
.element
.createFromHtml(
2037 ' class="cke_resizer' + direction
+ ' cke_resizer_' + editor
.lang
.dir
+ '"' +
2038 ' title="' + CKEDITOR
.tools
.htmlEncode( editor
.lang
.common
.resize
) + '"' +
2039 ' onmousedown="CKEDITOR.tools.callFunction(' + mouseDownFn
+ ', event )">' +
2040 // BLACK LOWER RIGHT TRIANGLE (ltr)
2041 // BLACK LOWER LEFT TRIANGLE (rtl)
2042 ( editor
.lang
.dir
== 'ltr' ? '\u25E2' : '\u25E3' ) +
2044 dialog
.parts
.footer
.append( resizer
, 1 );
2046 editor
.on( 'destroy', function() {
2047 CKEDITOR
.tools
.removeFunction( mouseDownFn
);
2050 function mouseMoveHandler( evt
) {
2051 var rtl
= editor
.lang
.dir
== 'rtl',
2052 dx
= ( evt
.data
.$.screenX
- origin
.x
) * ( rtl
? -1 : 1 ),
2053 dy
= evt
.data
.$.screenY
- origin
.y
,
2054 width
= startSize
.width
,
2055 height
= startSize
.height
,
2056 internalWidth
= width
+ dx
* ( dialog
._
.moved
? 1 : 2 ),
2057 internalHeight
= height
+ dy
* ( dialog
._
.moved
? 1 : 2 ),
2058 element
= dialog
._
.element
.getFirst(),
2059 right
= rtl
&& element
.getComputedStyle( 'right' ),
2060 position
= dialog
.getPosition();
2062 if ( position
.y
+ internalHeight
> viewSize
.height
)
2063 internalHeight
= viewSize
.height
- position
.y
;
2065 if ( ( rtl
? right : position
.x
) + internalWidth
> viewSize
.width
)
2066 internalWidth
= viewSize
.width
- ( rtl
? right : position
.x
);
2068 // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL.
2069 if ( ( resizable
== CKEDITOR
.DIALOG_RESIZE_WIDTH
|| resizable
== CKEDITOR
.DIALOG_RESIZE_BOTH
) )
2070 width
= Math
.max( def
.minWidth
|| 0, internalWidth
- wrapperWidth
);
2072 if ( resizable
== CKEDITOR
.DIALOG_RESIZE_HEIGHT
|| resizable
== CKEDITOR
.DIALOG_RESIZE_BOTH
)
2073 height
= Math
.max( def
.minHeight
|| 0, internalHeight
- wrapperHeight
);
2075 dialog
.resize( width
, height
);
2077 if ( !dialog
._
.moved
)
2080 evt
.data
.preventDefault();
2083 function mouseUpHandler() {
2084 CKEDITOR
.document
.removeListener( 'mouseup', mouseUpHandler
);
2085 CKEDITOR
.document
.removeListener( 'mousemove', mouseMoveHandler
);
2087 if ( dialogCover
) {
2088 dialogCover
.remove();
2092 if ( CKEDITOR
.env
.ie6Compat
) {
2093 var coverDoc
= currentCover
.getChild( 0 ).getFrameDocument();
2094 coverDoc
.removeListener( 'mouseup', mouseUpHandler
);
2095 coverDoc
.removeListener( 'mousemove', mouseMoveHandler
);
2101 // Caching resuable covers and allowing only one cover
2106 function cancelEvent( ev
) {
2107 ev
.data
.preventDefault( 1 );
2110 function showCover( editor
) {
2111 var win
= CKEDITOR
.document
.getWindow(),
2112 config
= editor
.config
,
2113 skinName
= ( CKEDITOR
.skinName
|| editor
.config
.skin
),
2114 backgroundColorStyle
= config
.dialog_backgroundCoverColor
|| ( skinName
== 'moono-lisa' ? 'black' : 'white' ),
2115 backgroundCoverOpacity
= config
.dialog_backgroundCoverOpacity
,
2116 baseFloatZIndex
= config
.baseFloatZIndex
,
2117 coverKey
= CKEDITOR
.tools
.genKey( backgroundColorStyle
, backgroundCoverOpacity
, baseFloatZIndex
),
2118 coverElement
= covers
[ coverKey
];
2120 if ( !coverElement
) {
2122 '<div tabIndex="-1" style="position: ', ( CKEDITOR
.env
.ie6Compat
? 'absolute' : 'fixed' ),
2123 '; z-index: ', baseFloatZIndex
,
2124 '; top: 0px; left: 0px; ',
2125 ( !CKEDITOR
.env
.ie6Compat
? 'background-color: ' + backgroundColorStyle : '' ),
2126 '" class="cke_dialog_background_cover">'
2129 if ( CKEDITOR
.env
.ie6Compat
) {
2130 // Support for custom document.domain in IE.
2131 var iframeHtml
= '<html><body style=\\\'background-color:' + backgroundColorStyle
+ ';\\\'></body></html>';
2133 html
.push( '<iframe' +
2134 ' hidefocus="true"' +
2135 ' frameborder="0"' +
2136 ' id="cke_dialog_background_iframe"' +
2137 ' src="javascript:' );
2139 html
.push( 'void((function(){' + encodeURIComponent(
2140 'document.open();' +
2141 // Support for custom document.domain in IE.
2142 '(' + CKEDITOR
.tools
.fixDomain
+ ')();' +
2143 'document.write( \'' + iframeHtml
+ '\' );' +
2149 'position:absolute;' +
2154 'filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0)">' +
2158 html
.push( '</div>' );
2160 coverElement
= CKEDITOR
.dom
.element
.createFromHtml( html
.join( '' ) );
2161 coverElement
.setOpacity( backgroundCoverOpacity
!== undefined ? backgroundCoverOpacity : 0.5 );
2163 coverElement
.on( 'keydown', cancelEvent
);
2164 coverElement
.on( 'keypress', cancelEvent
);
2165 coverElement
.on( 'keyup', cancelEvent
);
2167 coverElement
.appendTo( CKEDITOR
.document
.getBody() );
2168 covers
[ coverKey
] = coverElement
;
2170 coverElement
.show();
2173 // Makes the dialog cover a focus holder as well.
2174 editor
.focusManager
.add( coverElement
);
2176 currentCover
= coverElement
;
2177 var resizeFunc = function() {
2178 var size
= win
.getViewPaneSize();
2179 coverElement
.setStyles( {
2180 width: size
.width
+ 'px',
2181 height: size
.height
+ 'px'
2185 var scrollFunc = function() {
2186 var pos
= win
.getScrollPosition(),
2187 cursor
= CKEDITOR
.dialog
._
.currentTop
;
2188 coverElement
.setStyles( {
2195 var dialogPos
= cursor
.getPosition();
2196 cursor
.move( dialogPos
.x
, dialogPos
.y
);
2197 } while ( ( cursor
= cursor
._
.parentDialog
) );
2201 resizeCover
= resizeFunc
;
2202 win
.on( 'resize', resizeFunc
);
2204 // Using Safari/Mac, focus must be kept where it is (http://dev.ckeditor.com/ticket/7027)
2205 if ( !( CKEDITOR
.env
.mac
&& CKEDITOR
.env
.webkit
) )
2206 coverElement
.focus();
2208 if ( CKEDITOR
.env
.ie6Compat
) {
2209 // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll.
2210 // So we need to invent a really funny way to make it work.
2211 var myScrollHandler = function() {
2213 arguments
.callee
.prevScrollHandler
.apply( this, arguments
);
2215 win
.$.setTimeout( function() {
2216 myScrollHandler
.prevScrollHandler
= window
.onscroll
||
2218 window
.onscroll
= myScrollHandler
;
2224 function hideCover( editor
) {
2225 if ( !currentCover
)
2228 editor
.focusManager
.remove( currentCover
);
2229 var win
= CKEDITOR
.document
.getWindow();
2230 currentCover
.hide();
2231 win
.removeListener( 'resize', resizeCover
);
2233 if ( CKEDITOR
.env
.ie6Compat
) {
2234 win
.$.setTimeout( function() {
2235 var prevScrollHandler
= window
.onscroll
&& window
.onscroll
.prevScrollHandler
;
2236 window
.onscroll
= prevScrollHandler
|| null;
2242 function removeCovers() {
2243 for ( var coverId
in covers
)
2244 covers
[ coverId
].remove();
2248 var accessKeyProcessors
= {};
2250 var accessKeyDownHandler = function( evt
) {
2251 var ctrl
= evt
.data
.$.ctrlKey
|| evt
.data
.$.metaKey
,
2252 alt
= evt
.data
.$.altKey
,
2253 shift
= evt
.data
.$.shiftKey
,
2254 key
= String
.fromCharCode( evt
.data
.$.keyCode
),
2255 keyProcessor
= accessKeyProcessors
[ ( ctrl
? 'CTRL+' : '' ) + ( alt
? 'ALT+' : '' ) + ( shift
? 'SHIFT+' : '' ) + key
];
2257 if ( !keyProcessor
|| !keyProcessor
.length
)
2260 keyProcessor
= keyProcessor
[ keyProcessor
.length
- 1 ];
2261 keyProcessor
.keydown
&& keyProcessor
.keydown
.call( keyProcessor
.uiElement
, keyProcessor
.dialog
, keyProcessor
.key
);
2262 evt
.data
.preventDefault();
2265 var accessKeyUpHandler = function( evt
) {
2266 var ctrl
= evt
.data
.$.ctrlKey
|| evt
.data
.$.metaKey
,
2267 alt
= evt
.data
.$.altKey
,
2268 shift
= evt
.data
.$.shiftKey
,
2269 key
= String
.fromCharCode( evt
.data
.$.keyCode
),
2270 keyProcessor
= accessKeyProcessors
[ ( ctrl
? 'CTRL+' : '' ) + ( alt
? 'ALT+' : '' ) + ( shift
? 'SHIFT+' : '' ) + key
];
2272 if ( !keyProcessor
|| !keyProcessor
.length
)
2275 keyProcessor
= keyProcessor
[ keyProcessor
.length
- 1 ];
2276 if ( keyProcessor
.keyup
) {
2277 keyProcessor
.keyup
.call( keyProcessor
.uiElement
, keyProcessor
.dialog
, keyProcessor
.key
);
2278 evt
.data
.preventDefault();
2282 var registerAccessKey = function( uiElement
, dialog
, key
, downFunc
, upFunc
) {
2283 var procList
= accessKeyProcessors
[ key
] || ( accessKeyProcessors
[ key
] = [] );
2285 uiElement: uiElement
,
2288 keyup: upFunc
|| uiElement
.accessKeyUp
,
2289 keydown: downFunc
|| uiElement
.accessKeyDown
2293 var unregisterAccessKey = function( obj
) {
2294 for ( var i
in accessKeyProcessors
) {
2295 var list
= accessKeyProcessors
[ i
];
2296 for ( var j
= list
.length
- 1; j
>= 0; j
-- ) {
2297 if ( list
[ j
].dialog
== obj
|| list
[ j
].uiElement
== obj
)
2298 list
.splice( j
, 1 );
2300 if ( list
.length
=== 0 )
2301 delete accessKeyProcessors
[ i
];
2305 var tabAccessKeyUp = function( dialog
, key
) {
2306 if ( dialog
._
.accessKeyMap
[ key
] )
2307 dialog
.selectPage( dialog
._
.accessKeyMap
[ key
] );
2310 var tabAccessKeyDown = function() {};
2313 CKEDITOR
.ui
.dialog
= {
2315 * The base class of all dialog UI elements.
2317 * @class CKEDITOR.ui.dialog.uiElement
2318 * @constructor Creates a uiElement class instance.
2319 * @param {CKEDITOR.dialog} dialog Parent dialog object.
2320 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element
2325 * * `id` (Required) The id of the UI element. See {@link CKEDITOR.dialog#getContentElement}.
2326 * * `type` (Required) The type of the UI element. The
2327 * value to this field specifies which UI element class will be used to
2328 * generate the final widget.
2329 * * `title` (Optional) The popup tooltip for the UI
2331 * * `hidden` (Optional) A flag that tells if the element
2332 * should be initially visible.
2333 * * `className` (Optional) Additional CSS class names
2334 * to add to the UI element. Separated by space.
2335 * * `style` (Optional) Additional CSS inline styles
2336 * to add to the UI element. A semicolon (;) is required after the last
2337 * style declaration.
2338 * * `accessKey` (Optional) The alphanumeric access key
2339 * for this element. Access keys are automatically prefixed by CTRL.
2340 * * `on*` (Optional) Any UI element definition field that
2341 * starts with `on` followed immediately by a capital letter and
2342 * probably more letters is an event handler. Event handlers may be further
2343 * divided into registered event handlers and DOM event handlers. Please
2344 * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and
2345 * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more information.
2347 * @param {Array} htmlList
2348 * List of HTML code to be added to the dialog's content area.
2349 * @param {Function/String} [nodeNameArg='div']
2350 * A function returning a string, or a simple string for the node name for
2351 * the root DOM node.
2352 * @param {Function/Object} [stylesArg={}]
2353 * A function returning an object, or a simple object for CSS styles applied
2355 * @param {Function/Object} [attributesArg={}]
2356 * A fucntion returning an object, or a simple object for attributes applied
2358 * @param {Function/String} [contentsArg='']
2359 * A function returning a string, or a simple string for the HTML code inside
2360 * the root DOM node. Default is empty string.
2362 uiElement: function( dialog
, elementDefinition
, htmlList
, nodeNameArg
, stylesArg
, attributesArg
, contentsArg
) {
2363 if ( arguments
.length
< 4 )
2366 var nodeName
= ( nodeNameArg
.call
? nodeNameArg( elementDefinition
) : nodeNameArg
) || 'div',
2367 html
= [ '<', nodeName
, ' ' ],
2368 styles
= ( stylesArg
&& stylesArg
.call
? stylesArg( elementDefinition
) : stylesArg
) || {},
2369 attributes
= ( attributesArg
&& attributesArg
.call
? attributesArg( elementDefinition
) : attributesArg
) || {},
2370 innerHTML
= ( contentsArg
&& contentsArg
.call
? contentsArg
.call( this, dialog
, elementDefinition
) : contentsArg
) || '',
2371 domId
= this.domId
= attributes
.id
|| CKEDITOR
.tools
.getNextId() + '_uiElement',
2374 if ( elementDefinition
.requiredContent
&& !dialog
.getParentEditor().filter
.check( elementDefinition
.requiredContent
) ) {
2375 styles
.display
= 'none';
2376 this.notAllowed
= true;
2379 // Set the id, a unique id is required for getElement() to work.
2380 attributes
.id
= domId
;
2382 // Set the type and definition CSS class names.
2384 if ( elementDefinition
.type
)
2385 classes
[ 'cke_dialog_ui_' + elementDefinition
.type
] = 1;
2386 if ( elementDefinition
.className
)
2387 classes
[ elementDefinition
.className
] = 1;
2388 if ( elementDefinition
.disabled
)
2389 classes
.cke_disabled
= 1;
2391 var attributeClasses
= ( attributes
[ 'class' ] && attributes
[ 'class' ].split
) ? attributes
[ 'class' ].split( ' ' ) : [];
2392 for ( i
= 0; i
< attributeClasses
.length
; i
++ ) {
2393 if ( attributeClasses
[ i
] )
2394 classes
[ attributeClasses
[ i
] ] = 1;
2396 var finalClasses
= [];
2397 for ( i
in classes
)
2398 finalClasses
.push( i
);
2399 attributes
[ 'class' ] = finalClasses
.join( ' ' );
2401 // Set the popup tooltop.
2402 if ( elementDefinition
.title
)
2403 attributes
.title
= elementDefinition
.title
;
2405 // Write the inline CSS styles.
2406 var styleStr
= ( elementDefinition
.style
|| '' ).split( ';' );
2408 // Element alignment support.
2409 if ( elementDefinition
.align
) {
2410 var align
= elementDefinition
.align
;
2411 styles
[ 'margin-left' ] = align
== 'left' ? 0 : 'auto';
2412 styles
[ 'margin-right' ] = align
== 'right' ? 0 : 'auto';
2416 styleStr
.push( i
+ ':' + styles
[ i
] );
2417 if ( elementDefinition
.hidden
)
2418 styleStr
.push( 'display:none' );
2419 for ( i
= styleStr
.length
- 1; i
>= 0; i
-- ) {
2420 if ( styleStr
[ i
] === '' )
2421 styleStr
.splice( i
, 1 );
2423 if ( styleStr
.length
> 0 )
2424 attributes
.style
= ( attributes
.style
? ( attributes
.style
+ '; ' ) : '' ) + styleStr
.join( '; ' );
2426 // Write the attributes.
2427 for ( i
in attributes
)
2428 html
.push( i
+ '="' + CKEDITOR
.tools
.htmlEncode( attributes
[ i
] ) + '" ' );
2430 // Write the content HTML.
2431 html
.push( '>', innerHTML
, '</', nodeName
, '>' );
2433 // Add contents to the parent HTML array.
2434 htmlList
.push( html
.join( '' ) );
2436 ( this._
|| ( this._
= {} ) ).dialog
= dialog
;
2438 // Override isChanged if it is defined in element definition.
2439 if ( typeof elementDefinition
.isChanged
== 'boolean' )
2440 this.isChanged = function() {
2441 return elementDefinition
.isChanged
;
2443 if ( typeof elementDefinition
.isChanged
== 'function' )
2444 this.isChanged
= elementDefinition
.isChanged
;
2446 // Overload 'get(set)Value' on definition.
2447 if ( typeof elementDefinition
.setValue
== 'function' ) {
2448 this.setValue
= CKEDITOR
.tools
.override( this.setValue
, function( org
) {
2449 return function( val
) {
2450 org
.call( this, elementDefinition
.setValue
.call( this, val
) );
2455 if ( typeof elementDefinition
.getValue
== 'function' ) {
2456 this.getValue
= CKEDITOR
.tools
.override( this.getValue
, function( org
) {
2458 return elementDefinition
.getValue
.call( this, org
.call( this ) );
2464 CKEDITOR
.event
.implementOn( this );
2466 this.registerEvents( elementDefinition
);
2467 if ( this.accessKeyUp
&& this.accessKeyDown
&& elementDefinition
.accessKey
)
2468 registerAccessKey( this, dialog
, 'CTRL+' + elementDefinition
.accessKey
);
2471 dialog
.on( 'load', function() {
2472 var input
= me
.getInputElement();
2474 var focusClass
= me
.type
in { 'checkbox': 1, 'ratio': 1 } && CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 8 ? 'cke_dialog_ui_focused' : '';
2475 input
.on( 'focus', function() {
2476 dialog
._
.tabBarMode
= false;
2477 dialog
._
.hasFocus
= true;
2479 focusClass
&& this.addClass( focusClass
);
2483 input
.on( 'blur', function() {
2485 focusClass
&& this.removeClass( focusClass
);
2490 // Completes this object with everything we have in the
2492 CKEDITOR
.tools
.extend( this, elementDefinition
);
2494 // Register the object as a tab focus if it can be included.
2495 if ( this.keyboardFocusable
) {
2496 this.tabIndex
= elementDefinition
.tabIndex
|| 0;
2498 this.focusIndex
= dialog
._
.focusList
.push( this ) - 1;
2499 this.on( 'focus', function() {
2500 dialog
._
.currentFocusIndex
= me
.focusIndex
;
2506 * Horizontal layout box for dialog UI elements, auto-expends to available width of container.
2508 * @class CKEDITOR.ui.dialog.hbox
2509 * @extends CKEDITOR.ui.dialog.uiElement
2510 * @constructor Creates a hbox class instance.
2511 * @param {CKEDITOR.dialog} dialog Parent dialog object.
2512 * @param {Array} childObjList
2513 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container.
2514 * @param {Array} childHtmlList
2515 * Array of HTML code that correspond to the HTML output of all the
2516 * objects in childObjList.
2517 * @param {Array} htmlList
2518 * Array of HTML code that this element will output to.
2519 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
2520 * The element definition. Accepted fields:
2522 * * `widths` (Optional) The widths of child cells.
2523 * * `height` (Optional) The height of the layout.
2524 * * `padding` (Optional) The padding width inside child cells.
2525 * * `align` (Optional) The alignment of the whole layout.
2527 hbox: function( dialog
, childObjList
, childHtmlList
, htmlList
, elementDefinition
) {
2528 if ( arguments
.length
< 4 )
2531 this._
|| ( this._
= {} );
2533 var children
= this._
.children
= childObjList
,
2534 widths
= elementDefinition
&& elementDefinition
.widths
|| null,
2535 height
= elementDefinition
&& elementDefinition
.height
|| null,
2539 var innerHTML = function() {
2540 var html
= [ '<tbody><tr class="cke_dialog_ui_hbox">' ];
2541 for ( i
= 0; i
< childHtmlList
.length
; i
++ ) {
2542 var className
= 'cke_dialog_ui_hbox_child',
2545 className
= 'cke_dialog_ui_hbox_first';
2547 if ( i
== childHtmlList
.length
- 1 ) {
2548 className
= 'cke_dialog_ui_hbox_last';
2551 html
.push( '<td class="', className
, '" role="presentation" ' );
2553 if ( widths
[ i
] ) {
2554 styles
.push( 'width:' + cssLength( widths
[i
] ) );
2557 styles
.push( 'width:' + Math
.floor( 100 / childHtmlList
.length
) + '%' );
2560 styles
.push( 'height:' + cssLength( height
) );
2562 if ( elementDefinition
&& elementDefinition
.padding
!== undefined ) {
2563 styles
.push( 'padding:' + cssLength( elementDefinition
.padding
) );
2565 // In IE Quirks alignment has to be done on table cells. (http://dev.ckeditor.com/ticket/7324)
2566 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
&& children
[ i
].align
) {
2567 styles
.push( 'text-align:' + children
[ i
].align
);
2569 if ( styles
.length
> 0 ) {
2570 html
.push( 'style="' + styles
.join( '; ' ) + '" ' );
2572 html
.push( '>', childHtmlList
[ i
], '</td>' );
2574 html
.push( '</tr></tbody>' );
2575 return html
.join( '' );
2578 var attribs
= { role: 'presentation' };
2579 elementDefinition
&& elementDefinition
.align
&& ( attribs
.align
= elementDefinition
.align
);
2581 CKEDITOR
.ui
.dialog
.uiElement
.call( this, dialog
, elementDefinition
|| { type: 'hbox' }, htmlList
, 'table', styles
, attribs
, innerHTML
);
2585 * Vertical layout box for dialog UI elements.
2587 * @class CKEDITOR.ui.dialog.vbox
2588 * @extends CKEDITOR.ui.dialog.hbox
2589 * @constructor Creates a vbox class instance.
2590 * @param {CKEDITOR.dialog} dialog Parent dialog object.
2591 * @param {Array} childObjList
2592 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container.
2593 * @param {Array} childHtmlList
2594 * Array of HTML code that correspond to the HTML output of all the
2595 * objects in childObjList.
2596 * @param {Array} htmlList Array of HTML code that this element will output to.
2597 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
2598 * The element definition. Accepted fields:
2600 * * `width` (Optional) The width of the layout.
2601 * * `heights` (Optional) The heights of individual cells.
2602 * * `align` (Optional) The alignment of the layout.
2603 * * `padding` (Optional) The padding width inside child cells.
2604 * * `expand` (Optional) Whether the layout should expand
2605 * vertically to fill its container.
2607 vbox: function( dialog
, childObjList
, childHtmlList
, htmlList
, elementDefinition
) {
2608 if ( arguments
.length
< 3 )
2611 this._
|| ( this._
= {} );
2613 var children
= this._
.children
= childObjList
,
2614 width
= elementDefinition
&& elementDefinition
.width
|| null,
2615 heights
= elementDefinition
&& elementDefinition
.heights
|| null;
2617 var innerHTML = function() {
2618 var html
= [ '<table role="presentation" cellspacing="0" border="0" ' ];
2619 html
.push( 'style="' );
2620 if ( elementDefinition
&& elementDefinition
.expand
)
2621 html
.push( 'height:100%;' );
2622 html
.push( 'width:' + cssLength( width
|| '100%' ), ';' );
2624 // (http://dev.ckeditor.com/ticket/10123) Temp fix for dialog broken layout in latest webkit.
2625 if ( CKEDITOR
.env
.webkit
)
2626 html
.push( 'float:none;' );
2629 html
.push( 'align="', CKEDITOR
.tools
.htmlEncode(
2630 ( elementDefinition
&& elementDefinition
.align
) || ( dialog
.getParentEditor().lang
.dir
== 'ltr' ? 'left' : 'right' ) ), '" ' );
2632 html
.push( '><tbody>' );
2633 for ( var i
= 0; i
< childHtmlList
.length
; i
++ ) {
2635 html
.push( '<tr><td role="presentation" ' );
2637 styles
.push( 'width:' + cssLength( width
|| '100%' ) );
2639 styles
.push( 'height:' + cssLength( heights
[ i
] ) );
2640 else if ( elementDefinition
&& elementDefinition
.expand
)
2641 styles
.push( 'height:' + Math
.floor( 100 / childHtmlList
.length
) + '%' );
2642 if ( elementDefinition
&& elementDefinition
.padding
!== undefined )
2643 styles
.push( 'padding:' + cssLength( elementDefinition
.padding
) );
2644 // In IE Quirks alignment has to be done on table cells. (http://dev.ckeditor.com/ticket/7324)
2645 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
&& children
[ i
].align
)
2646 styles
.push( 'text-align:' + children
[ i
].align
);
2647 if ( styles
.length
> 0 )
2648 html
.push( 'style="', styles
.join( '; ' ), '" ' );
2649 html
.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList
[ i
], '</td></tr>' );
2651 html
.push( '</tbody></table>' );
2652 return html
.join( '' );
2654 CKEDITOR
.ui
.dialog
.uiElement
.call( this, dialog
, elementDefinition
|| { type: 'vbox' }, htmlList
, 'div', null, { role: 'presentation' }, innerHTML
);
2659 /** @class CKEDITOR.ui.dialog.uiElement */
2660 CKEDITOR
.ui
.dialog
.uiElement
.prototype = {
2662 * Gets the root DOM element of this dialog UI object.
2664 * uiElement.getElement().hide();
2666 * @returns {CKEDITOR.dom.element} Root DOM element of UI object.
2668 getElement: function() {
2669 return CKEDITOR
.document
.getById( this.domId
);
2673 * Gets the DOM element that the user inputs values.
2675 * This function is used by {@link #setValue}, {@link #getValue} and {@link #focus}. It should
2676 * be overrided in child classes where the input element isn't the root
2679 * var rawValue = textInput.getInputElement().$.value;
2681 * @returns {CKEDITOR.dom.element} The element where the user input values.
2683 getInputElement: function() {
2684 return this.getElement();
2688 * Gets the parent dialog object containing this UI element.
2690 * var dialog = uiElement.getDialog();
2692 * @returns {CKEDITOR.dialog} Parent dialog object.
2694 getDialog: function() {
2695 return this._
.dialog
;
2699 * Sets the value of this dialog UI object.
2701 * uiElement.setValue( 'Dingo' );
2704 * @param {Object} value The new value.
2705 * @param {Boolean} noChangeEvent Internal commit, to supress `change` event on this element.
2707 setValue: function( value
, noChangeEvent
) {
2708 this.getInputElement().setValue( value
);
2709 !noChangeEvent
&& this.fire( 'change', { value: value
} );
2714 * Gets the current value of this dialog UI object.
2716 * var myValue = uiElement.getValue();
2718 * @returns {Object} The current value.
2720 getValue: function() {
2721 return this.getInputElement().getValue();
2725 * Tells whether the UI object's value has changed.
2727 * if ( uiElement.isChanged() )
2728 * confirm( 'Value changed! Continue?' );
2730 * @returns {Boolean} `true` if changed, `false` if not changed.
2732 isChanged: function() {
2733 // Override in input classes.
2738 * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods.
2740 * focus : function() {
2741 * this.selectParentTab();
2742 * // do something else.
2747 selectParentTab: function() {
2748 var element
= this.getInputElement(),
2751 while ( ( cursor
= cursor
.getParent() ) && cursor
.$.className
.search( 'cke_dialog_page_contents' ) == -1 ) {
2755 // Some widgets don't have parent tabs (e.g. OK and Cancel buttons).
2759 tabId
= cursor
.getAttribute( 'name' );
2760 // Avoid duplicate select.
2761 if ( this._
.dialog
._
.currentTabId
!= tabId
)
2762 this._
.dialog
.selectPage( tabId
);
2767 * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page.
2769 * uiElement.focus();
2774 this.selectParentTab().getInputElement().focus();
2779 * Registers the `on*` event handlers defined in the element definition.
2781 * The default behavior of this function is:
2783 * 1. If the on* event is defined in the class's eventProcesors list,
2784 * then the registration is delegated to the corresponding function
2785 * in the eventProcessors list.
2786 * 2. If the on* event is not defined in the eventProcessors list, then
2787 * register the event handler under the corresponding DOM event of
2788 * the UI element's input DOM element (as defined by the return value
2789 * of {@link #getInputElement}).
2791 * This function is only called at UI element instantiation, but can
2792 * be overridded in child classes if they require more flexibility.
2795 * @param {CKEDITOR.dialog.definition.uiElement} definition The UI element
2798 registerEvents: function( definition
) {
2799 var regex
= /^on([A-Z]\w+)/,
2802 var registerDomEvent = function( uiElement
, dialog
, eventName
, func
) {
2803 dialog
.on( 'load', function() {
2804 uiElement
.getInputElement().on( eventName
, func
, uiElement
);
2808 for ( var i
in definition
) {
2809 if ( !( match
= i
.match( regex
) ) )
2811 if ( this.eventProcessors
[ i
] )
2812 this.eventProcessors
[ i
].call( this, this._
.dialog
, definition
[ i
] );
2814 registerDomEvent( this, this._
.dialog
, match
[ 1 ].toLowerCase(), definition
[ i
] );
2821 * The event processor list used by
2822 * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element
2823 * instantiation. The default list defines three `on*` events:
2825 * 1. `onLoad` - Called when the element's parent dialog opens for the
2827 * 2. `onShow` - Called whenever the element's parent dialog opens.
2828 * 3. `onHide` - Called whenever the element's parent dialog closes.
2830 * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick
2831 * // handlers in the UI element's definitions.
2832 * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {},
2833 * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
2834 * { onClick : function( dialog, func ) { this.on( 'click', func ); } },
2838 * @property {Object}
2841 onLoad: function( dialog
, func
) {
2842 dialog
.on( 'load', func
, this );
2845 onShow: function( dialog
, func
) {
2846 dialog
.on( 'show', func
, this );
2849 onHide: function( dialog
, func
) {
2850 dialog
.on( 'hide', func
, this );
2855 * The default handler for a UI element's access key down event, which
2856 * tries to put focus to the UI element.
2858 * Can be overridded in child classes for more sophisticaed behavior.
2860 * @param {CKEDITOR.dialog} dialog The parent dialog object.
2861 * @param {String} key The key combination pressed. Since access keys
2862 * are defined to always include the `CTRL` key, its value should always
2863 * include a `'CTRL+'` prefix.
2865 accessKeyDown: function() {
2870 * The default handler for a UI element's access key up event, which
2873 * Can be overridded in child classes for more sophisticated behavior.
2875 * @param {CKEDITOR.dialog} dialog The parent dialog object.
2876 * @param {String} key The key combination pressed. Since access keys
2877 * are defined to always include the `CTRL` key, its value should always
2878 * include a `'CTRL+'` prefix.
2880 accessKeyUp: function() {},
2883 * Disables a UI element.
2885 disable: function() {
2886 var element
= this.getElement(),
2887 input
= this.getInputElement();
2888 input
.setAttribute( 'disabled', 'true' );
2889 element
.addClass( 'cke_disabled' );
2893 * Enables a UI element.
2895 enable: function() {
2896 var element
= this.getElement(),
2897 input
= this.getInputElement();
2898 input
.removeAttribute( 'disabled' );
2899 element
.removeClass( 'cke_disabled' );
2903 * Determines whether an UI element is enabled or not.
2905 * @returns {Boolean} Whether the UI element is enabled.
2907 isEnabled: function() {
2908 return !this.getElement().hasClass( 'cke_disabled' );
2912 * Determines whether an UI element is visible or not.
2914 * @returns {Boolean} Whether the UI element is visible.
2916 isVisible: function() {
2917 return this.getInputElement().isVisible();
2921 * Determines whether an UI element is focus-able or not.
2922 * Focus-able is defined as being both visible and enabled.
2924 * @returns {Boolean} Whether the UI element can be focused.
2926 isFocusable: function() {
2927 if ( !this.isEnabled() || !this.isVisible() )
2933 /** @class CKEDITOR.ui.dialog.hbox */
2934 CKEDITOR
.ui
.dialog
.hbox
.prototype = CKEDITOR
.tools
.extend( new CKEDITOR
.ui
.dialog
.uiElement(), {
2936 * Gets a child UI element inside this container.
2938 * var checkbox = hbox.getChild( [0,1] );
2939 * checkbox.setValue( true );
2941 * @param {Array/Number} indices An array or a single number to indicate the child's
2942 * position in the container's descendant tree. Omit to get all the children in an array.
2943 * @returns {Array/CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container
2944 * if no argument given, or the specified UI element if indices is given.
2946 getChild: function( indices
) {
2947 // If no arguments, return a clone of the children array.
2948 if ( arguments
.length
< 1 )
2949 return this._
.children
.concat();
2951 // If indices isn't array, make it one.
2952 if ( !indices
.splice
)
2953 indices
= [ indices
];
2955 // Retrieve the child element according to tree position.
2956 if ( indices
.length
< 2 )
2957 return this._
.children
[ indices
[ 0 ] ];
2959 return ( this._
.children
[ indices
[ 0 ] ] && this._
.children
[ indices
[ 0 ] ].getChild
) ? this._
.children
[ indices
[ 0 ] ].getChild( indices
.slice( 1, indices
.length
) ) : null;
2963 CKEDITOR
.ui
.dialog
.vbox
.prototype = new CKEDITOR
.ui
.dialog
.hbox();
2966 var commonBuilder
= {
2967 build: function( dialog
, elementDefinition
, output
) {
2968 var children
= elementDefinition
.children
,
2973 ( i
< children
.length
&& ( child
= children
[ i
] ) ); i
++ ) {
2975 childHtmlList
.push( childHtml
);
2976 childObjList
.push( CKEDITOR
.dialog
._
.uiElementBuilders
[ child
.type
].build( dialog
, child
, childHtml
) );
2978 return new CKEDITOR
.ui
.dialog
[ elementDefinition
.type
]( dialog
, childObjList
, childHtmlList
, output
, elementDefinition
);
2982 CKEDITOR
.dialog
.addUIElement( 'hbox', commonBuilder
);
2983 CKEDITOR
.dialog
.addUIElement( 'vbox', commonBuilder
);
2987 * Generic dialog command. It opens a specific dialog when executed.
2989 * // Register the "link" command which opens the "link" dialog.
2990 * editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) );
2993 * @constructor Creates a dialogCommand class instance.
2994 * @extends CKEDITOR.commandDefinition
2995 * @param {String} dialogName The name of the dialog to open when executing
2997 * @param {Object} [ext] Additional command definition's properties.
2998 * @param {String} [ext.tabId] You can provide additional property (`tabId`) if you wish to open the dialog on a specific tabId.
3000 * // Open the dialog on the 'keystroke' tabId.
3001 * editor.addCommand( 'keystroke', new CKEDITOR.dialogCommand( 'a11yHelp', { tabId: 'keystroke' } ) );
3003 CKEDITOR
.dialogCommand = function( dialogName
, ext
) {
3004 this.dialogName
= dialogName
;
3005 CKEDITOR
.tools
.extend( this, ext
, true );
3008 CKEDITOR
.dialogCommand
.prototype = {
3009 exec: function( editor
) {
3010 var tabId
= this.tabId
;
3011 editor
.openDialog( this.dialogName
, function( dialog
) {
3012 // Select different tab if it's provided (#830).
3014 dialog
.selectPage( tabId
);
3019 // Dialog commands just open a dialog ui, thus require no undo logic,
3020 // undo support should dedicate to specific dialog implementation.
3027 var notEmptyRegex
= /^([a]|[^a])+$/,
3028 integerRegex
= /^\d*$/,
3029 numberRegex
= /^\d*(?:\.\d+)?$/,
3030 htmlLengthRegex
= /^(((\d*(\.\d+))|(\d*))(px|\%)?)?$/,
3031 cssLengthRegex
= /^(((\d*(\.\d+))|(\d*))(px|em|ex|in|cm|mm|pt|pc|\%)?)?$/i,
3032 inlineStyleRegex
= /^(\s*[\w-]+\s*:\s*[^:;]+(?:;|$))*$/;
3034 CKEDITOR
.VALIDATE_OR
= 1;
3035 CKEDITOR
.VALIDATE_AND
= 2;
3037 CKEDITOR
.dialog
.validate
= {
3038 functions: function() {
3039 var args
= arguments
;
3042 * It's important for validate functions to be able to accept the value
3043 * as argument in addition to this.getValue(), so that it is possible to
3044 * combine validate functions together to make more sophisticated
3047 var value
= this && this.getValue
? this.getValue() : args
[ 0 ];
3050 relation
= CKEDITOR
.VALIDATE_AND
,
3054 for ( i
= 0; i
< args
.length
; i
++ ) {
3055 if ( typeof args
[ i
] == 'function' )
3056 functions
.push( args
[ i
] );
3061 if ( i
< args
.length
&& typeof args
[ i
] == 'string' ) {
3066 if ( i
< args
.length
&& typeof args
[ i
] == 'number' )
3067 relation
= args
[ i
];
3069 var passed
= ( relation
== CKEDITOR
.VALIDATE_AND
? true : false );
3070 for ( i
= 0; i
< functions
.length
; i
++ ) {
3071 if ( relation
== CKEDITOR
.VALIDATE_AND
)
3072 passed
= passed
&& functions
[ i
]( value
);
3074 passed
= passed
|| functions
[ i
]( value
);
3077 return !passed
? msg : true;
3081 regex: function( regex
, msg
) {
3083 * Can be greatly shortened by deriving from functions validator if code size
3084 * turns out to be more important than performance.
3087 var value
= this && this.getValue
? this.getValue() : arguments
[ 0 ];
3088 return !regex
.test( value
) ? msg : true;
3092 notEmpty: function( msg
) {
3093 return this.regex( notEmptyRegex
, msg
);
3096 integer: function( msg
) {
3097 return this.regex( integerRegex
, msg
);
3100 'number': function( msg
) {
3101 return this.regex( numberRegex
, msg
);
3104 'cssLength': function( msg
) {
3105 return this.functions( function( val
) {
3106 return cssLengthRegex
.test( CKEDITOR
.tools
.trim( val
) );
3110 'htmlLength': function( msg
) {
3111 return this.functions( function( val
) {
3112 return htmlLengthRegex
.test( CKEDITOR
.tools
.trim( val
) );
3116 'inlineStyle': function( msg
) {
3117 return this.functions( function( val
) {
3118 return inlineStyleRegex
.test( CKEDITOR
.tools
.trim( val
) );
3122 equals: function( value
, msg
) {
3123 return this.functions( function( val
) {
3124 return val
== value
;
3128 notEqual: function( value
, msg
) {
3129 return this.functions( function( val
) {
3130 return val
!= value
;
3135 CKEDITOR
.on( 'instanceDestroyed', function( evt
) {
3136 // Remove dialog cover on last instance destroy.
3137 if ( CKEDITOR
.tools
.isEmpty( CKEDITOR
.instances
) ) {
3138 var currentTopDialog
;
3139 while ( ( currentTopDialog
= CKEDITOR
.dialog
._
.currentTop
) )
3140 currentTopDialog
.hide();
3144 var dialogs
= evt
.editor
._
.storedDialogs
;
3145 for ( var name
in dialogs
)
3146 dialogs
[ name
].destroy();
3152 // Extend the CKEDITOR.editor class with dialog specific functions.
3153 CKEDITOR
.tools
.extend( CKEDITOR
.editor
.prototype, {
3155 * Loads and opens a registered dialog.
3157 * CKEDITOR.instances.editor1.openDialog( 'smiley' );
3159 * @member CKEDITOR.editor
3160 * @param {String} dialogName The registered name of the dialog.
3161 * @param {Function} callback The function to be invoked after dialog instance created.
3162 * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed.
3163 * `null` if the dialog name is not registered.
3164 * @see CKEDITOR.dialog#add
3166 openDialog: function( dialogName
, callback
) {
3167 var dialog
= null, dialogDefinitions
= CKEDITOR
.dialog
._
.dialogDefinitions
[ dialogName
];
3169 if ( CKEDITOR
.dialog
._
.currentTop
=== null )
3172 // If the dialogDefinition is already loaded, open it immediately.
3173 if ( typeof dialogDefinitions
== 'function' ) {
3174 var storedDialogs
= this._
.storedDialogs
|| ( this._
.storedDialogs
= {} );
3176 dialog
= storedDialogs
[ dialogName
] || ( storedDialogs
[ dialogName
] = new CKEDITOR
.dialog( this, dialogName
) );
3178 callback
&& callback
.call( dialog
, dialog
);
3181 } else if ( dialogDefinitions
== 'failed' ) {
3183 throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName
+ '" failed when loading definition.' );
3184 } else if ( typeof dialogDefinitions
== 'string' ) {
3186 CKEDITOR
.scriptLoader
.load( CKEDITOR
.getUrl( dialogDefinitions
),
3188 var dialogDefinition
= CKEDITOR
.dialog
._
.dialogDefinitions
[ dialogName
];
3189 // In case of plugin error, mark it as loading failed.
3190 if ( typeof dialogDefinition
!= 'function' )
3191 CKEDITOR
.dialog
._
.dialogDefinitions
[ dialogName
] = 'failed';
3193 this.openDialog( dialogName
, callback
);
3197 CKEDITOR
.skin
.loadPart( 'dialog' );
3204 CKEDITOR
.plugins
.add( 'dialog', {
3205 requires: 'dialogui',
3206 init: function( editor
) {
3207 editor
.on( 'doubleclick', function( evt
) {
3208 if ( evt
.data
.dialog
)
3209 editor
.openDialog( evt
.data
.dialog
);
3210 }, null, null, 999 );
3214 // Dialog related configurations.
3217 * The color of the dialog background cover. It should be a valid CSS color string.
3219 * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)';
3221 * @cfg {String} [dialog_backgroundCoverColor='white']
3222 * @member CKEDITOR.config
3226 * The opacity of the dialog background cover. It should be a number within the
3227 * range `[0.0, 1.0]`.
3229 * config.dialog_backgroundCoverOpacity = 0.7;
3231 * @cfg {Number} [dialog_backgroundCoverOpacity=0.5]
3232 * @member CKEDITOR.config
3236 * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened.
3238 * config.dialog_startupFocusTab = true;
3240 * @cfg {Boolean} [dialog_startupFocusTab=false]
3241 * @member CKEDITOR.config
3245 * The distance of magnetic borders used in moving and resizing dialogs,
3246 * measured in pixels.
3248 * config.dialog_magnetDistance = 30;
3250 * @cfg {Number} [dialog_magnetDistance=20]
3251 * @member CKEDITOR.config
3255 * The guideline to follow when generating the dialog buttons. There are 3 possible options:
3257 * * `'OS'` - the buttons will be displayed in the default order of the user's OS;
3258 * * `'ltr'` - for Left-To-Right order;
3259 * * `'rtl'` - for Right-To-Left order.
3263 * config.dialog_buttonsOrder = 'rtl';
3266 * @cfg {String} [dialog_buttonsOrder='OS']
3267 * @member CKEDITOR.config
3271 * The dialog contents to removed. It's a string composed by dialog name and tab name with a colon between them.
3273 * Separate each pair with semicolon (see example).
3275 * **Note:** All names are case-sensitive.
3277 * **Note:** Be cautious when specifying dialog tabs that are mandatory,
3278 * like `'info'`, dialog functionality might be broken because of this!
3280 * config.removeDialogTabs = 'flash:advanced;image:Link';
3283 * @cfg {String} [removeDialogTabs='']
3284 * @member CKEDITOR.config
3288 * Tells if user should not be asked to confirm close, if any dialog field was modified.
3289 * By default it is set to `false` meaning that the confirmation dialog will be shown.
3291 * config.dialog_noConfirmCancel = true;
3294 * @cfg {Boolean} [dialog_noConfirmCancel=false]
3295 * @member CKEDITOR.config
3299 * Event fired when a dialog definition is about to be used to create a dialog into
3300 * an editor instance. This event makes it possible to customize the definition
3301 * before creating it.
3303 * Note that this event is called only the first time a specific dialog is
3304 * opened. Successive openings will use the cached dialog, and this event will
3307 * @event dialogDefinition
3309 * @param {CKEDITOR.dialog.definition} data The dialog defination that
3311 * @param {CKEDITOR.editor} editor The editor instance that will use the dialog.
3315 * Event fired when a tab is going to be selected in a dialog.
3318 * @member CKEDITOR.dialog
3320 * @param {String} data.page The id of the page that it's gonna be selected.
3321 * @param {String} data.currentPage The id of the current page.
3325 * Event fired when the user tries to dismiss a dialog.
3328 * @member CKEDITOR.dialog
3330 * @param {Boolean} data.hide Whether the event should proceed or not.
3334 * Event fired when the user tries to confirm a dialog.
3337 * @member CKEDITOR.dialog
3339 * @param {Boolean} data.hide Whether the event should proceed or not.
3343 * Event fired when a dialog is shown.
3346 * @member CKEDITOR.dialog
3350 * Event fired when a dialog is shown.
3353 * @member CKEDITOR.editor
3354 * @param {CKEDITOR.editor} editor This editor instance.
3355 * @param {CKEDITOR.dialog} data The opened dialog instance.
3359 * Event fired when a dialog is hidden.
3362 * @member CKEDITOR.dialog
3366 * Event fired when a dialog is hidden.
3369 * @member CKEDITOR.editor
3370 * @param {CKEDITOR.editor} editor This editor instance.
3371 * @param {CKEDITOR.dialog} data The hidden dialog instance.
3375 * Event fired when a dialog is being resized. The event is fired on
3376 * both the {@link CKEDITOR.dialog} object and the dialog instance
3377 * since 3.5.3, previously it was only available in the global object.
3381 * @member CKEDITOR.dialog
3383 * @param {CKEDITOR.dialog} data.dialog The dialog being resized (if
3384 * it is fired on the dialog itself, this parameter is not sent).
3385 * @param {String} data.skin The skin name.
3386 * @param {Number} data.width The new width.
3387 * @param {Number} data.height The new height.
3391 * Event fired when a dialog is being resized. The event is fired on
3392 * both the {@link CKEDITOR.dialog} object and the dialog instance
3393 * since 3.5.3, previously it was only available in the global object.
3397 * @member CKEDITOR.dialog
3399 * @param {Number} data.width The new width.
3400 * @param {Number} data.height The new height.
3404 * Event fired when the dialog state changes, usually by {@link CKEDITOR.dialog#setState}.
3408 * @member CKEDITOR.dialog
3410 * @param {Number} data The new state. Either {@link CKEDITOR#DIALOG_STATE_IDLE} or {@link CKEDITOR#DIALOG_STATE_BUSY}.