1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 (function()
  7 {
  8 	var blurCommand =
  9 		{
 10 			exec : function( editor )
 11 			{
 12 				editor.container.focusNext( true );
 13 			}
 14 		};
 15
 16 	var blurBackCommand =
 17 		{
 18 			exec : function( editor )
 19 			{
 20 				editor.container.focusPrevious( true );
 21 			}
 22 		};
 23
 24 	CKEDITOR.plugins.add( 'tab',
 25 	{
 26 		requires : [ 'keystrokes' ],
 27
 28 		init : function( editor )
 29 		{
 30 			// Register the keystrokes.
 31 			var keystrokes = editor.keystrokeHandler.keystrokes;
 32 			keystrokes[ 9 /* TAB */ ] = 'tab';
 33 			keystrokes[ CKEDITOR.SHIFT + 9 /* TAB */ ] = 'shiftTab';
 34
 35 			var tabSpaces = editor.config.tabSpaces,
 36 				tabText = '';
 37
 38 			while ( tabSpaces-- )
 39 				tabText += '\xa0';
 40
 41 			// Register the "tab" and "shiftTab" commands.
 42 			editor.addCommand( 'tab',
 43 				{
 44 					exec : function( editor )
 45 					{
 46 						// Fire the "tab" event, making it possible to
 47 						// customize the TAB key behavior on specific cases.
 48 						if ( !editor.fire( 'tab' ) )
 49 						{
 50 							if ( tabText.length > 0 )
 51 								editor.insertHtml( tabText );
 52 							else
 53 							{
 54 								// All browsers jump to the next field on TAB,
 55 								// except Safari, so we have to do that manually
 56 								// here.
 57 								/// https://bugs.webkit.org/show_bug.cgi?id=20597
 58 								return editor.execCommand( 'blur' );
 59 							}
 60 						}
 61
 62 						return true;
 63 					}
 64 				});
 65
 66 			editor.addCommand( 'shiftTab',
 67 				{
 68 					exec : function( editor )
 69 					{
 70 						// Fire the "tab" event, making it possible to
 71 						// customize the TAB key behavior on specific cases.
 72 						if ( !editor.fire( 'shiftTab' ) )
 73 							return editor.execCommand( 'blurBack' );
 74
 75 						return true;
 76 					}
 77 				});
 78
 79 			editor.addCommand( 'blur', blurCommand );
 80 			editor.addCommand( 'blurBack', blurBackCommand );
 81 		}
 82 	});
 83 })();
 84
 85 /**
 86  * Moves the UI focus to the element following this element in the tabindex
 87  * order.
 88  * @example
 89  * var element = CKEDITOR.document.getById( 'example' );
 90  * element.focusNext();
 91  */
 92 CKEDITOR.dom.element.prototype.focusNext = function( ignoreChildren )
 93 {
 94 	var $ = this.$,
 95 		curTabIndex = this.getTabIndex(),
 96 		passedCurrent, enteredCurrent,
 97 		elected, electedTabIndex,
 98 		element, elementTabIndex;
 99
100 	if ( curTabIndex <= 0 )
101 	{
102 		// If this element has tabindex <= 0 then we must simply look for any
103 		// element following it containing tabindex=0.
104
105 		element = this.getNextSourceNode( ignoreChildren, CKEDITOR.NODE_ELEMENT );
106
107 		while( element )
108 		{
109 			if ( element.isVisible() && element.getTabIndex() === 0 )
110 			{
111 				elected = element;
112 				break;
113 			}
114
115 			element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT );
116 		}
117 	}
118 	else
119 	{
120 		// If this element has tabindex > 0 then we must look for:
121 		//		1. An element following this element with the same tabindex.
122 		//		2. The first element in source other with the lowest tabindex
123 		//		   that is higher than this element tabindex.
124 		//		3. The first element with tabindex=0.
125
126 		element = this.getDocument().getBody().getFirst();
127
128 		while( ( element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
129 		{
130 			if ( !passedCurrent )
131 			{
132 				if ( !enteredCurrent && element.equals( this ) )
133 				{
134 					enteredCurrent = true;
135
136 					// Ignore this element, if required.
137 					if ( ignoreChildren )
138 					{
139 						if ( !( element = element.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
140 							break;
141 						passedCurrent = 1;
142 					}
143 				}
144 				else if ( enteredCurrent && !this.contains( element ) )
145 					passedCurrent = 1;
146 			}
147
148 			if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
149 				continue;
150
151 			if ( passedCurrent && elementTabIndex == curTabIndex )
152 			{
153 				elected = element;
154 				break;
155 			}
156
157 			if ( elementTabIndex > curTabIndex && ( !elected || !electedTabIndex || elementTabIndex < electedTabIndex ) )
158 			{
159 				elected = element;
160 				electedTabIndex = elementTabIndex;
161 			}
162 			else if ( !elected && elementTabIndex === 0 )
163 			{
164 				elected = element;
165 				electedTabIndex = elementTabIndex;
166 			}
167 		}
168 	}
169
170 	if ( elected )
171 		elected.focus();
172 };
173
174 /**
175  * Moves the UI focus to the element before this element in the tabindex order.
176  * @example
177  * var element = CKEDITOR.document.getById( 'example' );
178  * element.focusPrevious();
179  */
180 CKEDITOR.dom.element.prototype.focusPrevious = function( ignoreChildren )
181 {
182 	var $ = this.$,
183 		curTabIndex = this.getTabIndex(),
184 		passedCurrent, enteredCurrent,
185 		elected,
186 		electedTabIndex = 0,
187 		elementTabIndex;
188
189 	var element = this.getDocument().getBody().getLast();
190
191 	while( ( element = element.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
192 	{
193 		if ( !passedCurrent )
194 		{
195 			if ( !enteredCurrent && element.equals( this ) )
196 			{
197 				enteredCurrent = true;
198
199 				// Ignore this element, if required.
200 				if ( ignoreChildren )
201 				{
202 					if ( !( element = element.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
203 						break;
204 					passedCurrent = 1;
205 				}
206 			}
207 			else if ( enteredCurrent && !this.contains( element ) )
208 				passedCurrent = 1;
209 		}
210
211 		if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
212 			continue;
213
214 		if ( curTabIndex <= 0 )
215 		{
216 			// If this element has tabindex <= 0 then we must look for:
217 			//		1. An element before this one containing tabindex=0.
218 			//		2. The last element with the highest tabindex.
219
220 			if ( passedCurrent && elementTabIndex === 0 )
221 			{
222 				elected = element;
223 				break;
224 			}
225
226 			if ( elementTabIndex > electedTabIndex )
227 			{
228 				elected = element;
229 				electedTabIndex = elementTabIndex;
230 			}
231 		}
232 		else
233 		{
234 			// If this element has tabindex > 0 we must look for:
235 			//		1. An element preceeding this one, with the same tabindex.
236 			//		2. The last element in source other with the highest tabindex
237 			//		   that is lower than this element tabindex.
238
239 			if ( passedCurrent && elementTabIndex == curTabIndex )
240 			{
241 				elected = element;
242 				break;
243 			}
244
245 			if ( elementTabIndex < curTabIndex && ( !elected || elementTabIndex > electedTabIndex ) )
246 			{
247 				elected = element;
248 				electedTabIndex = elementTabIndex;
249 			}
250 		}
251 	}
252
253 	if ( elected )
254 		elected.focus();
255 };
256
257 /**
258  * Intructs the editor to add a number of spaces (&nbsp;) to the text when
259  * hitting the TAB key. If set to zero, the TAB key will have its default
260  * behavior instead (like moving out of the editor).
261  * @type {Number}
262  * @default 0
263  * @example
264  * config.tabSpaces = 4;
265  */
266 CKEDITOR.config.tabSpaces = 0 ;
267