/** * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dom.node} class which is the base * class for classes that represent DOM nodes. */ /** * Base class for classes representing DOM nodes. This constructor may return * an instance of a class that inherits from this class, like * {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}. * * @class * @extends CKEDITOR.dom.domObject * @constructor Creates a node class instance. * @param {Object} domNode A native DOM node. * @see CKEDITOR.dom.element * @see CKEDITOR.dom.text */ CKEDITOR.dom.node = function( domNode ) { if ( domNode ) { var type = domNode.nodeType == CKEDITOR.NODE_DOCUMENT ? 'document' : domNode.nodeType == CKEDITOR.NODE_ELEMENT ? 'element' : domNode.nodeType == CKEDITOR.NODE_TEXT ? 'text' : domNode.nodeType == CKEDITOR.NODE_COMMENT ? 'comment' : domNode.nodeType == CKEDITOR.NODE_DOCUMENT_FRAGMENT ? 'documentFragment' : 'domObject'; // Call the base constructor otherwise. return new CKEDITOR.dom[ type ]( domNode ); } return this; }; CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject(); /** * Element node type. * * @readonly * @property {Number} [=1] * @member CKEDITOR */ CKEDITOR.NODE_ELEMENT = 1; /** * Document node type. * * @readonly * @property {Number} [=9] * @member CKEDITOR */ CKEDITOR.NODE_DOCUMENT = 9; /** * Text node type. * * @readonly * @property {Number} [=3] * @member CKEDITOR */ CKEDITOR.NODE_TEXT = 3; /** * Comment node type. * * @readonly * @property {Number} [=8] * @member CKEDITOR */ CKEDITOR.NODE_COMMENT = 8; /** * Document fragment node type. * * @readonly * @property {Number} [=11] * @member CKEDITOR */ CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11; CKEDITOR.POSITION_IDENTICAL = 0; CKEDITOR.POSITION_DISCONNECTED = 1; CKEDITOR.POSITION_FOLLOWING = 2; CKEDITOR.POSITION_PRECEDING = 4; CKEDITOR.POSITION_IS_CONTAINED = 8; CKEDITOR.POSITION_CONTAINS = 16; CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, { /** * Makes this node a child of another element. * * var p = new CKEDITOR.dom.element( 'p' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.appendTo( p ); * * // Result: '
'. * * @param {CKEDITOR.dom.element} element The target element to which this node will be appended. * @returns {CKEDITOR.dom.element} The target element. */ appendTo: function( element, toStart ) { element.append( this, toStart ); return element; }, /** * Clone this node. * * **Note**: Values set by {#setCustomData} won't be available in the clone. * * @param {Boolean} [includeChildren=false] If `true` then all node's * children will be cloned recursively. * @param {Boolean} [cloneId=false] Whether ID attributes should be cloned too. * @returns {CKEDITOR.dom.node} Clone of this node. */ clone: function( includeChildren, cloneId ) { var $clone = this.$.cloneNode( includeChildren ); var removeIds = function( node ) { // Reset data-cke-expando only when has been cloned (IE and only for some types of objects). if ( node[ 'data-cke-expando' ] ) node[ 'data-cke-expando' ] = false; if ( node.nodeType != CKEDITOR.NODE_ELEMENT ) return; if ( !cloneId ) node.removeAttribute( 'id', false ); if ( includeChildren ) { var childs = node.childNodes; for ( var i = 0; i < childs.length; i++ ) removeIds( childs[ i ] ); } }; // The "id" attribute should never be cloned to avoid duplication. removeIds( $clone ); return new CKEDITOR.dom.node( $clone ); }, /** * Check if node is preceded by any sibling. * * @returns {Boolean} */ hasPrevious: function() { return !!this.$.previousSibling; }, /** * Check if node is succeeded by any sibling. * * @returns {Boolean} */ hasNext: function() { return !!this.$.nextSibling; }, /** * Inserts this element after a node. * * var em = new CKEDITOR.dom.element( 'em' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.insertAfter( em ); * * // Result: '' * * @param {CKEDITOR.dom.node} node The node that will precede this element. * @returns {CKEDITOR.dom.node} The node preceding this one after insertion. */ insertAfter: function( node ) { node.$.parentNode.insertBefore( this.$, node.$.nextSibling ); return node; }, /** * Inserts this element before a node. * * var em = new CKEDITOR.dom.element( 'em' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.insertBefore( em ); * * // result: '' * * @param {CKEDITOR.dom.node} node The node that will succeed this element. * @returns {CKEDITOR.dom.node} The node being inserted. */ insertBefore: function( node ) { node.$.parentNode.insertBefore( this.$, node.$ ); return node; }, /** * Inserts node before this node. * * var em = new CKEDITOR.dom.element( 'em' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.insertBeforeMe( em ); * * // result: '' * * @param {CKEDITOR.dom.node} node The node that will preceed this element. * @returns {CKEDITOR.dom.node} The node being inserted. */ insertBeforeMe: function( node ) { this.$.parentNode.insertBefore( node.$, this.$ ); return node; }, /** * Retrieves a uniquely identifiable tree address for this node. * The tree address returned is an array of integers, with each integer * indicating a child index of a DOM node, starting from * `document.documentElement`. * * For example, assuming `` is the second child * of `` (`` being the first), * and we would like to address the third child under the * fourth child of ``, the tree address returned would be: * `[1, 3, 2]`. * * The tree address cannot be used for finding back the DOM tree node once * the DOM tree structure has been modified. * * @param {Boolean} [normalized=false] See {@link #getIndex}. * @returns {Array} The address. */ getAddress: function( normalized ) { var address = []; var $documentElement = this.getDocument().$.documentElement; var node = this.$; while ( node && node != $documentElement ) { var parentNode = node.parentNode; if ( parentNode ) { // Get the node index. For performance, call getIndex // directly, instead of creating a new node object. address.unshift( this.getIndex.call( { $: node }, normalized ) ); } node = parentNode; } return address; }, /** * Gets the document containing this element. * * var element = CKEDITOR.document.getById( 'example' ); * alert( element.getDocument().equals( CKEDITOR.document ) ); // true * * @returns {CKEDITOR.dom.document} The document. */ getDocument: function() { return new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument ); }, /** * Get index of a node in an array of its parent.childNodes. * * Let's assume having childNodes array: * * [ emptyText, element1, text, text, element2 ] * element1.getIndex(); // 1 * element1.getIndex( true ); // 0 * element2.getIndex(); // 4 * element2.getIndex( true ); // 2 * * @param {Boolean} normalized When `true` empty text nodes and one followed * by another one text node are not counted in. * @returns {Number} Index of a node. */ getIndex: function( normalized ) { // Attention: getAddress depends on this.$ // getIndex is called on a plain object: { $ : node } var current = this.$, index = -1, isNormalizing; if ( !this.$.parentNode ) return index; do { // Bypass blank node and adjacent text nodes. if ( normalized && current != this.$ && current.nodeType == CKEDITOR.NODE_TEXT && ( isNormalizing || !current.nodeValue ) ) continue; index++; isNormalizing = current.nodeType == CKEDITOR.NODE_TEXT; } while ( ( current = current.previousSibling ) ) return index; }, /** * @todo */ getNextSourceNode: function( startFromSibling, nodeType, guard ) { // If "guard" is a node, transform it in a function. if ( guard && !guard.call ) { var guardNode = guard; guard = function( node ) { return !node.equals( guardNode ); }; } var node = ( !startFromSibling && this.getFirst && this.getFirst() ), parent; // Guarding when we're skipping the current element( no children or 'startFromSibling' ). // send the 'moving out' signal even we don't actually dive into. if ( !node ) { if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) return null; node = this.getNext(); } while ( !node && ( parent = ( parent || this ).getParent() ) ) { // The guard check sends the "true" paramenter to indicate that // we are moving "out" of the element. if ( guard && guard( parent, true ) === false ) return null; node = parent.getNext(); } if ( !node ) return null; if ( guard && guard( node ) === false ) return null; if ( nodeType && nodeType != node.type ) return node.getNextSourceNode( false, nodeType, guard ); return node; }, /** * @todo */ getPreviousSourceNode: function( startFromSibling, nodeType, guard ) { if ( guard && !guard.call ) { var guardNode = guard; guard = function( node ) { return !node.equals( guardNode ); }; } var node = ( !startFromSibling && this.getLast && this.getLast() ), parent; // Guarding when we're skipping the current element( no children or 'startFromSibling' ). // send the 'moving out' signal even we don't actually dive into. if ( !node ) { if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) return null; node = this.getPrevious(); } while ( !node && ( parent = ( parent || this ).getParent() ) ) { // The guard check sends the "true" paramenter to indicate that // we are moving "out" of the element. if ( guard && guard( parent, true ) === false ) return null; node = parent.getPrevious(); } if ( !node ) return null; if ( guard && guard( node ) === false ) return null; if ( nodeType && node.type != nodeType ) return node.getPreviousSourceNode( false, nodeType, guard ); return node; }, /** * Gets the node that preceed this element in its parent's child list. * * var element = CKEDITOR.dom.element.createFromHtml( '
Some text