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 ( ) 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