1 /* 2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 /** 7 * @fileOverview The "elementspath" plugin. It shows all elements in the DOM 8 * parent tree relative to the current selection in the editing area. 9 */ 10 11 (function() 12 { 13 var commands = 14 { 15 toolbarFocus : 16 { 17 editorFocus : false, 18 readOnly : 1, 19 exec : function( editor ) 20 { 21 var idBase = editor._.elementsPath.idBase; 22 var element = CKEDITOR.document.getById( idBase + '0' ); 23 24 // Make the first button focus accessible for IE. (#3417) 25 // Adobe AIR instead need while of delay. 26 element && element.focus( CKEDITOR.env.ie || CKEDITOR.env.air ); 27 } 28 } 29 }; 30 31 var emptyHtml = '<span class="cke_empty"> </span>'; 32 33 CKEDITOR.plugins.add( 'elementspath', 34 { 35 requires : [ 'selection' ], 36 37 init : function( editor ) 38 { 39 var spaceId = 'cke_path_' + editor.name; 40 var spaceElement; 41 var getSpaceElement = function() 42 { 43 if ( !spaceElement ) 44 spaceElement = CKEDITOR.document.getById( spaceId ); 45 return spaceElement; 46 }; 47 48 var idBase = 'cke_elementspath_' + CKEDITOR.tools.getNextNumber() + '_'; 49 50 editor._.elementsPath = { idBase : idBase, filters : [] }; 51 52 editor.on( 'themeSpace', function( event ) 53 { 54 if ( event.data.space == 'bottom' ) 55 { 56 event.data.html += 57 '<span id="' + spaceId + '_label" class="cke_voice_label">' + editor.lang.elementsPath.eleLabel + '</span>' + 58 '<div id="' + spaceId + '" class="cke_path" role="group" aria-labelledby="' + spaceId + '_label">' + emptyHtml + '</div>'; 59 } 60 }); 61 62 function onClick( elementIndex ) 63 { 64 editor.focus(); 65 var element = editor._.elementsPath.list[ elementIndex ]; 66 if ( element.is( 'body' ) ) 67 { 68 var range = new CKEDITOR.dom.range( editor.document ); 69 range.selectNodeContents( element ); 70 range.select(); 71 } 72 else 73 editor.getSelection().selectElement( element ); 74 } 75 76 var onClickHanlder = CKEDITOR.tools.addFunction( onClick ); 77 78 var onKeyDownHandler = CKEDITOR.tools.addFunction( function( elementIndex, ev ) 79 { 80 var idBase = editor._.elementsPath.idBase, 81 element; 82 83 ev = new CKEDITOR.dom.event( ev ); 84 85 var rtl = editor.lang.dir == 'rtl'; 86 switch ( ev.getKeystroke() ) 87 { 88 case rtl ? 39 : 37 : // LEFT-ARROW 89 case 9 : // TAB 90 element = CKEDITOR.document.getById( idBase + ( elementIndex + 1 ) ); 91 if ( !element ) 92 element = CKEDITOR.document.getById( idBase + '0' ); 93 element.focus(); 94 return false; 95 96 case rtl ? 37 : 39 : // RIGHT-ARROW 97 case CKEDITOR.SHIFT + 9 : // SHIFT + TAB 98 element = CKEDITOR.document.getById( idBase + ( elementIndex - 1 ) ); 99 if ( !element ) 100 element = CKEDITOR.document.getById( idBase + ( editor._.elementsPath.list.length - 1 ) ); 101 element.focus(); 102 return false; 103 104 case 27 : // ESC 105 editor.focus(); 106 return false; 107 108 case 13 : // ENTER // Opera 109 case 32 : // SPACE 110 onClick( elementIndex ); 111 return false; 112 } 113 return true; 114 }); 115 116 editor.on( 'selectionChange', function( ev ) 117 { 118 var env = CKEDITOR.env, 119 selection = ev.data.selection, 120 element = selection.getStartElement(), 121 html = [], 122 editor = ev.editor, 123 elementsList = editor._.elementsPath.list = [], 124 filters = editor._.elementsPath.filters; 125 126 while ( element ) 127 { 128 var ignore = 0, 129 name; 130 131 if ( element.data( 'cke-display-name' ) ) 132 name = element.data( 'cke-display-name' ); 133 else if ( element.data( 'cke-real-element-type' ) ) 134 name = element.data( 'cke-real-element-type' ); 135 else 136 name = element.getName(); 137 138 for ( var i = 0; i < filters.length; i++ ) 139 { 140 var ret = filters[ i ]( element, name ); 141 if ( ret === false ) 142 { 143 ignore = 1; 144 break; 145 } 146 name = ret || name; 147 } 148 149 if ( !ignore ) 150 { 151 var index = elementsList.push( element ) - 1; 152 153 // Use this variable to add conditional stuff to the 154 // HTML (because we are doing it in reverse order... unshift). 155 var extra = ''; 156 157 // Some browsers don't cancel key events in the keydown but in the 158 // keypress. 159 // TODO: Check if really needed for Gecko+Mac. 160 if ( env.opera || ( env.gecko && env.mac ) ) 161 extra += ' onkeypress="return false;"'; 162 163 // With Firefox, we need to force the button to redraw, otherwise it 164 // will remain in the focus state. 165 if ( env.gecko ) 166 extra += ' onblur="this.style.cssText = this.style.cssText;"'; 167 168 var label = editor.lang.elementsPath.eleTitle.replace( /%1/, name ); 169 html.unshift( 170 '<a' + 171 ' id="', idBase, index, '"' + 172 ' href="javascript:void(\'', name, '\')"' + 173 ' tabindex="-1"' + 174 ' title="', label, '"' + 175 ( ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) ? 176 ' onfocus="event.preventBubble();"' : '' ) + 177 ' hidefocus="true" ' + 178 ' onkeydown="return CKEDITOR.tools.callFunction(', onKeyDownHandler, ',', index, ', event );"' + 179 extra , 180 ' onclick="CKEDITOR.tools.callFunction('+ onClickHanlder, ',', index, '); return false;"', 181 ' role="button" aria-labelledby="' + idBase + index + '_label">', 182 name, 183 '<span id="', idBase, index, '_label" class="cke_label">' + label + '</span>', 184 '</a>' ); 185 186 } 187 188 if ( name == 'body' ) 189 break; 190 191 element = element.getParent(); 192 } 193 194 var space = getSpaceElement(); 195 space.setHtml( html.join('') + emptyHtml ); 196 editor.fire( 'elementsPathUpdate', { space : space } ); 197 }); 198 199 function empty() 200 { 201 spaceElement && spaceElement.setHtml( emptyHtml ); 202 delete editor._.elementsPath.list; 203 } 204 205 editor.on( 'readOnly', empty ); 206 editor.on( 'contentDomUnload', empty ); 207 208 editor.addCommand( 'elementsPathFocus', commands.toolbarFocus ); 209 } 210 }); 211 })(); 212 213 /** 214 * Fired when the contents of the elementsPath are changed 215 * @name CKEDITOR.editor#elementsPathUpdate 216 * @event 217 * @param {Object} eventData.space The elementsPath container 218 */ 219