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