1/* 2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net 3 * Copyright (C) 2003-2007 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 * Utility functions to work with the DOM. 22 */ 23 24var FCKDomTools = 25{ 26 MoveChildren : function( source, target ) 27 { 28 if ( source == target ) 29 return ; 30 31 var eChild ; 32 while ( (eChild = source.firstChild) ) 33 target.appendChild( source.removeChild( eChild ) ) ; 34 }, 35 36 // Remove blank spaces from the beginning and the end of the contents of a node. 37 TrimNode : function( node, ignoreEndBRs ) 38 { 39 this.LTrimNode( node ) ; 40 this.RTrimNode( node, ignoreEndBRs ) ; 41 }, 42 43 LTrimNode : function( node ) 44 { 45 var eChildNode ; 46 47 while ( (eChildNode = node.firstChild) ) 48 { 49 if ( eChildNode.nodeType == 3 ) 50 { 51 var sTrimmed = eChildNode.nodeValue.LTrim() ; 52 var iOriginalLength = eChildNode.nodeValue.length ; 53 54 if ( sTrimmed.length == 0 ) 55 { 56 node.removeChild( eChildNode ) ; 57 continue ; 58 } 59 else if ( sTrimmed.length < iOriginalLength ) 60 { 61 eChildNode.splitText( iOriginalLength - sTrimmed.length ) ; 62 node.removeChild( node.firstChild ) ; 63 } 64 } 65 break ; 66 } 67 }, 68 69 RTrimNode : function( node, ignoreEndBRs ) 70 { 71 var eChildNode ; 72 73 while ( (eChildNode = node.lastChild) ) 74 { 75 switch ( eChildNode.nodeType ) 76 { 77 case 1 : 78 if ( eChildNode.nodeName.toUpperCase() == 'BR' && ( ignoreEndBRs || eChildNode.getAttribute( 'type', 2 ) == '_moz' ) ) 79 { 80 node.removeChild( eChildNode ) ; 81 continue ; 82 } 83 break ; 84 85 case 3 : 86 var sTrimmed = eChildNode.nodeValue.RTrim() ; 87 var iOriginalLength = eChildNode.nodeValue.length ; 88 89 if ( sTrimmed.length == 0 ) 90 { 91 // If the trimmed text node is empty, just remove it. 92 93 // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#81). 94 eChildNode.parentNode.removeChild( eChildNode ) ; 95 continue ; 96 } 97 else if ( sTrimmed.length < iOriginalLength ) 98 { 99 // If the trimmed text lenght is less than the original 100 // lenght, strip all spaces from the end by splitting 101 // the text and removing the resulting useless node. 102 103 eChildNode.splitText( sTrimmed.length ) ; 104 // Use "node.lastChild.parentNode" instead of "node" to avoid IE bug (#81). 105 node.lastChild.parentNode.removeChild( node.lastChild ) ; 106 } 107 } 108 break ; 109 } 110 }, 111 112 RemoveNode : function( node, excludeChildren ) 113 { 114 if ( excludeChildren ) 115 { 116 // Move all children before the node. 117 var eChild ; 118 while ( (eChild = node.firstChild) ) 119 node.parentNode.insertBefore( node.removeChild( eChild ), node ) ; 120 } 121 122 return node.parentNode.removeChild( node ) ; 123 }, 124 125 GetFirstChild : function( node, childNames ) 126 { 127 // If childNames is a string, transform it in a Array. 128 if ( typeof ( childNames ) == 'string' ) 129 childNames = [ childNames ] ; 130 131 var eChild = node.firstChild ; 132 while( eChild ) 133 { 134 if ( eChild.nodeType == 1 && eChild.tagName.Equals.apply( eChild.tagName, childNames ) ) 135 return eChild ; 136 137 eChild = eChild.nextSibling ; 138 } 139 140 return null ; 141 }, 142 143 GetLastChild : function( node, childNames ) 144 { 145 // If childNames is a string, transform it in a Array. 146 if ( typeof ( childNames ) == 'string' ) 147 childNames = [ childNames ] ; 148 149 var eChild = node.lastChild ; 150 while( eChild ) 151 { 152 if ( eChild.nodeType == 1 && ( !childNames || eChild.tagName.Equals( childNames ) ) ) 153 return eChild ; 154 155 eChild = eChild.previousSibling ; 156 } 157 158 return null ; 159 }, 160 161 /* 162 * Gets the previous element (nodeType=1) in the source order. Returns 163 * "null" If no element is found. 164 * @param {Object} currentNode The node to start searching from. 165 * @param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be 166 * handled. If set to "true", only white spaces text nodes 167 * will be ignored, while non white space text nodes will stop 168 * the search, returning null. If "false" or ommitted, all 169 * text nodes are ignored. 170 * @param {string[]} stopSearchElements An array of element names that 171 * will cause the search to stop when found, returning null. 172 * May be ommitted (or null). 173 * @param {string[]} ignoreElements An array of element names that 174 * must be ignored during the search. 175 */ 176 GetPreviousSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) 177 { 178 if ( !currentNode ) 179 return null ; 180 181 if ( stopSearchElements && currentNode.nodeType == 1 && currentNode.nodeName.IEquals( stopSearchElements ) ) 182 return null ; 183 184 if ( currentNode.previousSibling ) 185 currentNode = currentNode.previousSibling ; 186 else 187 return this.GetPreviousSourceElement( currentNode.parentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ; 188 189 while ( currentNode ) 190 { 191 if ( currentNode.nodeType == 1 ) 192 { 193 if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) ) 194 break ; 195 196 if ( !ignoreElements || !currentNode.nodeName.IEquals( ignoreElements ) ) 197 return currentNode ; 198 } 199 else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 ) 200 break ; 201 202 if ( currentNode.lastChild ) 203 currentNode = currentNode.lastChild ; 204 else 205 return this.GetPreviousSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ; 206 } 207 208 return null ; 209 }, 210 211 /* 212 * Gets the next element (nodeType=1) in the source order. Returns 213 * "null" If no element is found. 214 * @param {Object} currentNode The node to start searching from. 215 * @param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be 216 * handled. If set to "true", only white spaces text nodes 217 * will be ignored, while non white space text nodes will stop 218 * the search, returning null. If "false" or ommitted, all 219 * text nodes are ignored. 220 * @param {string[]} stopSearchElements An array of element names that 221 * will cause the search to stop when found, returning null. 222 * May be ommitted (or null). 223 * @param {string[]} ignoreElements An array of element names that 224 * must be ignored during the search. 225 */ 226 GetNextSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) 227 { 228 if ( !currentNode ) 229 return null ; 230 231 if ( currentNode.nextSibling ) 232 currentNode = currentNode.nextSibling ; 233 else 234 return this.GetNextSourceElement( currentNode.parentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ; 235 236 while ( currentNode ) 237 { 238 if ( currentNode.nodeType == 1 ) 239 { 240 if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) ) 241 break ; 242 243 if ( !ignoreElements || !currentNode.nodeName.IEquals( ignoreElements ) ) 244 return currentNode ; 245 } 246 else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 ) 247 break ; 248 249 if ( currentNode.firstChild ) 250 currentNode = currentNode.firstChild ; 251 else 252 return this.GetNextSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ; 253 } 254 255 return null ; 256 }, 257 258 // Inserts a element after a existing one. 259 InsertAfterNode : function( existingNode, newNode ) 260 { 261 return existingNode.parentNode.insertBefore( newNode, existingNode.nextSibling ) ; 262 }, 263 264 GetParents : function( node ) 265 { 266 var parents = new Array() ; 267 268 while ( node ) 269 { 270 parents.splice( 0, 0, node ) ; 271 node = node.parentNode ; 272 } 273 274 return parents ; 275 }, 276 277 GetIndexOf : function( node ) 278 { 279 var currentNode = node.parentNode ? node.parentNode.firstChild : null ; 280 var currentIndex = -1 ; 281 282 while ( currentNode ) 283 { 284 currentIndex++ ; 285 286 if ( currentNode == node ) 287 return currentIndex ; 288 289 currentNode = currentNode.nextSibling ; 290 } 291 292 return -1 ; 293 } 294} ;