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