JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.2
[ckeditor.git] / _source / plugins / dialog / plugin.js
index fa4cc99..6b5c2e4 100644 (file)
@@ -127,6 +127,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
 \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
@@ -244,6 +250,26 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                                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
@@ -251,7 +277,17 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                        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
@@ -266,6 +302,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                focusList[ currentIndex ].select();\r
                }\r
 \r
+               this.changeFocus = changeFocus;\r
+\r
                var processed;\r
 \r
                function focusKeydownHandler( evt )\r
@@ -312,6 +350,14 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                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
@@ -325,14 +371,15 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                        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
@@ -342,9 +389,9 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                        } );\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
@@ -355,10 +402,30 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                // 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
@@ -407,28 +474,22 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                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
@@ -453,6 +514,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
        {\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
@@ -810,18 +873,26 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
 \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
@@ -880,6 +951,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                        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
@@ -1938,7 +2010,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                        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
@@ -2023,6 +2095,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                // 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
@@ -2085,7 +2159,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                                        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
@@ -2105,6 +2179,9 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                        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
@@ -2112,7 +2189,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                        htmlList,\r
                                        'table',\r
                                        styles,\r
-                                       elementDefinition && elementDefinition.align && { align : elementDefinition.align } || null,\r
+                                       attribs,\r
                                        innerHTML );\r
                        },\r
 \r
@@ -2157,7 +2234,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                /** @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
@@ -2170,7 +2247,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                        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
@@ -2186,7 +2263,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                                        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
@@ -2564,9 +2641,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3;
                {\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
@@ -2770,6 +2850,15 @@ CKEDITOR.plugins.add( 'dialog',
  */\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