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 /** 7 * @fileOverview Defines the {@link CKEDITOR.dom.node} class, which is the base 8 * class for classes that represent DOM nodes. 9 */ 10 11 /** 12 * Base class for classes representing DOM nodes. This constructor may return 13 * and instance of classes that inherits this class, like 14 * {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}. 15 * @augments CKEDITOR.dom.domObject 16 * @param {Object} domNode A native DOM node. 17 * @constructor 18 * @see CKEDITOR.dom.element 19 * @see CKEDITOR.dom.text 20 * @example 21 */ 22 CKEDITOR.dom.node = function( domNode ) 23 { 24 if ( domNode ) 25 { 26 switch ( domNode.nodeType ) 27 { 28 case CKEDITOR.NODE_ELEMENT : 29 return new CKEDITOR.dom.element( domNode ); 30 31 case CKEDITOR.NODE_TEXT : 32 return new CKEDITOR.dom.text( domNode ); 33 } 34 35 // Call the base constructor. 36 CKEDITOR.dom.domObject.call( this, domNode ); 37 } 38 39 return this; 40 }; 41 42 CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject(); 43 44 /** 45 * Element node type. 46 * @constant 47 * @example 48 */ 49 CKEDITOR.NODE_ELEMENT = 1; 50 51 /** 52 * Text node type. 53 * @constant 54 * @example 55 */ 56 CKEDITOR.NODE_TEXT = 3; 57 58 /** 59 * Comment node type. 60 * @constant 61 * @example 62 */ 63 CKEDITOR.NODE_COMMENT = 8; 64 65 CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11; 66 67 CKEDITOR.POSITION_IDENTICAL = 0; 68 CKEDITOR.POSITION_DISCONNECTED = 1; 69 CKEDITOR.POSITION_FOLLOWING = 2; 70 CKEDITOR.POSITION_PRECEDING = 4; 71 CKEDITOR.POSITION_IS_CONTAINED = 8; 72 CKEDITOR.POSITION_CONTAINS = 16; 73 74 CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, 75 /** @lends CKEDITOR.dom.node.prototype */ 76 { 77 /** 78 * Makes this node child of another element. 79 * @param {CKEDITOR.dom.element} element The target element to which append 80 * this node. 81 * @returns {CKEDITOR.dom.element} The target element. 82 * @example 83 * var p = new CKEDITOR.dom.element( 'p' ); 84 * var strong = new CKEDITOR.dom.element( 'strong' ); 85 * strong.appendTo( p ); 86 * 87 * // result: "<p><strong></strong></p>" 88 */ 89 appendTo : function( element, toStart ) 90 { 91 element.append( this, toStart ); 92 return element; 93 }, 94 95 clone : function( includeChildren ) 96 { 97 return new CKEDITOR.dom.node( this.$.cloneNode( includeChildren ) ); 98 }, 99 100 hasPrevious : function() 101 { 102 return !!this.$.previousSibling; 103 }, 104 105 hasNext : function() 106 { 107 return !!this.$.nextSibling; 108 }, 109 110 /** 111 * Inserts this element after a node. 112 * @param {CKEDITOR.dom.node} node The that will preceed this element. 113 * @returns {CKEDITOR.dom.node} The node preceeding this one after 114 * insertion. 115 * @example 116 * var em = new CKEDITOR.dom.element( 'em' ); 117 * var strong = new CKEDITOR.dom.element( 'strong' ); 118 * strong.insertAfter( em ); 119 * 120 * // result: "<em></em><strong></strong>" 121 */ 122 insertAfter : function( node ) 123 { 124 node.$.parentNode.insertBefore( this.$, node.$.nextSibling ); 125 return node; 126 }, 127 128 /** 129 * Inserts this element before a node. 130 * @param {CKEDITOR.dom.node} node The that will be after this element. 131 * @returns {CKEDITOR.dom.node} The node being inserted. 132 * @example 133 * var em = new CKEDITOR.dom.element( 'em' ); 134 * var strong = new CKEDITOR.dom.element( 'strong' ); 135 * strong.insertBefore( em ); 136 * 137 * // result: "<strong></strong><em></em>" 138 */ 139 insertBefore : function( node ) 140 { 141 node.$.parentNode.insertBefore( this.$, node.$ ); 142 return node; 143 }, 144 145 insertBeforeMe : function( node ) 146 { 147 this.$.parentNode.insertBefore( node.$, this.$ ); 148 return node; 149 }, 150 151 /** 152 * Gets a DOM tree descendant under the current node. 153 * @param {Array|Number} indices The child index or array of child indices under the node. 154 * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist. 155 * @example 156 * var strong = p.getChild(0); 157 */ 158 getChild : function( indices ) 159 { 160 var rawNode = this.$; 161 162 if ( !indices.slice ) 163 rawNode = rawNode.childNodes[ indices ]; 164 else 165 { 166 while ( indices.length > 0 && rawNode ) 167 rawNode = rawNode.childNodes[ indices.shift() ]; 168 } 169 170 return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; 171 }, 172 173 getChildCount : function() 174 { 175 return this.$.childNodes.length; 176 }, 177 178 /** 179 * Gets the document containing this element. 180 * @returns {CKEDITOR.dom.document} The document. 181 * @example 182 * var element = CKEDITOR.document.getById( 'example' ); 183 * alert( <b>element.getDocument().equals( CKEDITOR.document )</b> ); // "true" 184 */ 185 getDocument : function() 186 { 187 var document = new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument ); 188 189 return ( 190 /** @ignore */ 191 this.getDocument = function() 192 { 193 return document; 194 })(); 195 }, 196 197 getIndex : function() 198 { 199 var $ = this.$; 200 201 var currentNode = $.parentNode && $.parentNode.firstChild; 202 var currentIndex = -1; 203 204 while ( currentNode ) 205 { 206 currentIndex++; 207 208 if ( currentNode == $ ) 209 return currentIndex; 210 211 currentNode = currentNode.nextSibling; 212 } 213 214 return -1; 215 }, 216 217 /** 218 * Gets the node following this node (next sibling). 219 * @returns {CKEDITOR.dom.node} The next node. 220 */ 221 getNext : function() 222 { 223 var next = this.$.nextSibling; 224 return next ? new CKEDITOR.dom.node( next ) : null; 225 }, 226 227 getNextSourceNode : function( startFromSibling, nodeType ) 228 { 229 var $ = this.$; 230 231 var node = ( !startFromSibling && $.firstChild ) ? 232 $.firstChild : 233 $.nextSibling; 234 235 var parent; 236 237 while ( !node && ( parent = ( parent || $ ).parentNode ) ) 238 node = parent.nextSibling; 239 240 if ( !node ) 241 return null; 242 243 if ( nodeType && nodeType != node.nodeType ) 244 return arguments.callee.call( { $ : node }, false, nodeType ); 245 246 return new CKEDITOR.dom.node( node ); 247 }, 248 249 getPreviousSourceNode : function( startFromSibling, nodeType ) 250 { 251 var $ = startFromSibling ? this.$.previousSibling : this.$, 252 node = null; 253 254 if ( !$ ) 255 return null; 256 257 if ( ( node = $.previousSibling ) ) 258 { 259 while ( node.lastChild ) 260 node = node.lastChild; 261 } 262 else 263 node = $.parentNode; 264 265 if ( !node ) 266 return null; 267 268 if ( nodeType && node.nodeType != nodeType ) 269 return arguments.callee.apply( { $ : node }, false, nodeType ); 270 271 return new CKEDITOR.dom.node( node ); 272 }, 273 274 getPrevious : function() 275 { 276 var previous = this.$.previousSibling; 277 return previous ? new CKEDITOR.dom.node( previous ) : null; 278 }, 279 280 /** 281 * Gets the parent element for this node. 282 * @returns {CKEDITOR.dom.element} The parent element. 283 * @example 284 * var node = editor.document.getBody().getFirst(); 285 * var parent = node.<b>getParent()</b>; 286 * alert( node.getName() ); // "body" 287 */ 288 getParent : function() 289 { 290 var parent = this.$.parentNode; 291 return ( parent && parent.nodeType == 1 ) ? new CKEDITOR.dom.node( parent ) : null; 292 }, 293 294 getParents : function() 295 { 296 var node = this; 297 var parents = []; 298 299 do 300 { 301 parents.unshift( node ); 302 } 303 while ( ( node = node.getParent() ) ) 304 305 return parents; 306 }, 307 308 getPosition : function( otherNode ) 309 { 310 var $ = this.$; 311 var $other = otherNode.$; 312 313 if ( $.compareDocumentPosition ) 314 return $.compareDocumentPosition( $other ); 315 316 // IE and Safari have no support for compareDocumentPosition. 317 318 if ( $ == $other ) 319 return CKEDITOR.POSITION_IDENTICAL; 320 321 // Handle non element nodes (don't support contains nor sourceIndex). 322 if ( this.type != CKEDITOR.NODE_ELEMENT || otherNode.type != CKEDITOR.NODE_ELEMENT ) 323 { 324 if ( $.parentNode == $other ) 325 return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; 326 else if ( $other.parentNode == $ ) 327 return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING; 328 else if ( $.parentNode == $other.parentNode ) 329 return this.getIndex() < otherNode.getIndex() ? CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING; 330 else 331 { 332 $ = $.parentNode; 333 $other = $other.parentNode; 334 } 335 } 336 337 if ( $.contains( $other ) ) 338 return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING; 339 340 if ( $other.contains( $ ) ) 341 return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; 342 343 if ( 'sourceIndex' in $ ) 344 { 345 return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED : 346 ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING : 347 CKEDITOR.POSITION_FOLLOWING; 348 } 349 350 // WebKit has no support for sourceIndex. 351 352 var doc = this.getDocument().$; 353 354 var range1 = doc.createRange(); 355 var range2 = doc.createRange(); 356 357 range1.selectNode( $ ); 358 range2.selectNode( $other ); 359 360 return range1.compareBoundaryPoints( 1, range2 ) > 0 ? 361 CKEDITOR.POSITION_FOLLOWING : 362 CKEDITOR.POSITION_PRECEDING; 363 }, 364 365 /** 366 * Gets the closes ancestor node of a specified node name. 367 * @param {String} name Node name of ancestor node. 368 * @param {Boolean} includeSelf (Optional) Whether to include the current 369 * node in the calculation or not. 370 * @returns {CKEDITOR.dom.node} Ancestor node. 371 */ 372 getAscendant : function( name, includeSelf ) 373 { 374 var node = this.$; 375 if ( includeSelf && node.nodeName.toLowerCase() == name ) 376 return this; 377 while ( ( node = node.parentNode ) ) 378 { 379 if ( node.nodeName && node.nodeName.toLowerCase() == name ) 380 return new CKEDITOR.dom.node( node ); 381 } 382 return null; 383 }, 384 385 move : function( target, toStart ) 386 { 387 target.append( this.remove(), toStart ); 388 }, 389 390 /** 391 * Removes this node from the document DOM. 392 * @param {Boolean} [preserveChildren] Indicates that the children 393 * elements must remain in the document, removing only the outer 394 * tags. 395 * @example 396 * var element = CKEDITOR.dom.element.getById( 'MyElement' ); 397 * <b>element.remove()</b>; 398 */ 399 remove : function( preserveChildren ) 400 { 401 var $ = this.$; 402 var parent = $.parentNode; 403 404 if ( parent ) 405 { 406 if ( preserveChildren ) 407 { 408 // Move all children before the node. 409 for ( var child ; ( child = $.firstChild ) ; ) 410 { 411 parent.insertBefore( $.removeChild( child ), $ ); 412 } 413 } 414 415 parent.removeChild( $ ); 416 } 417 418 return this; 419 } 420 } 421 ); 422