// 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
this.hide();\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 startIndex = ( 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
focusList[ currentIndex ].select();\r
}\r
\r
+ this.changeFocus = changeFocus;\r
+\r
var processed;\r
\r
function focusKeydownHandler( evt )\r
me._.tabs[ nextId ][ 0 ].focus();\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
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
- CKEDITOR.document.on( 'keypress', focusKeyPressHandler, this );\r
+ dialogElement.on( 'keypress', focusKeyPressHandler, this );\r
\r
if ( CKEDITOR.env.ie6Compat )\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
- CKEDITOR.document.removeListener( 'keypress', focusKeyPressHandler );\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._.tabIdList.length > 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
for ( var i = 0 ; i < definition.contents.length ; i++ )\r
this.addPage( definition.contents[i] );\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
+ var id = target.$.id;\r
+ this.selectPage( id.substr( 0, id.lastIndexOf( '_' ) ) );\r
+ if ( this._.tabBarMode )\r
+ {\r
+ this._.tabBarMode = false;\r
+ this._.currentFocusIndex = -1;\r
+ changeFocus( true );\r
+ }\r
+ evt.data.preventDefault();\r
}\r
-\r
- evt.data.preventDefault();\r
}, this );\r
\r
// Insert buttons.\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 !element.getAttribute( 'disabled' ) && element.isVisible();\r
children : contents.elements,\r
expand : !!contents.expand,\r
padding : contents.padding,\r
- style : contents.style || 'width: 100%;'\r
+ style : contents.style || 'width: 100%;' + ( CKEDITOR.env.ie6Compat ? '' : 'height: 100%;' )\r
}, pageHtml );\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 = 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
+ page.setAttribute( 'aria-labelledby', tabId );\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
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
*/\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 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
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
+ innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '',\r
domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement',\r
id = this.id = elementDefinition.id,\r
i;\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
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\r
};\r
\r
(function()\r
*/\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
* @example\r
* config.dialog_magnetDistance = 30;\r
*/\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