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 (function()
  7 {
  8 	var meta =
  9 	{
 10 		editorFocus : false,
 11 		modes : { wysiwyg:1, source:1 }
 12 	};
 13 
 14 	var blurCommand =
 15 		{
 16 			exec : function( editor )
 17 			{
 18 				editor.container.focusNext( true, editor.tabIndex );
 19 			}
 20 		};
 21 
 22 	var blurBackCommand =
 23 		{
 24 			exec : function( editor )
 25 			{
 26 				editor.container.focusPrevious( true, editor.tabIndex );
 27 			}
 28 		};
 29 
 30 	function selectNextCellCommand( backward )
 31 	{
 32 		return {
 33 			editorFocus : false,
 34 			canUndo : false,
 35 			modes : { wysiwyg : 1 },
 36 			exec : function( editor )
 37 			{
 38 				if ( editor.focusManager.hasFocus )
 39 				{
 40 					var sel = editor.getSelection(),
 41 						ancestor = sel.getCommonAncestor(),
 42 						cell;
 43 
 44 					if ( ( cell = ( ancestor.getAscendant( 'td', true ) || ancestor.getAscendant( 'th', true ) ) ) )
 45 					{
 46 						var resultRange = new CKEDITOR.dom.range( editor.document ),
 47 								next = CKEDITOR.tools.tryThese( function()
 48 								{
 49 									var row = cell.getParent(),
 50 											next = row.$.cells[ cell.$.cellIndex + ( backward ? - 1 : 1 ) ];
 51 
 52 									// Invalid any empty value.
 53 									next.parentNode.parentNode;
 54 									return next;
 55 								},
 56 								function()
 57 								{
 58 									var row = cell.getParent(),
 59 											table = row.getAscendant( 'table' ),
 60 											nextRow = table.$.rows[ row.$.rowIndex + ( backward ? - 1 : 1 ) ];
 61 
 62 									return nextRow.cells[ backward? nextRow.cells.length -1 : 0 ];
 63 								});
 64 
 65 						// Clone one more row at the end of table and select the first newly established cell.
 66 						if ( ! ( next || backward ) )
 67 						{
 68 							var table = cell.getAscendant( 'table' ).$,
 69 									cells = cell.getParent().$.cells;
 70 
 71 							var newRow = new CKEDITOR.dom.element( table.insertRow( -1 ), editor.document );
 72 
 73 							for ( var i = 0, count = cells.length ; i < count; i++ )
 74 							{
 75 								var newCell = newRow.append( new CKEDITOR.dom.element(
 76 										cells[ i ], editor.document ).clone( false, false ) );
 77 								!CKEDITOR.env.ie && newCell.appendBogus();
 78 							}
 79 
 80 							resultRange.moveToElementEditStart( newRow );
 81 						}
 82 						else if ( next )
 83 						{
 84 							next = new CKEDITOR.dom.element( next );
 85 							resultRange.moveToElementEditStart( next );
 86 							// Avoid selecting empty block makes the cursor blind.
 87 							if ( !( resultRange.checkStartOfBlock() && resultRange.checkEndOfBlock() ) )
 88 								resultRange.selectNodeContents( next );
 89 						}
 90 						else
 91 							return true;
 92 
 93 						resultRange.select( true );
 94 						return true;
 95 					}
 96 				}
 97 				return false;
 98 			}
 99 		};
100 	}
101 
102 	CKEDITOR.plugins.add( 'tab',
103 	{
104 		requires : [ 'keystrokes' ],
105 
106 		init : function( editor )
107 		{
108 			var tabTools = editor.config.enableTabKeyTools !== false,
109 				tabSpaces = editor.config.tabSpaces || 0,
110 				tabText = '';
111 
112 			while ( tabSpaces-- )
113 				tabText += '\xa0';
114 
115 			if ( tabText )
116 			{
117 				editor.on( 'key', function( ev )
118 					{
119 						if ( ev.data.keyCode == 9 )	// TAB
120 						{
121 							editor.insertHtml( tabText );
122 							ev.cancel();
123 						}
124 					});
125 			}
126 
127 			if ( tabTools )
128 			{
129 				editor.on( 'key', function( ev )
130 				{
131 					if ( ev.data.keyCode == 9 && editor.execCommand( 'selectNextCell' ) ||	// TAB
132 							ev.data.keyCode == ( CKEDITOR.SHIFT + 9 ) && editor.execCommand( 'selectPreviousCell' ) )	// SHIFT+TAB
133 						ev.cancel();
134 				});
135 			}
136 
137 			if ( CKEDITOR.env.webkit || CKEDITOR.env.gecko )
138 			{
139 				editor.on( 'key', function( ev )
140 					{
141 						var keyCode = ev.data.keyCode;
142 
143 						if ( keyCode == 9 && !tabText )				// TAB
144 						{
145 							ev.cancel();
146 							editor.execCommand( 'blur' );
147 						}
148 
149 						if ( keyCode == ( CKEDITOR.SHIFT + 9 ) )	// SHIFT+TAB
150 						{
151 							editor.execCommand( 'blurBack' );
152 							ev.cancel();
153 						}
154 					});
155 			}
156 
157 			editor.addCommand( 'blur', CKEDITOR.tools.extend( blurCommand, meta ) );
158 			editor.addCommand( 'blurBack', CKEDITOR.tools.extend( blurBackCommand, meta ) );
159 			editor.addCommand( 'selectNextCell', selectNextCellCommand() );
160 			editor.addCommand( 'selectPreviousCell', selectNextCellCommand( true ) );
161 		}
162 	});
163 })();
164 
165 /**
166  * Moves the UI focus to the element following this element in the tabindex
167  * order.
168  * @example
169  * var element = CKEDITOR.document.getById( 'example' );
170  * element.focusNext();
171  */
172 CKEDITOR.dom.element.prototype.focusNext = function( ignoreChildren, indexToUse )
173 {
174 	var $ = this.$,
175 		curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ),
176 		passedCurrent, enteredCurrent,
177 		elected, electedTabIndex,
178 		element, elementTabIndex;
179 
180 	if ( curTabIndex <= 0 )
181 	{
182 		// If this element has tabindex <= 0 then we must simply look for any
183 		// element following it containing tabindex=0.
184 
185 		element = this.getNextSourceNode( ignoreChildren, CKEDITOR.NODE_ELEMENT );
186 
187 		while ( element )
188 		{
189 			if ( element.isVisible() && element.getTabIndex() === 0 )
190 			{
191 				elected = element;
192 				break;
193 			}
194 
195 			element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT );
196 		}
197 	}
198 	else
199 	{
200 		// If this element has tabindex > 0 then we must look for:
201 		//		1. An element following this element with the same tabindex.
202 		//		2. The first element in source other with the lowest tabindex
203 		//		   that is higher than this element tabindex.
204 		//		3. The first element with tabindex=0.
205 
206 		element = this.getDocument().getBody().getFirst();
207 
208 		while ( ( element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
209 		{
210 			if ( !passedCurrent )
211 			{
212 				if ( !enteredCurrent && element.equals( this ) )
213 				{
214 					enteredCurrent = true;
215 
216 					// Ignore this element, if required.
217 					if ( ignoreChildren )
218 					{
219 						if ( !( element = element.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
220 							break;
221 						passedCurrent = 1;
222 					}
223 				}
224 				else if ( enteredCurrent && !this.contains( element ) )
225 					passedCurrent = 1;
226 			}
227 
228 			if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
229 				continue;
230 
231 			if ( passedCurrent && elementTabIndex == curTabIndex )
232 			{
233 				elected = element;
234 				break;
235 			}
236 
237 			if ( elementTabIndex > curTabIndex && ( !elected || !electedTabIndex || elementTabIndex < electedTabIndex ) )
238 			{
239 				elected = element;
240 				electedTabIndex = elementTabIndex;
241 			}
242 			else if ( !elected && elementTabIndex === 0 )
243 			{
244 				elected = element;
245 				electedTabIndex = elementTabIndex;
246 			}
247 		}
248 	}
249 
250 	if ( elected )
251 		elected.focus();
252 };
253 
254 /**
255  * Moves the UI focus to the element before this element in the tabindex order.
256  * @example
257  * var element = CKEDITOR.document.getById( 'example' );
258  * element.focusPrevious();
259  */
260 CKEDITOR.dom.element.prototype.focusPrevious = function( ignoreChildren, indexToUse )
261 {
262 	var $ = this.$,
263 		curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ),
264 		passedCurrent, enteredCurrent,
265 		elected,
266 		electedTabIndex = 0,
267 		elementTabIndex;
268 
269 	var element = this.getDocument().getBody().getLast();
270 
271 	while ( ( element = element.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
272 	{
273 		if ( !passedCurrent )
274 		{
275 			if ( !enteredCurrent && element.equals( this ) )
276 			{
277 				enteredCurrent = true;
278 
279 				// Ignore this element, if required.
280 				if ( ignoreChildren )
281 				{
282 					if ( !( element = element.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
283 						break;
284 					passedCurrent = 1;
285 				}
286 			}
287 			else if ( enteredCurrent && !this.contains( element ) )
288 				passedCurrent = 1;
289 		}
290 
291 		if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
292 			continue;
293 
294 		if ( curTabIndex <= 0 )
295 		{
296 			// If this element has tabindex <= 0 then we must look for:
297 			//		1. An element before this one containing tabindex=0.
298 			//		2. The last element with the highest tabindex.
299 
300 			if ( passedCurrent && elementTabIndex === 0 )
301 			{
302 				elected = element;
303 				break;
304 			}
305 
306 			if ( elementTabIndex > electedTabIndex )
307 			{
308 				elected = element;
309 				electedTabIndex = elementTabIndex;
310 			}
311 		}
312 		else
313 		{
314 			// If this element has tabindex > 0 we must look for:
315 			//		1. An element preceeding this one, with the same tabindex.
316 			//		2. The last element in source other with the highest tabindex
317 			//		   that is lower than this element tabindex.
318 
319 			if ( passedCurrent && elementTabIndex == curTabIndex )
320 			{
321 				elected = element;
322 				break;
323 			}
324 
325 			if ( elementTabIndex < curTabIndex && ( !elected || elementTabIndex > electedTabIndex ) )
326 			{
327 				elected = element;
328 				electedTabIndex = elementTabIndex;
329 			}
330 		}
331 	}
332 
333 	if ( elected )
334 		elected.focus();
335 };
336 
337 /**
338  * Intructs the editor to add a number of spaces (&nbsp;) to the text when
339  * hitting the TAB key. If set to zero, the TAB key will be used to move the
340  * cursor focus to the next element in the page, out of the editor focus.
341  * @name CKEDITOR.config.tabSpaces
342  * @type Number
343  * @default 0
344  * @example
345  * config.tabSpaces = 4;
346  */
347 
348 /**
349  * Allow context-sensitive tab key behaviors, including the following scenarios:
350  * <h5>When selection is anchored inside <b>table cells</b>:</h5>
351  * <ul>
352  * 		<li>If TAB is pressed, select the contents of the "next" cell. If in the last cell in the table, add a new row to it and focus its first cell.</li>
353  * 		<li>If SHIFT+TAB is pressed, select the contents of the "previous" cell. Do nothing when it's in the first cell.</li>
354  * </ul>
355  * @name CKEDITOR.config.enableTabKeyTools
356  * @type Boolean
357  * @default true
358  * @example
359  * config.enableTabKeyTools = false;
360  */
361 
362 // If the TAB key is not supposed to be enabled for navigation, the following
363 // settings could be used alternatively:
364 // config.keystrokes.push(
365 //	[ CKEDITOR.ALT + 38 /*Arrow Up*/, 'selectPreviousCell' ],
366 //	[ CKEDITOR.ALT + 40 /*Arrow Down*/, 'selectNextCell' ]
367 // );
368