1 /* 2 * CKEditor - The text editor for Internet - http://ckeditor.com 3 * Copyright (C) 2003-2008 Frederico Caldeira Knabben 4 * 5 * == BEGIN LICENSE == 6 * 7 * Licensed under the terms of any of the following licenses at your 8 * choice: 9 * 10 * - GNU General Public License Version 2 or later (the "GPL") 11 * http://www.gnu.org/licenses/gpl.html 12 * 13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL") 14 * http://www.gnu.org/licenses/lgpl.html 15 * 16 * - Mozilla Public License Version 1.1 or later (the "MPL") 17 * http://www.mozilla.org/MPL/MPL-1.1.html 18 * 19 * == END LICENSE == 20 */ 21 22 (function() 23 { 24 // #### checkSelectionChange : START 25 26 // The selection change check basically saves the element parent tree of 27 // the current node and check it on successive requests. If there is any 28 // change on the tree, then the selectionChange event gets fired. 29 var checkSelectionPreviousPath; 30 var checkSelectionChange = function() 31 { 32 // In IE, the "selectionchange" event may still get thrown when 33 // releasing the WYSIWYG mode, so we need to check it first. 34 var sel = this.getSelection(); 35 if ( !sel ) 36 return; 37 38 // Get the element at the start of the selection. 39 var node = this.getSelection().getStartElement(), 40 changed, 41 currentPath = [], 42 counter = 0; 43 44 // Loops through the parent tree of the main node. 45 while( node ) 46 { 47 // Look for changes in the parent node tree. 48 if ( !changed && ( !checkSelectionPreviousPath || !node.equals( checkSelectionPreviousPath[ counter++ ] ) ) ) 49 changed = true; 50 51 currentPath.push( node ); 52 node = node.getParent(); 53 } 54 55 checkSelectionPreviousPath = currentPath; 56 57 if ( changed ) 58 this.fire( 'selectionChange' ); 59 }; 60 61 var checkSelectionChangeTimer; 62 var checkSelectionChangeTimeoutPending; 63 var checkSelectionChangeTimeout = function() 64 { 65 // Firing the "OnSelectionChange" event on every key press started to 66 // be too slow. This function guarantees that there will be at least 67 // 200ms delay between selection checks. 68 69 checkSelectionChangeTimeoutPending = true; 70 71 if ( checkSelectionChangeTimer ) 72 return; 73 74 checkSelectionChangeTimeoutExec.call( this ); 75 76 checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this ); 77 }; 78 79 var checkSelectionChangeTimeoutExec = function() 80 { 81 checkSelectionChangeTimer = null; 82 83 if ( checkSelectionChangeTimeoutPending ) 84 { 85 // Call this with a timeout so the browser properly moves the 86 // selection after the mouseup. It happened that the selection was 87 // being moved after the mouseup when clicking inside selected text 88 // with Firefox. 89 CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this ); 90 91 checkSelectionChangeTimeoutPending = false; 92 } 93 }; 94 95 // #### checkSelectionChange : END 96 97 CKEDITOR.plugins.add( 'selection', 98 { 99 init : function( editor, pluginPath ) 100 { 101 editor.on( 'contentDom', function() 102 { 103 if ( CKEDITOR.env.ie ) 104 { 105 // IE is the only to provide the "selectionchange" 106 // event. 107 editor.document.on( 'selectionchange', checkSelectionChangeTimeout, editor ); 108 } 109 else 110 { 111 // In other browsers, we make the selection change 112 // check based on other events, like clicks or keys 113 // press. 114 115 editor.document.on( 'mouseup', checkSelectionChangeTimeout, editor ); 116 editor.document.on( 'keyup', checkSelectionChangeTimeout, editor ); 117 } 118 }); 119 } 120 }); 121 })(); 122 123 /** 124 * Gets the current selection from the editing area when in WYSIWYG mode. 125 * @returns {CKEDITOR.dom.selection} A selection object or null if not on 126 * WYSIWYG mode or no selection is available. 127 * @example 128 * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>; 129 * alert( selection.getType() ); 130 */ 131 CKEDITOR.editor.prototype.getSelection = function() 132 { 133 return this.document ? this.document.getSelection() : null; 134 }; 135 136 /** 137 * Gets the current selection from the document. 138 * @returns {CKEDITOR.dom.selection} A selection object. 139 * @example 140 * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>; 141 * alert( selection.getType() ); 142 */ 143 CKEDITOR.dom.document.prototype.getSelection = function() 144 { 145 return new CKEDITOR.dom.selection( this ); 146 }; 147 148 /** 149 * No selection. 150 * @constant 151 * @example 152 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE ) 153 * alert( 'Nothing is selected' ); 154 */ 155 CKEDITOR.SELECTION_NONE = 1; 156 157 /** 158 * Text or collapsed selection. 159 * @constant 160 * @example 161 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT ) 162 * alert( 'Text is selected' ); 163 */ 164 CKEDITOR.SELECTION_TEXT = 2; 165 166 /** 167 * Element selection. 168 * @constant 169 * @example 170 * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT ) 171 * alert( 'An element is selected' ); 172 */ 173 CKEDITOR.SELECTION_ELEMENT = 3; 174 175 /** 176 * Manipulates the selection in a DOM document. 177 * @constructor 178 * @example 179 */ 180 CKEDITOR.dom.selection = function( document ) 181 { 182 this.document = document; 183 }; 184 185 (function() 186 { 187 var styleObjectElements = { img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1 }; 188 189 CKEDITOR.dom.selection.prototype = 190 { 191 /** 192 * Gets the native selection object from the browser. 193 * @returns {Object} The native selection object. 194 * @example 195 * var selection = editor.getSelection().<b>getNative()</b>; 196 */ 197 getNative : 198 CKEDITOR.env.ie ? 199 function() 200 { 201 return this.document.$.selection; 202 } 203 : 204 function() 205 { 206 return this.document.getWindow().$.getSelection(); 207 }, 208 209 /** 210 * Gets the type of the current selection. The following values are 211 * available: 212 * <ul> 213 * <li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li> 214 * <li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or 215 * collapsed selection.</li> 216 * <li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element 217 * selection.</li> 218 * </ul> 219 * @returns {Number} One of the following constant values: 220 * {@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or 221 * {@link CKEDITOR.SELECTION_ELEMENT}. 222 * @example 223 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT ) 224 * alert( 'Text is selected' ); 225 */ 226 getType : 227 CKEDITOR.env.ie ? 228 function() 229 { 230 try 231 { 232 var sel = this.getNative(), 233 ieType = sel.type; 234 235 if ( ieType == 'Text' ) 236 return CKEDITOR.SELECTION_TEXT; 237 238 if ( ieType == 'Control' ) 239 return CKEDITOR.SELECTION_ELEMENT; 240 241 // It is possible that we can still get a text range 242 // object even when type == 'None' is returned by IE. 243 // So we'd better check the object returned by 244 // createRange() rather than by looking at the type. 245 if ( sel.createRange().parentElement ) 246 return CKEDITOR.SELECTION_TEXT; 247 } 248 catch(e) {} 249 250 return CKEDITOR.SELECTION_NONE; 251 } 252 : 253 function() 254 { 255 var sel = this.getNative(); 256 if ( !sel ) 257 return CKEDITOR.SELECTION_NONE; 258 259 if ( sel.rangeCount == 1 ) 260 { 261 // Check if the actual selection is a control (IMG, 262 // TABLE, HR, etc...). 263 264 var range = sel.getRangeAt(0), 265 startContainer = range.startContainer; 266 267 if ( startContainer == range.endContainer 268 && startContainer.nodeType == 1 269 && ( range.endOffset - range.startOffset ) == 1 270 && styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] ) 271 { 272 return CKEDITOR.SELECTION_ELEMENT; 273 } 274 } 275 276 return CKEDITOR.SELECTION_TEXT; 277 }, 278 279 /** 280 * Gets the DOM element in which the selection starts. 281 * @returns {CKEDITOR.dom.element} The element at the beginning of the 282 * selection. 283 * @example 284 * var element = editor.getSelection().<b>getStartElement()</b>; 285 * alert( element.getName() ); 286 */ 287 getStartElement : function() 288 { 289 var node, 290 sel = this.getNative(); 291 292 switch ( this.getType() ) 293 { 294 case CKEDITOR.SELECTION_ELEMENT : 295 return this.getSelectedElement(); 296 297 case CKEDITOR.SELECTION_TEXT : 298 299 if ( CKEDITOR.env.ie ) 300 { 301 var range = sel.createRange(); 302 range.collapse( true ); 303 304 node = range.parentElement(); 305 } 306 else 307 { 308 node = sel.anchorNode; 309 310 if ( node.nodeType != 1 ) 311 node = node.parentNode; 312 } 313 } 314 315 return ( node ? new CKEDITOR.dom.element( node ) : null ); 316 }, 317 318 /** 319 * Gets the current selected element. 320 * @returns {CKEDITOR.dom.element} The selected element. Null if no 321 * selection is available or the selection type is not 322 * {@link CKEDITOR.SELECTION_ELEMENT}. 323 * @example 324 * var element = editor.getSelection().<b>getSelectedElement()</b>; 325 * alert( element.getName() ); 326 */ 327 getSelectedElement : function() 328 { 329 var node; 330 331 if ( this.getType() == CKEDITOR.SELECTION_ELEMENT ) 332 { 333 var sel = this.getNative(); 334 335 if ( CKEDITOR.env.ie ) 336 { 337 try 338 { 339 node = sel.createRange().item(0); 340 } 341 catch(e) {} 342 } 343 else 344 { 345 var range = sel.getRangeAt( 0 ); 346 node = range.startContainer.childNodes[ range.startOffset ]; 347 } 348 } 349 350 return ( node ? new CKEDITOR.dom.element( node ) : null ); 351 }, 352 353 selectElement : 354 CKEDITOR.env.ie ? 355 function( element ) 356 { 357 this.getNative().empty() ; 358 359 var range ; 360 try 361 { 362 // Try to select the node as a control. 363 range = this.document.$.body.createControlRange() ; 364 range.addElement( element.$ ) ; 365 } 366 catch(e) 367 { 368 // If failed, select it as a text range. 369 range = this.document.$.body.createTextRange() ; 370 range.moveToElementText( element.$ ) ; 371 } 372 373 range.select() ; 374 } 375 : 376 function( element ) 377 { 378 // Create the range for the element. 379 var range = this.document.$.createRange() ; 380 range.selectNode( element.$ ) ; 381 382 // Select the range. 383 var sel = this.getNative() ; 384 sel.removeAllRanges() ; 385 sel.addRange( range ) ; 386 } 387 }; 388 })(); 389