initial commit
[namibia] / public / scripts / ckeditor / _source / core / dom / node.js
1 /*
2 Copyright (c) 2003-2011, 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  * an instance of a class that inherits from 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                         // Safari don't consider document as element node type. (#3389)
29                         case CKEDITOR.NODE_DOCUMENT :
30                                 return new CKEDITOR.dom.document( domNode );
31
32                         case CKEDITOR.NODE_ELEMENT :
33                                 return new CKEDITOR.dom.element( domNode );
34
35                         case CKEDITOR.NODE_TEXT :
36                                 return new CKEDITOR.dom.text( domNode );
37                 }
38
39                 // Call the base constructor.
40                 CKEDITOR.dom.domObject.call( this, domNode );
41         }
42
43         return this;
44 };
45
46 CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject();
47
48 /**
49  * Element node type.
50  * @constant
51  * @example
52  */
53 CKEDITOR.NODE_ELEMENT = 1;
54
55 /**
56  * Document node type.
57  * @constant
58  * @example
59  */
60 CKEDITOR.NODE_DOCUMENT = 9;
61
62 /**
63  * Text node type.
64  * @constant
65  * @example
66  */
67 CKEDITOR.NODE_TEXT = 3;
68
69 /**
70  * Comment node type.
71  * @constant
72  * @example
73  */
74 CKEDITOR.NODE_COMMENT = 8;
75
76 CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11;
77
78 CKEDITOR.POSITION_IDENTICAL = 0;
79 CKEDITOR.POSITION_DISCONNECTED = 1;
80 CKEDITOR.POSITION_FOLLOWING = 2;
81 CKEDITOR.POSITION_PRECEDING = 4;
82 CKEDITOR.POSITION_IS_CONTAINED = 8;
83 CKEDITOR.POSITION_CONTAINS = 16;
84
85 CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype,
86         /** @lends CKEDITOR.dom.node.prototype */
87         {
88                 /**
89                  * Makes this node a child of another element.
90                  * @param {CKEDITOR.dom.element} element The target element to which
91                  *              this node will be appended.
92                  * @returns {CKEDITOR.dom.element} The target element.
93                  * @example
94                  * var p = new CKEDITOR.dom.element( 'p' );
95                  * var strong = new CKEDITOR.dom.element( 'strong' );
96                  * strong.appendTo( p );
97                  *
98                  * // result: "<p><strong></strong></p>"
99                  */
100                 appendTo : function( element, toStart )
101                 {
102                         element.append( this, toStart );
103                         return element;
104                 },
105
106                 clone : function( includeChildren, cloneId )
107                 {
108                         var $clone = this.$.cloneNode( includeChildren );
109
110                         var removeIds = function( node )
111                         {
112                                 if ( node.nodeType != CKEDITOR.NODE_ELEMENT )
113                                         return;
114
115                                 if ( !cloneId )
116                                         node.removeAttribute( 'id', false );
117                                 node.removeAttribute( 'data-cke-expando', false );
118
119                                 if ( includeChildren )
120                                 {
121                                         var childs = node.childNodes;
122                                         for ( var i=0; i < childs.length; i++ )
123                                                 removeIds( childs[ i ] );
124                                 }
125                         };
126
127                         // The "id" attribute should never be cloned to avoid duplication.
128                         removeIds( $clone );
129
130                         return new CKEDITOR.dom.node( $clone );
131                 },
132
133                 hasPrevious : function()
134                 {
135                         return !!this.$.previousSibling;
136                 },
137
138                 hasNext : function()
139                 {
140                         return !!this.$.nextSibling;
141                 },
142
143                 /**
144                  * Inserts this element after a node.
145                  * @param {CKEDITOR.dom.node} node The node that will precede this element.
146                  * @returns {CKEDITOR.dom.node} The node preceding this one after
147                  *              insertion.
148                  * @example
149                  * var em = new CKEDITOR.dom.element( 'em' );
150                  * var strong = new CKEDITOR.dom.element( 'strong' );
151                  * strong.insertAfter( em );
152                  *
153                  * // result: "&lt;em&gt;&lt;/em&gt;&lt;strong&gt;&lt;/strong&gt;"
154                  */
155                 insertAfter : function( node )
156                 {
157                         node.$.parentNode.insertBefore( this.$, node.$.nextSibling );
158                         return node;
159                 },
160
161                 /**
162                  * Inserts this element before a node.
163                  * @param {CKEDITOR.dom.node} node The node that will succeed this element.
164                  * @returns {CKEDITOR.dom.node} The node being inserted.
165                  * @example
166                  * var em = new CKEDITOR.dom.element( 'em' );
167                  * var strong = new CKEDITOR.dom.element( 'strong' );
168                  * strong.insertBefore( em );
169                  *
170                  * // result: "&lt;strong&gt;&lt;/strong&gt;&lt;em&gt;&lt;/em&gt;"
171                  */
172                 insertBefore : function( node )
173                 {
174                         node.$.parentNode.insertBefore( this.$, node.$ );
175                         return node;
176                 },
177
178                 insertBeforeMe : function( node )
179                 {
180                         this.$.parentNode.insertBefore( node.$, this.$ );
181                         return node;
182                 },
183
184                 /**
185                  * Retrieves a uniquely identifiable tree address for this node.
186                  * The tree address returned is an array of integers, with each integer
187                  * indicating a child index of a DOM node, starting from
188                  * <code>document.documentElement</code>.
189                  *
190                  * For example, assuming <code>&lt;body&gt;</code> is the second child
191                  * of <code>&lt;html&gt;</code> (<code>&lt;head&gt;</code> being the first),
192                  * and we would like to address the third child under the
193                  * fourth child of <code>&lt;body&gt;</code>, the tree address returned would be:
194                  * [1, 3, 2]
195                  *
196                  * The tree address cannot be used for finding back the DOM tree node once
197                  * the DOM tree structure has been modified.
198                  */
199                 getAddress : function( normalized )
200                 {
201                         var address = [];
202                         var $documentElement = this.getDocument().$.documentElement;
203                         var node = this.$;
204
205                         while ( node && node != $documentElement )
206                         {
207                                 var parentNode = node.parentNode;
208
209                                 if ( parentNode )
210                                 {
211                                         // Get the node index. For performance, call getIndex
212                                         // directly, instead of creating a new node object.
213                                         address.unshift( this.getIndex.call( { $ : node }, normalized ) );
214                                 }
215
216                                 node = parentNode;
217                         }
218
219                         return address;
220                 },
221
222                 /**
223                  * Gets the document containing this element.
224                  * @returns {CKEDITOR.dom.document} The document.
225                  * @example
226                  * var element = CKEDITOR.document.getById( 'example' );
227                  * alert( <strong>element.getDocument().equals( CKEDITOR.document )</strong> );  // "true"
228                  */
229                 getDocument : function()
230                 {
231                         return new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument );
232                 },
233
234                 getIndex : function( normalized )
235                 {
236                         // Attention: getAddress depends on this.$
237
238                         var current = this.$,
239                                 index = 0;
240
241                         while ( ( current = current.previousSibling ) )
242                         {
243                                 // When normalizing, do not count it if this is an
244                                 // empty text node or if it's a text node following another one.
245                                 if ( normalized && current.nodeType == 3 &&
246                                          ( !current.nodeValue.length ||
247                                            ( current.previousSibling && current.previousSibling.nodeType == 3 ) ) )
248                                 {
249                                         continue;
250                                 }
251
252                                 index++;
253                         }
254
255                         return index;
256                 },
257
258                 getNextSourceNode : function( startFromSibling, nodeType, guard )
259                 {
260                         // If "guard" is a node, transform it in a function.
261                         if ( guard && !guard.call )
262                         {
263                                 var guardNode = guard;
264                                 guard = function( node )
265                                 {
266                                         return !node.equals( guardNode );
267                                 };
268                         }
269
270                         var node = ( !startFromSibling && this.getFirst && this.getFirst() ),
271                                 parent;
272
273                         // Guarding when we're skipping the current element( no children or 'startFromSibling' ).
274                         // send the 'moving out' signal even we don't actually dive into.
275                         if ( !node )
276                         {
277                                 if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false )
278                                         return null;
279                                 node = this.getNext();
280                         }
281
282                         while ( !node && ( parent = ( parent || this ).getParent() ) )
283                         {
284                                 // The guard check sends the "true" paramenter to indicate that
285                                 // we are moving "out" of the element.
286                                 if ( guard && guard( parent, true ) === false )
287                                         return null;
288
289                                 node = parent.getNext();
290                         }
291
292                         if ( !node )
293                                 return null;
294
295                         if ( guard && guard( node ) === false )
296                                 return null;
297
298                         if ( nodeType && nodeType != node.type )
299                                 return node.getNextSourceNode( false, nodeType, guard );
300
301                         return node;
302                 },
303
304                 getPreviousSourceNode : function( startFromSibling, nodeType, guard )
305                 {
306                         if ( guard && !guard.call )
307                         {
308                                 var guardNode = guard;
309                                 guard = function( node )
310                                 {
311                                         return !node.equals( guardNode );
312                                 };
313                         }
314
315                         var node = ( !startFromSibling && this.getLast && this.getLast() ),
316                                 parent;
317
318                         // Guarding when we're skipping the current element( no children or 'startFromSibling' ).
319                         // send the 'moving out' signal even we don't actually dive into.
320                         if ( !node )
321                         {
322                                 if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false )
323                                         return null;
324                                 node = this.getPrevious();
325                         }
326
327                         while ( !node && ( parent = ( parent || this ).getParent() ) )
328                         {
329                                 // The guard check sends the "true" paramenter to indicate that
330                                 // we are moving "out" of the element.
331                                 if ( guard && guard( parent, true ) === false )
332                                         return null;
333
334                                 node = parent.getPrevious();
335                         }
336
337                         if ( !node )
338                                 return null;
339
340                         if ( guard && guard( node ) === false )
341                                 return null;
342
343                         if ( nodeType && node.type != nodeType )
344                                 return node.getPreviousSourceNode( false, nodeType, guard );
345
346                         return node;
347                 },
348
349                 getPrevious : function( evaluator )
350                 {
351                         var previous = this.$, retval;
352                         do
353                         {
354                                 previous = previous.previousSibling;
355                                 retval = previous && new CKEDITOR.dom.node( previous );
356                         }
357                         while ( retval && evaluator && !evaluator( retval ) )
358                         return retval;
359                 },
360
361                 /**
362                  * Gets the node that follows this element in its parent's child list.
363                  * @param {Function} evaluator Filtering the result node.
364                  * @returns {CKEDITOR.dom.node} The next node or null if not available.
365                  * @example
366                  * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;&lt;b&gt;Example&lt;/b&gt; &lt;i&gt;next&lt;/i&gt;&lt;/div&gt;' );
367                  * var first = <strong>element.getFirst().getNext()</strong>;
368                  * alert( first.getName() );  // "i"
369                  */
370                 getNext : function( evaluator )
371                 {
372                         var next = this.$, retval;
373                         do
374                         {
375                                 next = next.nextSibling;
376                                 retval = next && new CKEDITOR.dom.node( next );
377                         }
378                         while ( retval && evaluator && !evaluator( retval ) )
379                         return retval;
380                 },
381
382                 /**
383                  * Gets the parent element for this node.
384                  * @returns {CKEDITOR.dom.element} The parent element.
385                  * @example
386                  * var node = editor.document.getBody().getFirst();
387                  * var parent = node.<strong>getParent()</strong>;
388                  * alert( node.getName() );  // "body"
389                  */
390                 getParent : function()
391                 {
392                         var parent = this.$.parentNode;
393                         return ( parent && parent.nodeType == 1 ) ? new CKEDITOR.dom.node( parent ) : null;
394                 },
395
396                 getParents : function( closerFirst )
397                 {
398                         var node = this;
399                         var parents = [];
400
401                         do
402                         {
403                                 parents[  closerFirst ? 'push' : 'unshift' ]( node );
404                         }
405                         while ( ( node = node.getParent() ) )
406
407                         return parents;
408                 },
409
410                 getCommonAncestor : function( node )
411                 {
412                         if ( node.equals( this ) )
413                                 return this;
414
415                         if ( node.contains && node.contains( this ) )
416                                 return node;
417
418                         var start = this.contains ? this : this.getParent();
419
420                         do
421                         {
422                                 if ( start.contains( node ) )
423                                         return start;
424                         }
425                         while ( ( start = start.getParent() ) );
426
427                         return null;
428                 },
429
430                 getPosition : function( otherNode )
431                 {
432                         var $ = this.$;
433                         var $other = otherNode.$;
434
435                         if ( $.compareDocumentPosition )
436                                 return $.compareDocumentPosition( $other );
437
438                         // IE and Safari have no support for compareDocumentPosition.
439
440                         if ( $ == $other )
441                                 return CKEDITOR.POSITION_IDENTICAL;
442
443                         // Only element nodes support contains and sourceIndex.
444                         if ( this.type == CKEDITOR.NODE_ELEMENT && otherNode.type == CKEDITOR.NODE_ELEMENT )
445                         {
446                                 if ( $.contains )
447                                 {
448                                         if ( $.contains( $other ) )
449                                                 return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING;
450
451                                         if ( $other.contains( $ ) )
452                                                 return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING;
453                                 }
454
455                                 if ( 'sourceIndex' in $ )
456                                 {
457                                         return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED :
458                                                 ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING :
459                                                 CKEDITOR.POSITION_FOLLOWING;
460                                 }
461                         }
462
463                         // For nodes that don't support compareDocumentPosition, contains
464                         // or sourceIndex, their "address" is compared.
465
466                         var addressOfThis = this.getAddress(),
467                                 addressOfOther = otherNode.getAddress(),
468                                 minLevel = Math.min( addressOfThis.length, addressOfOther.length );
469
470                                 // Determinate preceed/follow relationship.
471                                 for ( var i = 0 ; i <= minLevel - 1 ; i++ )
472                                 {
473                                         if ( addressOfThis[ i ] != addressOfOther[ i ] )
474                                         {
475                                                 if ( i < minLevel )
476                                                 {
477                                                         return addressOfThis[ i ] < addressOfOther[ i ] ?
478                                                             CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING;
479                                                 }
480                                                 break;
481                                         }
482                                 }
483
484                                 // Determinate contains/contained relationship.
485                                 return ( addressOfThis.length < addressOfOther.length ) ?
486                                                         CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING :
487                                                         CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING;
488                 },
489
490                 /**
491                  * Gets the closest ancestor node of this node, specified by its name.
492                  * @param {String} reference The name of the ancestor node to search or
493                  *              an object with the node names to search for.
494                  * @param {Boolean} [includeSelf] Whether to include the current
495                  *              node in the search.
496                  * @returns {CKEDITOR.dom.node} The located ancestor node or null if not found.
497                  * @since 3.6.1
498                  * @example
499                  * // Suppose we have the following HTML structure:
500                  * // &lt;div id="outer"&gt;&lt;div id="inner"&gt;&lt;p&gt;&lt;b&gt;Some text&lt;/b&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
501                  * // If node == &lt;b&gt;
502                  * ascendant = node.getAscendant( 'div' );      // ascendant == &lt;div id="inner"&gt
503                  * ascendant = node.getAscendant( 'b' );        // ascendant == null
504                  * ascendant = node.getAscendant( 'b', true );  // ascendant == &lt;b&gt;
505                  * ascendant = node.getAscendant( { div: 1, p: 1} );      // Searches for the first 'div' or 'p': ascendant == &lt;div id="inner"&gt
506                  */
507                 getAscendant : function( reference, includeSelf )
508                 {
509                         var $ = this.$,
510                                 name;
511
512                         if ( !includeSelf )
513                                 $ = $.parentNode;
514
515                         while ( $ )
516                         {
517                                 if ( $.nodeName && ( name = $.nodeName.toLowerCase(), ( typeof reference == 'string' ? name == reference : name in reference ) ) )
518                                         return new CKEDITOR.dom.node( $ );
519
520                                 $ = $.parentNode;
521                         }
522                         return null;
523                 },
524
525                 hasAscendant : function( name, includeSelf )
526                 {
527                         var $ = this.$;
528
529                         if ( !includeSelf )
530                                 $ = $.parentNode;
531
532                         while ( $ )
533                         {
534                                 if ( $.nodeName && $.nodeName.toLowerCase() == name )
535                                         return true;
536
537                                 $ = $.parentNode;
538                         }
539                         return false;
540                 },
541
542                 move : function( target, toStart )
543                 {
544                         target.append( this.remove(), toStart );
545                 },
546
547                 /**
548                  * Removes this node from the document DOM.
549                  * @param {Boolean} [preserveChildren] Indicates that the children
550                  *              elements must remain in the document, removing only the outer
551                  *              tags.
552                  * @example
553                  * var element = CKEDITOR.dom.element.getById( 'MyElement' );
554                  * <strong>element.remove()</strong>;
555                  */
556                 remove : function( preserveChildren )
557                 {
558                         var $ = this.$;
559                         var parent = $.parentNode;
560
561                         if ( parent )
562                         {
563                                 if ( preserveChildren )
564                                 {
565                                         // Move all children before the node.
566                                         for ( var child ; ( child = $.firstChild ) ; )
567                                         {
568                                                 parent.insertBefore( $.removeChild( child ), $ );
569                                         }
570                                 }
571
572                                 parent.removeChild( $ );
573                         }
574
575                         return this;
576                 },
577
578                 replace : function( nodeToReplace )
579                 {
580                         this.insertBefore( nodeToReplace );
581                         nodeToReplace.remove();
582                 },
583
584                 trim : function()
585                 {
586                         this.ltrim();
587                         this.rtrim();
588                 },
589
590                 ltrim : function()
591                 {
592                         var child;
593                         while ( this.getFirst && ( child = this.getFirst() ) )
594                         {
595                                 if ( child.type == CKEDITOR.NODE_TEXT )
596                                 {
597                                         var trimmed = CKEDITOR.tools.ltrim( child.getText() ),
598                                                 originalLength = child.getLength();
599
600                                         if ( !trimmed )
601                                         {
602                                                 child.remove();
603                                                 continue;
604                                         }
605                                         else if ( trimmed.length < originalLength )
606                                         {
607                                                 child.split( originalLength - trimmed.length );
608
609                                                 // IE BUG: child.remove() may raise JavaScript errors here. (#81)
610                                                 this.$.removeChild( this.$.firstChild );
611                                         }
612                                 }
613                                 break;
614                         }
615                 },
616
617                 rtrim : function()
618                 {
619                         var child;
620                         while ( this.getLast && ( child = this.getLast() ) )
621                         {
622                                 if ( child.type == CKEDITOR.NODE_TEXT )
623                                 {
624                                         var trimmed = CKEDITOR.tools.rtrim( child.getText() ),
625                                                 originalLength = child.getLength();
626
627                                         if ( !trimmed )
628                                         {
629                                                 child.remove();
630                                                 continue;
631                                         }
632                                         else if ( trimmed.length < originalLength )
633                                         {
634                                                 child.split( trimmed.length );
635
636                                                 // IE BUG: child.getNext().remove() may raise JavaScript errors here.
637                                                 // (#81)
638                                                 this.$.lastChild.parentNode.removeChild( this.$.lastChild );
639                                         }
640                                 }
641                                 break;
642                         }
643
644                         if ( !CKEDITOR.env.ie && !CKEDITOR.env.opera )
645                         {
646                                 child = this.$.lastChild;
647
648                                 if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' )
649                                 {
650                                         // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324).
651                                         child.parentNode.removeChild( child ) ;
652                                 }
653                         }
654                 },
655
656                 /**
657                  * Checks if this node is read-only (should not be changed).
658                  * @returns {Boolean}
659                  * @since 3.5
660                  * @example
661                  * // For the following HTML:
662                  * // &lt;div contenteditable="false"&gt;Some &lt;b&gt;text&lt;/b&gt;&lt;/div&gt;
663                  *
664                  * // If "ele" is the above &lt;div&gt;
665                  * ele.isReadOnly();  // true
666                  */
667                 isReadOnly : function()
668                 {
669                         var element = this;
670                         if ( this.type != CKEDITOR.NODE_ELEMENT )
671                                 element = this.getParent();
672
673                         if ( element && typeof element.$.isContentEditable != 'undefined' )
674                                 return ! ( element.$.isContentEditable || element.data( 'cke-editable' ) );
675                         else
676                         {
677                                 // Degrade for old browsers which don't support "isContentEditable", e.g. FF3
678                                 var current = element;
679                                 while( current )
680                                 {
681                                         if ( current.is( 'body' ) || !!current.data( 'cke-editable' ) )
682                                                 break;
683
684                                         if ( current.getAttribute( 'contentEditable' ) == 'false' )
685                                                 return true;
686                                         else if ( current.getAttribute( 'contentEditable' ) == 'true' )
687                                                 break;
688
689                                         current = current.getParent();
690                                 }
691
692                                 return false;
693                         }
694                 }
695         }
696 );