/*\r
-Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
For licensing, see LICENSE.html or http://ckeditor.com/license\r
*/\r
\r
* @fileOverview The floating dialog plugin.\r
*/\r
\r
-CKEDITOR.plugins.add( 'dialog',\r
- {\r
- requires : [ 'dialogui' ]\r
- });\r
-\r
/**\r
* No resize for this dialog.\r
* @constant\r
return null;\r
}\r
\r
- // Stores dialog related data from skin definitions. e.g. margin sizes.\r
- var skinData = {};\r
-\r
/**\r
* This is the base class for runtime dialog objects. An instance of this\r
* class represents a single named dialog for a single editor instance.\r
{\r
// Load the dialog definition.\r
var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ];\r
- if ( !definition )\r
- {\r
- console.log( 'Error: The dialog "' + dialogName + '" is not defined.' );\r
- return;\r
- }\r
\r
// Completes the definition with the default values.\r
definition = CKEDITOR.tools.extend( definition( editor ), defaultDialogDefinition );\r
// functions.\r
definition = new definitionObject( this, definition );\r
\r
- // Fire the "dialogDefinition" event, making it possible to customize\r
- // the dialog definition.\r
- this.definition = definition = CKEDITOR.fire( 'dialogDefinition',\r
- {\r
- name : dialogName,\r
- definition : definition\r
- }\r
- , editor ).definition;\r
\r
var doc = CKEDITOR.document;\r
\r
\r
this.parts = themeBuilt.parts;\r
\r
+ CKEDITOR.tools.setTimeout( function()\r
+ {\r
+ editor.fire( 'ariaWidget', this.parts.contents );\r
+ },\r
+ 0, this );\r
+\r
// Set the startup styles for the dialog, avoiding it enlarging the\r
// page size on the dialog creation.\r
this.parts.dialog.setStyles(\r
// Call the CKEDITOR.event constructor to initialize this instance.\r
CKEDITOR.event.call( this );\r
\r
+ // Fire the "dialogDefinition" event, making it possible to customize\r
+ // the dialog definition.\r
+ this.definition = definition = CKEDITOR.fire( 'dialogDefinition',\r
+ {\r
+ name : dialogName,\r
+ definition : definition\r
+ }\r
+ , editor ).definition;\r
// Initialize load, show, hide, ok and cancel events.\r
if ( definition.onLoad )\r
this.on( 'load', definition.onLoad );\r
{\r
this.on( 'ok', function( evt )\r
{\r
+ // Dialog confirm might probably introduce content changes (#5415).\r
+ editor.fire( 'saveSnapshot' );\r
+ setTimeout( function () { editor.fire( 'saveSnapshot' ); }, 0 );\r
if ( definition.onOk.call( this, evt ) === false )\r
evt.data.hide = false;\r
});\r
{\r
if ( this.fire( 'cancel', { hide : true } ).hide !== false )\r
this.hide();\r
+ evt.data.preventDefault();\r
}, this );\r
\r
+ // Sort focus list according to tab order definitions.\r
+ function setupFocus()\r
+ {\r
+ var focusList = me._.focusList;\r
+ focusList.sort( function( a, b )\r
+ {\r
+ // Mimics browser tab order logics;\r
+ if ( a.tabIndex != b.tabIndex )\r
+ return b.tabIndex - a.tabIndex;\r
+ // Sort is not stable in some browsers,\r
+ // fall-back the comparator to 'focusIndex';\r
+ else\r
+ return a.focusIndex - b.focusIndex;\r
+ });\r
+\r
+ var size = focusList.length;\r
+ for ( var i = 0; i < size; i++ )\r
+ focusList[ i ].focusIndex = i;\r
+ }\r
+\r
function changeFocus( forward )\r
{\r
var focusList = me._.focusList,\r
if ( focusList.length < 1 )\r
return;\r
\r
- var currentIndex = ( me._.currentFocusIndex + offset + focusList.length ) % focusList.length;\r
+ var current = me._.currentFocusIndex;\r
+\r
+ // Trigger the 'blur' event of any input element before anything,\r
+ // since certain UI updates may depend on it.\r
+ try\r
+ {\r
+ focusList[ current ].getInputElement().$.blur();\r
+ }\r
+ catch( e ){}\r
+\r
+ var startIndex = ( current + offset + focusList.length ) % focusList.length,\r
+ currentIndex = startIndex;\r
while ( !focusList[ currentIndex ].isFocusable() )\r
{\r
currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length;\r
- if ( currentIndex == me._.currentFocusIndex )\r
+ if ( currentIndex == startIndex )\r
break;\r
}\r
focusList[ currentIndex ].focus();\r
+\r
+ // Select whole field content.\r
+ if ( focusList[ currentIndex ].type == 'text' )\r
+ focusList[ currentIndex ].select();\r
}\r
\r
+ this.changeFocus = changeFocus;\r
+\r
+ var processed;\r
+\r
function focusKeydownHandler( evt )\r
{\r
// If I'm not the top dialog, ignore.\r
return;\r
\r
var keystroke = evt.data.getKeystroke(),\r
- processed = false;\r
+ rtl = editor.lang.dir == 'rtl';\r
+\r
+ processed = 0;\r
if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 )\r
{\r
var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 );\r
changeFocus( !shiftPressed );\r
}\r
\r
- processed = true;\r
+ processed = 1;\r
}\r
- else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode )\r
+ else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 )\r
{\r
// Alt-F10 puts focus into the current tab item in the tab bar.\r
me._.tabBarMode = true;\r
me._.tabs[ me._.currentTabId ][ 0 ].focus();\r
- processed = true;\r
+ processed = 1;\r
}\r
else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode )\r
{\r
// Arrow keys - used for changing tabs.\r
- nextId = ( keystroke == 37 ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) );\r
+ nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) );\r
me.selectPage( nextId );\r
me._.tabs[ nextId ][ 0 ].focus();\r
- processed = true;\r
+ processed = 1;\r
+ }\r
+ else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode )\r
+ {\r
+ this.selectPage( this._.currentTabId );\r
+ this._.tabBarMode = false;\r
+ this._.currentFocusIndex = -1;\r
+ changeFocus( true );\r
+ processed = 1;\r
}\r
\r
if ( processed )\r
}\r
}\r
\r
+ function focusKeyPressHandler( evt )\r
+ {\r
+ processed && evt.data.preventDefault();\r
+ }\r
+\r
+ var dialogElement = this._.element;\r
// Add the dialog keyboard handlers.\r
this.on( 'show', function()\r
{\r
- CKEDITOR.document.on( 'keydown', focusKeydownHandler, this, null, 0 );\r
+ dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 );\r
+ // Some browsers instead, don't cancel key events in the keydown, but in the\r
+ // keypress. So we must do a longer trip in those cases. (#4531)\r
+ if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )\r
+ dialogElement.on( 'keypress', focusKeyPressHandler, this );\r
\r
- if ( CKEDITOR.env.ie6Compat )\r
- {\r
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();\r
- coverDoc.on( 'keydown', focusKeydownHandler, this, null, 0 );\r
- }\r
} );\r
this.on( 'hide', function()\r
{\r
- CKEDITOR.document.removeListener( 'keydown', focusKeydownHandler );\r
+ dialogElement.removeListener( 'keydown', focusKeydownHandler );\r
+ if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )\r
+ dialogElement.removeListener( 'keypress', focusKeyPressHandler );\r
} );\r
this.on( 'iframeAdded', function( evt )\r
{\r
// Auto-focus logic in dialog.\r
this.on( 'show', function()\r
{\r
- if ( !this._.hasFocus )\r
+ // Setup tabIndex on showing the dialog instead of on loading\r
+ // to allow dynamic tab order happen in dialog definition.\r
+ setupFocus();\r
+\r
+ if ( editor.config.dialog_startupFocusTab\r
+ && me._.pageCount > 1 )\r
+ {\r
+ me._.tabBarMode = true;\r
+ me._.tabs[ me._.currentTabId ][ 0 ].focus();\r
+ }\r
+ else if ( !this._.hasFocus )\r
{\r
this._.currentFocusIndex = -1;\r
- changeFocus( true );\r
+\r
+ // Decide where to put the initial focus.\r
+ if ( definition.onFocus )\r
+ {\r
+ var initialFocus = definition.onFocus.call( this );\r
+ // Focus the field that the user specified.\r
+ initialFocus && initialFocus.focus();\r
+ }\r
+ // Focus the first field in layout order.\r
+ else\r
+ changeFocus( true );\r
\r
/*\r
* IE BUG: If the initial focus went into a non-text element (e.g. button),\r
\r
// Insert the tabs and contents.\r
for ( var i = 0 ; i < definition.contents.length ; i++ )\r
- this.addPage( definition.contents[i] );\r
+ {\r
+ var page = definition.contents[i];\r
+ page && this.addPage( page );\r
+ }\r
\r
- var tabRegex = /cke_dialog_tab(\s|$|_)/,\r
- tabOuterRegex = /cke_dialog_tab(\s|$)/;\r
this.parts['tabs'].on( 'click', function( evt )\r
{\r
- var target = evt.data.getTarget(), firstNode = target, id, page;\r
-\r
+ var target = evt.data.getTarget();\r
// If we aren't inside a tab, bail out.\r
- if ( !( tabRegex.test( target.$.className ) || target.getName() == 'a' ) )\r
- return;\r
-\r
- // Find the outer <td> container of the tab.\r
- id = target.$.id.substr( 0, target.$.id.lastIndexOf( '_' ) );\r
- this.selectPage( id );\r
-\r
- if ( this._.tabBarMode )\r
+ if ( target.hasClass( 'cke_dialog_tab' ) )\r
{\r
- this._.tabBarMode = false;\r
- this._.currentFocusIndex = -1;\r
- changeFocus( true );\r
- }\r
+ // Get the ID of the tab, without the 'cke_' prefix and the unique number suffix.\r
+ var id = target.$.id;\r
+ this.selectPage( id.substring( 4, id.lastIndexOf( '_' ) ) );\r
\r
- evt.data.preventDefault();\r
+ if ( this._.tabBarMode )\r
+ {\r
+ this._.tabBarMode = false;\r
+ this._.currentFocusIndex = -1;\r
+ changeFocus( true );\r
+ }\r
+ evt.data.preventDefault();\r
+ }\r
}, this );\r
\r
// Insert buttons.\r
\r
for ( i = 0 ; i < buttons.length ; i++ )\r
this._.buttons[ buttons[i].id ] = buttons[i];\r
-\r
- CKEDITOR.skins.load( editor, 'dialog' );\r
};\r
\r
// Focusable interface. Use it via dialog.addFocusable.\r
- function Focusable( dialog, element, index ) {\r
+ function Focusable( dialog, element, index )\r
+ {\r
this.element = element;\r
this.focusIndex = index;\r
+ // TODO: support tabIndex for focusables.\r
+ this.tabIndex = 0;\r
this.isFocusable = function()\r
{\r
- return true;\r
+ return !element.getAttribute( 'disabled' ) && element.isVisible();\r
};\r
this.focus = function()\r
{\r
\r
CKEDITOR.dialog.prototype =\r
{\r
+ destroy : function()\r
+ {\r
+ this.hide();\r
+ this._.element.remove();\r
+ },\r
+\r
/**\r
* Resizes the dialog.\r
* @param {Number} width The width of the dialog in pixels.\r
*/\r
show : function()\r
{\r
- if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie )\r
- this._.editor.getSelection().lock();\r
+ var editor = this._.editor;\r
+ if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie )\r
+ {\r
+ var selection = editor.getSelection();\r
+ selection && selection.lock();\r
+ }\r
\r
// Insert the dialog's element to the root document.\r
var element = this._.element;\r
if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) )\r
element.appendTo( CKEDITOR.document.getBody() );\r
else\r
- return;\r
+ element.setStyle( 'display', 'block' );\r
\r
// FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8.\r
if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )\r
{\r
CKEDITOR.dialog._.currentTop = this;\r
this._.parentDialog = null;\r
- addCover( this._.editor );\r
+ showCover( this._.editor );\r
+\r
+ element.on( 'keydown', accessKeyDownHandler );\r
+ element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler );\r
\r
- CKEDITOR.document.on( 'keydown', accessKeyDownHandler );\r
- CKEDITOR.document.on( 'keyup', accessKeyUpHandler );\r
+ // Prevent some keys from bubbling up. (#4269)\r
+ for ( var event in { keyup :1, keydown :1, keypress :1 } )\r
+ element.on( event, preventKeyBubbling );\r
}\r
else\r
{\r
// Execute onLoad for the first show.\r
this.fireOnce( 'load', {} );\r
this.fire( 'show', {} );\r
+ this._.editor.fire( 'dialogShow', this );\r
\r
// Save the initial values of the dialog.\r
this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } );\r
*/\r
hide : function()\r
{\r
- this.fire( 'hide', {} );\r
-\r
- // Remove the dialog's element from the root document.\r
- var element = this._.element;\r
- if ( !element.getParent() )\r
+ if ( !this.parts.dialog.isVisible() )\r
return;\r
\r
- element.remove();\r
+ this.fire( 'hide', {} );\r
+ this._.editor.fire( 'dialogHide', this );\r
+ var element = this._.element;\r
+ element.setStyle( 'display', 'none' );\r
this.parts.dialog.setStyle( 'visibility', 'hidden' );\r
-\r
// Unregister all access keys associated with this dialog.\r
unregisterAccessKey( this );\r
\r
+ // Close any child(top) dialogs first.\r
+ while( CKEDITOR.dialog._.currentTop != this )\r
+ CKEDITOR.dialog._.currentTop.hide();\r
+\r
// Maintain dialog ordering and remove cover if needed.\r
if ( !this._.parentDialog )\r
- removeCover();\r
+ hideCover();\r
else\r
{\r
var parentElement = this._.parentDialog.getElement().getFirst();\r
CKEDITOR.dialog._.currentZIndex = null;\r
\r
// Remove access key handlers.\r
- CKEDITOR.document.removeListener( 'keydown', accessKeyDownHandler );\r
- CKEDITOR.document.removeListener( 'keyup', accessKeyUpHandler );\r
+ element.removeListener( 'keydown', accessKeyDownHandler );\r
+ element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler );\r
+\r
+ // Remove bubbling-prevention handler. (#4269)\r
+ for ( var event in { keyup :1, keydown :1, keypress :1 } )\r
+ element.removeListener( event, preventKeyBubbling );\r
\r
var editor = this._.editor;\r
editor.focus();\r
\r
if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie )\r
- editor.getSelection().unlock( true );\r
+ {\r
+ var selection = editor.getSelection();\r
+ selection && selection.unlock( true );\r
+ }\r
}\r
else\r
CKEDITOR.dialog._.currentZIndex -= 10;\r
\r
-\r
+ delete this._.parentDialog;\r
// Reset the initial values of the dialog.\r
this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } );\r
},\r
\r
// Create the HTML for the tab and the content block.\r
var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) );\r
- var tab = CKEDITOR.dom.element.createFromHtml( [\r
+ page.setAttribute( 'role', 'tabpanel' );\r
+\r
+ var env = CKEDITOR.env;\r
+ var tabId = 'cke_' + contents.id + '_' + CKEDITOR.tools.getNextNumber(),\r
+ tab = CKEDITOR.dom.element.createFromHtml( [\r
'<a class="cke_dialog_tab"',\r
( this._.pageCount > 0 ? ' cke_last' : 'cke_first' ),\r
titleHtml,\r
( !!contents.hidden ? ' style="display:none"' : '' ),\r
- ' id="', contents.id + '_', CKEDITOR.tools.getNextNumber(), '"' +\r
- ' href="javascript:void(0)"',\r
- ' hidefocus="true">',\r
+ ' id="', tabId, '"',\r
+ env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"',\r
+ ' tabIndex="-1"',\r
+ ' hidefocus="true"',\r
+ ' role="tab">',\r
contents.label,\r
'</a>'\r
].join( '' ) );\r
\r
- // If only a single page exist, a different style is used in the central pane.\r
- if ( this._.pageCount === 0 )\r
- this.parts.dialog.addClass( 'cke_single_page' );\r
- else\r
- this.parts.dialog.removeClass( 'cke_single_page' );\r
+ page.setAttribute( 'aria-labelledby', tabId );\r
\r
// Take records for the tabs and elements created.\r
this._.tabs[ contents.id ] = [ tab, page ];\r
this._.tabIdList.push( contents.id );\r
- this._.pageCount++;\r
+ !contents.hidden && this._.pageCount++;\r
this._.lastTab = tab;\r
+ this.updateStyle();\r
\r
var contentMap = this._.contents[ contents.id ] = {},\r
cursor,\r
*/\r
selectPage : function( id )\r
{\r
+ if ( this._.currentTabId == id )\r
+ return;\r
+\r
+ // Returning true means that the event has been canceled\r
+ if ( this.fire( 'selectPage', { page : id, currentPage : this._.currentTabId } ) === true )\r
+ return;\r
+\r
// Hide the non-selected tabs and pages.\r
for ( var i in this._.tabs )\r
{\r
tab.removeClass( 'cke_dialog_tab_selected' );\r
page.hide();\r
}\r
+ page.setAttribute( 'aria-hidden', i != id );\r
}\r
\r
var selected = this._.tabs[id];\r
this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id );\r
},\r
\r
+ // Dialog state-specific style updates.\r
+ updateStyle : function()\r
+ {\r
+ // If only a single page shown, a different style is used in the central pane.\r
+ this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' );\r
+ },\r
+\r
/**\r
* Hides a page's tab away from the dialog.\r
* @param {String} id The page's Id.\r
hidePage : function( id )\r
{\r
var tab = this._.tabs[id] && this._.tabs[id][0];\r
- if ( !tab )\r
+ if ( !tab || this._.pageCount == 1 )\r
return;\r
+ // Switch to other tab first when we're hiding the active tab.\r
+ else if ( id == this._.currentTabId )\r
+ this.selectPage( getPreviousVisibleTab.call( this ) );\r
+\r
tab.hide();\r
+ this._.pageCount--;\r
+ this.updateStyle();\r
},\r
\r
/**\r
if ( !tab )\r
return;\r
tab.show();\r
+ this._.pageCount++;\r
+ this.updateStyle();\r
},\r
\r
/**\r
*/\r
getContentElement : function( pageId, elementId )\r
{\r
- return this._.contents[pageId][elementId];\r
+ var page = this._.contents[ pageId ];\r
+ return page && page[ elementId ];\r
},\r
\r
/**\r
\r
var defaultDialogDefinition =\r
{\r
- resizable : CKEDITOR.DIALOG_RESIZE_NONE,\r
+ resizable : CKEDITOR.DIALOG_RESIZE_BOTH,\r
minWidth : 600,\r
minHeight : 400,\r
buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ]\r
};\r
\r
+ // The buttons in MacOS Apps are in reverse order #4750\r
+ CKEDITOR.env.mac && defaultDialogDefinition.buttons.reverse();\r
+\r
// Tool function used to return an item from an array based on its id\r
// property.\r
var getById = function( array, id, recurse )\r
// Transform the contents entries in contentObjects.\r
var contents = dialogDefinition.contents;\r
for ( var i = 0, content ; ( content = contents[i] ) ; i++ )\r
- contents[ i ] = new contentObject( dialog, content );\r
+ contents[ i ] = content && new contentObject( dialog, content );\r
\r
CKEDITOR.tools.extend( this, dialogDefinition );\r
};\r
element = dialog.getElement().getFirst(),\r
editor = dialog.getParentEditor(),\r
magnetDistance = editor.config.dialog_magnetDistance,\r
- margins = skinData[ editor.skinName ].margins || [ 0, 0, 0, 0 ];\r
+ margins = editor.skin.margins || [ 0, 0, 0, 0 ];\r
+\r
+ if ( typeof magnetDistance == 'undefined' )\r
+ magnetDistance = 20;\r
\r
function mouseMoveHandler( evt )\r
{\r
\r
if ( CKEDITOR.env.ie6Compat )\r
{\r
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();\r
+ var coverDoc = currentCover.getChild( 0 ).getFrameDocument();\r
coverDoc.removeListener( 'mousemove', mouseMoveHandler );\r
coverDoc.removeListener( 'mouseup', mouseUpHandler );\r
}\r
\r
if ( CKEDITOR.env.ie6Compat )\r
{\r
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();\r
+ var coverDoc = currentCover.getChild( 0 ).getFrameDocument();\r
coverDoc.on( 'mousemove', mouseMoveHandler );\r
coverDoc.on( 'mouseup', mouseUpHandler );\r
}\r
minWidth = definition.minWidth || 0,\r
minHeight = definition.minHeight || 0,\r
resizable = definition.resizable,\r
- margins = skinData[ dialog.getParentEditor().skinName ].margins || [ 0, 0, 0, 0 ];\r
+ margins = dialog.getParentEditor().skin.margins || [ 0, 0, 0, 0 ];\r
\r
function topSizer( coords, dy )\r
{\r
\r
if ( CKEDITOR.env.ie6Compat )\r
{\r
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();\r
+ var coverDoc = currentCover.getChild( 0 ).getFrameDocument();\r
coverDoc.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } );\r
coverDoc.on( 'mouseup', mouseUpHandler, dialog, { part : partName } );\r
}\r
\r
if ( CKEDITOR.env.ie6Compat )\r
{\r
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();\r
+ var coverDoc = currentCover.getChild( 0 ).getFrameDocument();\r
coverDoc.removeListener( 'mouseup', mouseUpHandler );\r
coverDoc.removeListener( 'mousemove', mouseMoveHandler );\r
}\r
}\r
\r
var resizeCover;\r
- var coverElement;\r
+ // Caching resuable covers and allowing only one cover\r
+ // on screen.\r
+ var covers = {},\r
+ currentCover;\r
\r
- var addCover = function( editor )\r
+ function showCover( editor )\r
{\r
var win = CKEDITOR.document.getWindow();\r
+ var backgroundColorStyle = editor.config.dialog_backgroundCoverColor || 'white',\r
+ backgroundCoverOpacity = editor.config.dialog_backgroundCoverOpacity,\r
+ baseFloatZIndex = editor.config.baseFloatZIndex,\r
+ coverKey = CKEDITOR.tools.genKey(\r
+ backgroundColorStyle,\r
+ backgroundCoverOpacity,\r
+ baseFloatZIndex ),\r
+ coverElement = covers[ coverKey ];\r
\r
if ( !coverElement )\r
{\r
var html = [\r
'<div style="position: ', ( CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed' ),\r
- '; z-index: ', editor.config.baseFloatZIndex,\r
+ '; z-index: ', baseFloatZIndex,\r
'; top: 0px; left: 0px; ',\r
- 'background-color: ', editor.config.dialog_backgroundCoverColor,\r
- '" id="cke_dialog_background_cover">'\r
+ ( !CKEDITOR.env.ie6Compat ? 'background-color: ' + backgroundColorStyle : '' ),\r
+ '" class="cke_dialog_background_cover">'\r
];\r
\r
-\r
if ( CKEDITOR.env.ie6Compat )\r
{\r
// Support for custom document.domain in IE.\r
- var isCustomDomain = CKEDITOR.env.isCustomDomain();\r
+ var isCustomDomain = CKEDITOR.env.isCustomDomain(),\r
+ iframeHtml = '<html><body style=\\\'background-color:' + backgroundColorStyle + ';\\\'></body></html>';\r
\r
html.push(\r
'<iframe' +\r
' id="cke_dialog_background_iframe"' +\r
' src="javascript:' );\r
\r
- html.push(\r
- isCustomDomain ?\r
- 'void((function(){' +\r
+ html.push( 'void((function(){' +\r
'document.open();' +\r
- 'document.domain=\'' + document.domain + '\';' +\r
+ ( isCustomDomain ? 'document.domain=\'' + document.domain + '\';' : '' ) +\r
+ 'document.write( \'' + iframeHtml + '\' );' +\r
'document.close();' +\r
- '})())'\r
- :\r
- '\'\'' );\r
+ '})())' );\r
\r
html.push(\r
'"' +\r
html.push( '</div>' );\r
\r
coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) );\r
- }\r
+ coverElement.setOpacity( backgroundCoverOpacity != undefined ? backgroundCoverOpacity : 0.5 );\r
\r
- var element = coverElement;\r
+ coverElement.appendTo( CKEDITOR.document.getBody() );\r
+ covers[ coverKey ] = coverElement;\r
+ }\r
+ else\r
+ coverElement. show();\r
\r
+ currentCover = coverElement;\r
var resizeFunc = function()\r
{\r
var size = win.getViewPaneSize();\r
- element.setStyles(\r
+ coverElement.setStyles(\r
{\r
width : size.width + 'px',\r
height : size.height + 'px'\r
{\r
var pos = win.getScrollPosition(),\r
cursor = CKEDITOR.dialog._.currentTop;\r
- element.setStyles(\r
+ coverElement.setStyles(\r
{\r
left : pos.x + 'px',\r
top : pos.y + 'px'\r
{\r
var dialogPos = cursor.getPosition();\r
cursor.move( dialogPos.x, dialogPos.y );\r
- } while( ( cursor = cursor._.parentDialog ) );\r
+ } while ( ( cursor = cursor._.parentDialog ) );\r
};\r
\r
resizeCover = resizeFunc;\r
}, 0 );\r
scrollFunc();\r
}\r
- element.setOpacity( editor.config.dialog_backgroundCoverOpacity );\r
- element.appendTo( CKEDITOR.document.getBody() );\r
- };\r
+ }\r
\r
- var removeCover = function()\r
+ function hideCover()\r
{\r
- if ( !coverElement )\r
+ if ( !currentCover )\r
return;\r
\r
var win = CKEDITOR.document.getWindow();\r
- coverElement.remove();\r
+ currentCover.hide();\r
win.removeListener( 'resize', resizeCover );\r
\r
if ( CKEDITOR.env.ie6Compat )\r
}, 0 );\r
}\r
resizeCover = null;\r
- };\r
+ }\r
+\r
+ function removeCovers()\r
+ {\r
+ for ( var coverId in covers )\r
+ covers[ coverId ].remove();\r
+ covers = {};\r
+ }\r
\r
var accessKeyProcessors = {};\r
\r
return;\r
\r
keyProcessor = keyProcessor[keyProcessor.length - 1];\r
- keyProcessor.keyup && keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key );\r
- evt.data.preventDefault();\r
+ if ( keyProcessor.keyup )\r
+ {\r
+ keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key );\r
+ evt.data.preventDefault();\r
+ }\r
};\r
\r
var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc )\r
{\r
};\r
\r
+ // ESC, ENTER\r
+ var preventKeyBubblingKeys = { 27 :1, 13 :1 };\r
+ var preventKeyBubbling = function( e )\r
+ {\r
+ if ( e.data.getKeystroke() in preventKeyBubblingKeys )\r
+ e.data.stopPropagation();\r
+ };\r
+\r
(function()\r
{\r
CKEDITOR.ui.dialog =\r
html = [ '<', nodeName, ' ' ],\r
styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {},\r
attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {},\r
- innerHTML = ( contentsArg && contentsArg.call ? contentsArg( dialog, elementDefinition ) : contentsArg ) || '',\r
- domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement',\r
+ innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '',\r
+ domId = this.domId = attributes.id || CKEDITOR.tools.getNextId() + '_uiElement',\r
id = this.id = elementDefinition.id,\r
i;\r
\r
// Register the object as a tab focus if it can be included.\r
if ( this.keyboardFocusable )\r
{\r
+ this.tabIndex = elementDefinition.tabIndex || 0;\r
+\r
this.focusIndex = dialog._.focusList.push( this ) - 1;\r
this.on( 'focus', function()\r
{\r
className = 'cke_dialog_ui_hbox_first';\r
if ( i == childHtmlList.length - 1 )\r
className = 'cke_dialog_ui_hbox_last';\r
- html.push( '<td class="', className, '" ' );\r
+ html.push( '<td class="', className, '" role="presentation" ' );\r
if ( widths )\r
{\r
if ( widths[i] )\r
return html.join( '' );\r
};\r
\r
+ var attribs = { role : 'presentation' };\r
+ elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align );\r
+\r
CKEDITOR.ui.dialog.uiElement.call(\r
this,\r
dialog,\r
htmlList,\r
'table',\r
styles,\r
- elementDefinition && elementDefinition.align && { align : elementDefinition.align } || null,\r
+ attribs,\r
innerHTML );\r
},\r
\r
/** @ignore */\r
var innerHTML = function()\r
{\r
- var html = [ '<table cellspacing="0" border="0" ' ];\r
+ var html = [ '<table role="presentation" cellspacing="0" border="0" ' ];\r
html.push( 'style="' );\r
if ( elementDefinition && elementDefinition.expand )\r
html.push( 'height:100%;' );\r
for ( var i = 0 ; i < childHtmlList.length ; i++ )\r
{\r
var styles = [];\r
- html.push( '<tr><td ' );\r
+ html.push( '<tr><td role="presentation" ' );\r
if ( width )\r
styles.push( 'width:' + CKEDITOR.tools.cssLength( width || '100%' ) );\r
if ( heights )\r
html.push( '</tbody></table>' );\r
return html.join( '' );\r
};\r
- CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, null, innerHTML );\r
+ CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML );\r
}\r
};\r
})();\r
*/\r
isVisible : function()\r
{\r
- return !!this.getInputElement().$.offsetHeight;\r
+ return this.getInputElement().isVisible();\r
},\r
\r
/**\r
{\r
editor.openDialog( this.dialogName );\r
},\r
+\r
// Dialog commands just open a dialog ui, thus require no undo logic,\r
// undo support should dedicate to specific dialog implementation.\r
- canUndo: false\r
+ canUndo: false,\r
+\r
+ editorFocus : CKEDITOR.env.ie || CKEDITOR.env.webkit\r
};\r
\r
(function()\r
return this.functions( function( val ){ return val != value; }, msg );\r
}\r
};\r
- })();\r
\r
- // Grab the margin data from skin definition and store it away.\r
- CKEDITOR.skins.add = ( function()\r
+ CKEDITOR.on( 'instanceDestroyed', function( evt )\r
{\r
- var original = CKEDITOR.skins.add;\r
- return function( skinName, skinDefinition )\r
+ // Remove dialog cover on last instance destroy.\r
+ if ( CKEDITOR.tools.isEmpty( CKEDITOR.instances ) )\r
{\r
- skinData[ skinName ] = { margins : skinDefinition.margins };\r
- return original.apply( this, arguments );\r
- };\r
- } )();\r
+ var currentTopDialog;\r
+ while ( ( currentTopDialog = CKEDITOR.dialog._.currentTop ) )\r
+ currentTopDialog.hide();\r
+ removeCovers();\r
+ }\r
+\r
+ var dialogs = evt.editor._.storedDialogs;\r
+ for ( var name in dialogs )\r
+ dialogs[ name ].destroy();\r
+\r
+ });\r
+\r
+ })();\r
})();\r
\r
// Extend the CKEDITOR.editor class with dialog specific functions.\r
/**\r
* Loads and opens a registered dialog.\r
* @param {String} dialogName The registered name of the dialog.\r
+ * @param {Function} callback The function to be invoked after dialog instance created.\r
* @see CKEDITOR.dialog.add\r
* @example\r
* CKEDITOR.instances.editor1.openDialog( 'smiley' );\r
* @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. null if the dialog name is not registered.\r
*/\r
- openDialog : function( dialogName )\r
+ openDialog : function( dialogName, callback )\r
{\r
- var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ];\r
+ var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ],\r
+ dialogSkin = this.skin.dialog;\r
\r
// If the dialogDefinition is already loaded, open it immediately.\r
- if ( typeof dialogDefinitions == 'function' )\r
+ if ( typeof dialogDefinitions == 'function' && dialogSkin._isLoaded )\r
{\r
var storedDialogs = this._.storedDialogs ||\r
( this._.storedDialogs = {} );\r
var dialog = storedDialogs[ dialogName ] ||\r
( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) );\r
\r
+ callback && callback.call( dialog, dialog );\r
dialog.show();\r
\r
return dialog;\r
me = this;\r
\r
body.setStyle( 'cursor', 'wait' );\r
- CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), function()\r
- {\r
- // In case of plugin error, mark it as loading failed.\r
- if ( typeof CKEDITOR.dialog._.dialogDefinitions[ dialogName ] != 'function' )\r
- CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed';\r
- me.openDialog( dialogName );\r
- body.setStyle( 'cursor', cursor );\r
- } );\r
+\r
+ function onDialogFileLoaded( success )\r
+ {\r
+ var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ],\r
+ skin = me.skin.dialog;\r
+\r
+ // Check if both skin part and definition is loaded.\r
+ if ( !skin._isLoaded || loadDefinition && typeof success == 'undefined' )\r
+ return;\r
+\r
+ // In case of plugin error, mark it as loading failed.\r
+ if ( typeof dialogDefinition != 'function' )\r
+ CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed';\r
+\r
+ me.openDialog( dialogName, callback );\r
+ body.setStyle( 'cursor', cursor );\r
+ }\r
+\r
+ if ( typeof dialogDefinitions == 'string' )\r
+ {\r
+ var loadDefinition = 1;\r
+ CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), onDialogFileLoaded );\r
+ }\r
+\r
+ CKEDITOR.skins.load( this, 'dialog', onDialogFileLoaded );\r
\r
return null;\r
}\r
});\r
\r
+CKEDITOR.plugins.add( 'dialog',\r
+ {\r
+ requires : [ 'dialogui' ]\r
+ });\r
+\r
// Dialog related configurations.\r
\r
/**\r
* The color of the dialog background cover. It should be a valid CSS color\r
* string.\r
+ * @name CKEDITOR.config.dialog_backgroundCoverColor\r
* @type String\r
- * @default white\r
+ * @default 'white'\r
* @example\r
* config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)';\r
*/\r
-CKEDITOR.config.dialog_backgroundCoverColor = 'white';\r
\r
/**\r
* The opacity of the dialog background cover. It should be a number within the\r
* range [0.0, 1.0].\r
+ * @name CKEDITOR.config.dialog_backgroundCoverOpacity\r
* @type Number\r
* @default 0.5\r
* @example\r
* config.dialog_backgroundCoverOpacity = 0.7;\r
*/\r
-CKEDITOR.config.dialog_backgroundCoverOpacity = 0.5;\r
+\r
+/**\r
+ * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened.\r
+ * @name CKEDITOR.config.dialog_startupFocusTab\r
+ * @type Boolean\r
+ * @default false\r
+ * @example\r
+ * config.dialog_startupFocusTab = true;\r
+ */\r
\r
/**\r
* The distance of magnetic borders used in moving and resizing dialogs,\r
* measured in pixels.\r
+ * @name CKEDITOR.config.dialog_magnetDistance\r
* @type Number\r
* @default 20\r
* @example\r
* config.dialog_magnetDistance = 30;\r
*/\r
-CKEDITOR.config.dialog_magnetDistance = 20;\r
+\r
+/**\r
+ * Fired when a dialog definition is about to be used to create a dialog into\r
+ * an editor instance. This event makes it possible to customize the definition\r
+ * before creating it.\r
+ * <p>Note that this event is called only the first time a specific dialog is\r
+ * opened. Successive openings will use the cached dialog, and this event will\r
+ * not get fired.</p>\r
+ * @name CKEDITOR#dialogDefinition\r
+ * @event\r
+ * @param {CKEDITOR.dialog.dialogDefinition} data The dialog defination that\r
+ * is being loaded.\r
+ * @param {CKEDITOR.editor} editor The editor instance that will use the\r
+ * dialog.\r
+ */\r
+\r
+/**\r
+ * Fired when a tab is going to be selected in a dialog\r
+ * @name dialog#selectPage\r
+ * @event\r
+ * @param String page The id of the page that it's gonna be selected.\r
+ * @param String currentPage The id of the current page.\r
+ */\r