X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=_source%2Fplugins%2Fdialog%2Fplugin.js;h=ee8d0e6d1c151dd8b668dc53e8275a0a9a423168;hb=e7789c1ad838194d45eeee6ac2eb6e55f5cf35a1;hp=706c92b61f017e806f7888dfdaaab8fe362ef14e;hpb=941b0a9ba4e673e292510d80a5a86806994b8ea6;p=ckeditor.git
diff --git a/_source/plugins/dialog/plugin.js b/_source/plugins/dialog/plugin.js
index 706c92b..ee8d0e6 100644
--- a/_source/plugins/dialog/plugin.js
+++ b/_source/plugins/dialog/plugin.js
@@ -92,14 +92,6 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
// functions.
definition = new definitionObject( this, definition );
- // Fire the "dialogDefinition" event, making it possible to customize
- // the dialog definition.
- this.definition = definition = CKEDITOR.fire( 'dialogDefinition',
- {
- name : dialogName,
- definition : definition
- }
- , editor ).definition;
var doc = CKEDITOR.document;
@@ -135,6 +127,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
this.parts = themeBuilt.parts;
+ CKEDITOR.tools.setTimeout( function()
+ {
+ editor.fire( 'ariaWidget', this.parts.contents );
+ },
+ 0, this );
+
// Set the startup styles for the dialog, avoiding it enlarging the
// page size on the dialog creation.
this.parts.dialog.setStyles(
@@ -148,6 +146,14 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
// Call the CKEDITOR.event constructor to initialize this instance.
CKEDITOR.event.call( this );
+ // Fire the "dialogDefinition" event, making it possible to customize
+ // the dialog definition.
+ this.definition = definition = CKEDITOR.fire( 'dialogDefinition',
+ {
+ name : dialogName,
+ definition : definition
+ }
+ , editor ).definition;
// Initialize load, show, hide, ok and cancel events.
if ( definition.onLoad )
this.on( 'load', definition.onLoad );
@@ -244,6 +250,26 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
this.hide();
}, this );
+ // Sort focus list according to tab order definitions.
+ function setupFocus()
+ {
+ var focusList = me._.focusList;
+ focusList.sort( function( a, b )
+ {
+ // Mimics browser tab order logics;
+ if ( a.tabIndex != b.tabIndex )
+ return b.tabIndex - a.tabIndex;
+ // Sort is not stable in some browsers,
+ // fall-back the comparator to 'focusIndex';
+ else
+ return a.focusIndex - b.focusIndex;
+ });
+
+ var size = focusList.length;
+ for ( var i = 0; i < size; i++ )
+ focusList[ i ].focusIndex = i;
+ }
+
function changeFocus( forward )
{
var focusList = me._.focusList,
@@ -251,7 +277,17 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
if ( focusList.length < 1 )
return;
- var startIndex = ( me._.currentFocusIndex + offset + focusList.length ) % focusList.length,
+ var current = me._.currentFocusIndex;
+
+ // Trigger the 'blur' event of any input element before anything,
+ // since certain UI updates may depend on it.
+ try
+ {
+ focusList[ current ].getInputElement().$.blur();
+ }
+ catch( e ){}
+
+ var startIndex = ( current + offset + focusList.length ) % focusList.length,
currentIndex = startIndex;
while ( !focusList[ currentIndex ].isFocusable() )
{
@@ -266,6 +302,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
focusList[ currentIndex ].select();
}
+ this.changeFocus = changeFocus;
+
var processed;
function focusKeydownHandler( evt )
@@ -297,7 +335,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
processed = 1;
}
- else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode )
+ else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 )
{
// Alt-F10 puts focus into the current tab item in the tab bar.
me._.tabBarMode = true;
@@ -312,6 +350,14 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
me._.tabs[ nextId ][ 0 ].focus();
processed = 1;
}
+ else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode )
+ {
+ this.selectPage( this._.currentTabId );
+ this._.tabBarMode = false;
+ this._.currentFocusIndex = -1;
+ changeFocus( true );
+ processed = 1;
+ }
if ( processed )
{
@@ -325,14 +371,15 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
processed && evt.data.preventDefault();
}
+ var dialogElement = this._.element;
// Add the dialog keyboard handlers.
this.on( 'show', function()
{
- CKEDITOR.document.on( 'keydown', focusKeydownHandler, this, null, 0 );
+ dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 );
// Some browsers instead, don't cancel key events in the keydown, but in the
// keypress. So we must do a longer trip in those cases. (#4531)
if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
- CKEDITOR.document.on( 'keypress', focusKeyPressHandler, this );
+ dialogElement.on( 'keypress', focusKeyPressHandler, this );
if ( CKEDITOR.env.ie6Compat )
{
@@ -342,9 +389,9 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
} );
this.on( 'hide', function()
{
- CKEDITOR.document.removeListener( 'keydown', focusKeydownHandler );
+ dialogElement.removeListener( 'keydown', focusKeydownHandler );
if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
- CKEDITOR.document.removeListener( 'keypress', focusKeyPressHandler );
+ dialogElement.removeListener( 'keypress', focusKeyPressHandler );
} );
this.on( 'iframeAdded', function( evt )
{
@@ -355,10 +402,30 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
// Auto-focus logic in dialog.
this.on( 'show', function()
{
- if ( !this._.hasFocus )
+ // Setup tabIndex on showing the dialog instead of on loading
+ // to allow dynamic tab order happen in dialog definition.
+ setupFocus();
+
+ if ( editor.config.dialog_startupFocusTab
+ && me._.tabIdList.length > 1 )
+ {
+ me._.tabBarMode = true;
+ me._.tabs[ me._.currentTabId ][ 0 ].focus();
+ }
+ else if ( !this._.hasFocus )
{
this._.currentFocusIndex = -1;
- changeFocus( true );
+
+ // Decide where to put the initial focus.
+ if ( definition.onFocus )
+ {
+ var initialFocus = definition.onFocus.call( this );
+ // Focus the field that the user specified.
+ initialFocus && initialFocus.focus();
+ }
+ // Focus the first field in layout order.
+ else
+ changeFocus( true );
/*
* IE BUG: If the initial focus went into a non-text element (e.g. button),
@@ -407,28 +474,22 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
for ( var i = 0 ; i < definition.contents.length ; i++ )
this.addPage( definition.contents[i] );
- var tabRegex = /cke_dialog_tab(\s|$|_)/,
- tabOuterRegex = /cke_dialog_tab(\s|$)/;
this.parts['tabs'].on( 'click', function( evt )
{
- var target = evt.data.getTarget(), firstNode = target, id, page;
-
+ var target = evt.data.getTarget();
// If we aren't inside a tab, bail out.
- if ( !( tabRegex.test( target.$.className ) || target.getName() == 'a' ) )
- return;
-
- // Find the outer
container of the tab.
- id = target.$.id.substr( 0, target.$.id.lastIndexOf( '_' ) );
- this.selectPage( id );
-
- if ( this._.tabBarMode )
+ if ( target.hasClass( 'cke_dialog_tab' ) )
{
- this._.tabBarMode = false;
- this._.currentFocusIndex = -1;
- changeFocus( true );
+ var id = target.$.id;
+ this.selectPage( id.substr( 0, id.lastIndexOf( '_' ) ) );
+ if ( this._.tabBarMode )
+ {
+ this._.tabBarMode = false;
+ this._.currentFocusIndex = -1;
+ changeFocus( true );
+ }
+ evt.data.preventDefault();
}
-
- evt.data.preventDefault();
}, this );
// Insert buttons.
@@ -444,8 +505,6 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
for ( i = 0 ; i < buttons.length ; i++ )
this._.buttons[ buttons[i].id ] = buttons[i];
-
- CKEDITOR.skins.load( editor, 'dialog' );
};
// Focusable interface. Use it via dialog.addFocusable.
@@ -453,6 +512,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
{
this.element = element;
this.focusIndex = index;
+ // TODO: support tabIndex for focusables.
+ this.tabIndex = 0;
this.isFocusable = function()
{
return !element.getAttribute( 'disabled' ) && element.isVisible();
@@ -805,34 +866,37 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
children : contents.elements,
expand : !!contents.expand,
padding : contents.padding,
- style : contents.style || 'width: 100%;'
+ style : contents.style || 'width: 100%; height: 100%;'
}, pageHtml );
// Create the HTML for the tab and the content block.
var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) );
- var tab = CKEDITOR.dom.element.createFromHtml( [
+ page.setAttribute( 'role', 'tabpanel' );
+
+ var env = CKEDITOR.env;
+ var tabId = contents.id + '_' + CKEDITOR.tools.getNextNumber(),
+ tab = CKEDITOR.dom.element.createFromHtml( [
' 0 ? ' cke_last' : 'cke_first' ),
titleHtml,
( !!contents.hidden ? ' style="display:none"' : '' ),
- ' id="', contents.id + '_', CKEDITOR.tools.getNextNumber(), '"' +
- ' href="javascript:void(0)"',
- ' hidefocus="true">',
+ ' id="', tabId, '"',
+ env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"',
+ ' tabIndex="-1"',
+ ' hidefocus="true"',
+ ' role="tab">',
contents.label,
''
].join( '' ) );
- // If only a single page exist, a different style is used in the central pane.
- if ( this._.pageCount === 0 )
- this.parts.dialog.addClass( 'cke_single_page' );
- else
- this.parts.dialog.removeClass( 'cke_single_page' );
+ page.setAttribute( 'aria-labelledby', tabId );
// Take records for the tabs and elements created.
this._.tabs[ contents.id ] = [ tab, page ];
this._.tabIdList.push( contents.id );
- this._.pageCount++;
+ !contents.hidden && this._.pageCount++;
this._.lastTab = tab;
+ this.updateStyle();
var contentMap = this._.contents[ contents.id ] = {},
cursor,
@@ -880,6 +944,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
tab.removeClass( 'cke_dialog_tab_selected' );
page.hide();
}
+ page.setAttribute( 'aria-hidden', i != id );
}
var selected = this._.tabs[id];
@@ -889,6 +954,13 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id );
},
+ // Dialog state-specific style updates.
+ updateStyle : function()
+ {
+ // If only a single page shown, a different style is used in the central pane.
+ this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' );
+ },
+
/**
* Hides a page's tab away from the dialog.
* @param {String} id The page's Id.
@@ -898,9 +970,15 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
hidePage : function( id )
{
var tab = this._.tabs[id] && this._.tabs[id][0];
- if ( !tab )
+ if ( !tab || this._.pageCount == 1 )
return;
+ // Switch to other tab first when we're hiding the active tab.
+ else if ( id == this._.currentTabId )
+ this.selectPage( getPreviousVisibleTab.call( this ) );
+
tab.hide();
+ this._.pageCount--;
+ this.updateStyle();
},
/**
@@ -915,6 +993,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
if ( !tab )
return;
tab.show();
+ this._.pageCount++;
+ this.updateStyle();
},
/**
@@ -949,7 +1029,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
*/
getContentElement : function( pageId, elementId )
{
- return this._.contents[pageId][elementId];
+ var page = this._.contents[ pageId ];
+ return page && page[ elementId ];
},
/**
@@ -1196,12 +1277,15 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
var defaultDialogDefinition =
{
- resizable : CKEDITOR.DIALOG_RESIZE_NONE,
+ resizable : CKEDITOR.DIALOG_RESIZE_BOTH,
minWidth : 600,
minHeight : 400,
buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ]
};
+ // The buttons in MacOS Apps are in reverse order #4750
+ CKEDITOR.env.mac && defaultDialogDefinition.buttons.reverse();
+
// Tool function used to return an item from an array based on its id
// property.
var getById = function( array, id, recurse )
@@ -1746,7 +1830,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
{
var dialogPos = cursor.getPosition();
cursor.move( dialogPos.x, dialogPos.y );
- } while( ( cursor = cursor._.parentDialog ) );
+ } while ( ( cursor = cursor._.parentDialog ) );
};
resizeCover = resizeFunc;
@@ -1937,7 +2021,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
html = [ '<', nodeName, ' ' ],
styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {},
attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {},
- innerHTML = ( contentsArg && contentsArg.call ? contentsArg( dialog, elementDefinition ) : contentsArg ) || '',
+ innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '',
domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement',
id = this.id = elementDefinition.id,
i;
@@ -2022,6 +2106,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
// Register the object as a tab focus if it can be included.
if ( this.keyboardFocusable )
{
+ this.tabIndex = elementDefinition.tabIndex || 0;
+
this.focusIndex = dialog._.focusList.push( this ) - 1;
this.on( 'focus', function()
{
@@ -2084,7 +2170,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
className = 'cke_dialog_ui_hbox_first';
if ( i == childHtmlList.length - 1 )
className = 'cke_dialog_ui_hbox_last';
- html.push( ' | | | ' );
return html.join( '' );
};
- CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, null, innerHTML );
+ CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML );
}
};
})();
@@ -2563,9 +2652,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
{
editor.openDialog( this.dialogName );
},
+
// Dialog commands just open a dialog ui, thus require no undo logic,
// undo support should dedicate to specific dialog implementation.
- canUndo: false
+ canUndo: false,
+
+ editorFocus : CKEDITOR.env.ie
};
(function()
@@ -2703,10 +2795,11 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
*/
openDialog : function( dialogName, callback )
{
- var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ];
+ var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ],
+ dialogSkin = this.skin.dialog;
// If the dialogDefinition is already loaded, open it immediately.
- if ( typeof dialogDefinitions == 'function' )
+ if ( typeof dialogDefinitions == 'function' && dialogSkin._isLoaded )
{
var storedDialogs = this._.storedDialogs ||
( this._.storedDialogs = {} );
@@ -2728,14 +2821,31 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
me = this;
body.setStyle( 'cursor', 'wait' );
- CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), function()
- {
- // In case of plugin error, mark it as loading failed.
- if ( typeof CKEDITOR.dialog._.dialogDefinitions[ dialogName ] != 'function' )
- CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed';
- me.openDialog( dialogName, callback );
- body.setStyle( 'cursor', cursor );
- } );
+
+ function onDialogFileLoaded( success )
+ {
+ var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ],
+ skin = me.skin.dialog;
+
+ // Check if both skin part and definition is loaded.
+ if ( !skin._isLoaded || loadDefinition && typeof success == 'undefined' )
+ return;
+
+ // In case of plugin error, mark it as loading failed.
+ if ( typeof dialogDefinition != 'function' )
+ CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed';
+
+ me.openDialog( dialogName, callback );
+ body.setStyle( 'cursor', cursor );
+ }
+
+ if ( typeof dialogDefinitions == 'string' )
+ {
+ var loadDefinition = 1;
+ CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), onDialogFileLoaded );
+ }
+
+ CKEDITOR.skins.load( this, 'dialog', onDialogFileLoaded );
return null;
}
@@ -2769,6 +2879,15 @@ CKEDITOR.plugins.add( 'dialog',
*/
/**
+ * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened.
+ * @name CKEDITOR.config.dialog_startupFocusTab
+ * @type Boolean
+ * @default false
+ * @example
+ * config.dialog_startupFocusTab = true;
+ */
+
+/**
* The distance of magnetic borders used in moving and resizing dialogs,
* measured in pixels.
* @name CKEDITOR.config.dialog_magnetDistance
@@ -2777,3 +2896,18 @@ CKEDITOR.plugins.add( 'dialog',
* @example
* config.dialog_magnetDistance = 30;
*/
+
+/**
+ * Fired when a dialog definition is about to be used to create a dialog into
+ * an editor instance. This event makes it possible to customize the definition
+ * before creating it.
+ * Note that this event is called only the first time a specific dialog is
+ * opened. Successive openings will use the cached dialog, and this event will
+ * not get fired.
+ * @name CKEDITOR#dialogDefinition
+ * @event
+ * @param {CKEDITOR.dialog.dialogDefinition} data The dialog defination that
+ * is being loaded.
+ * @param {CKEDITOR.editor} editor The editor instance that will use the
+ * dialog.
+ */
|