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. 22 */ 23 24var FCKTools = new Object() ; 25 26FCKTools.CreateBogusBR = function( targetDocument ) 27{ 28 var eBR = targetDocument.createElement( 'br' ) ; 29// eBR.setAttribute( '_moz_editor_bogus_node', 'TRUE' ) ; 30 eBR.setAttribute( 'type', '_moz' ) ; 31 return eBR ; 32} 33 34// Returns a reference to the appended style sheet or an array with all the appended references 35FCKTools.AppendStyleSheet = function( documentElement, cssFileUrlOrArray ) 36{ 37 if ( typeof( cssFileUrlOrArray ) == 'string' ) 38 return this._AppendStyleSheet( documentElement, cssFileUrlOrArray ) ; 39 else 40 { 41 var aStyleSheeArray = new Array() ; 42 43 for ( var i = 0 ; i < cssFileUrlOrArray.length ; i++ ) 44 aStyleSheeArray.push(this._AppendStyleSheet( documentElement, cssFileUrlOrArray[i] ) ) ; 45 46 return aStyleSheeArray ; 47 } 48} 49 50FCKTools.AppendStyleString = function ( documentElement, cssStyles ) 51{ 52 this._AppendStyleString( documentElement, cssStyles ) ; 53} 54 55FCKTools.GetElementDocument = function ( element ) 56{ 57 return element.ownerDocument || element.document ; 58} 59 60// Get the window object where the element is placed in. 61FCKTools.GetElementWindow = function( element ) 62{ 63 return this.GetDocumentWindow( this.GetElementDocument( element ) ) ; 64} 65 66FCKTools.GetDocumentWindow = function( document ) 67{ 68 // With Safari, there is not way to retrieve the window from the document, so we must fix it. 69 if ( FCKBrowserInfo.IsSafari && !document.parentWindow ) 70 this.FixDocumentParentWindow( window.top ) ; 71 72 return document.parentWindow || document.defaultView ; 73} 74 75/* 76 This is a Safari specific function that fix the reference to the parent 77 window from the document object. 78*/ 79FCKTools.FixDocumentParentWindow = function( targetWindow ) 80{ 81 targetWindow.document.parentWindow = targetWindow ; 82 83 for ( var i = 0 ; i < targetWindow.frames.length ; i++ ) 84 FCKTools.FixDocumentParentWindow( targetWindow.frames[i] ) ; 85} 86 87FCKTools.HTMLEncode = function( text ) 88{ 89 if ( !text ) 90 return '' ; 91 92 text = text.replace( /&/g, '&' ) ; 93 text = text.replace( /</g, '<' ) ; 94 text = text.replace( />/g, '>' ) ; 95 96 return text ; 97} 98 99FCKTools.HTMLDecode = function( text ) 100{ 101 if ( !text ) 102 return '' ; 103 104 text = text.replace( />/g, '>' ) ; 105 text = text.replace( /</g, '<' ) ; 106 text = text.replace( /&/g, '&' ) ; 107 108 return text ; 109} 110 111FCKTools._ProcessLineBreaksForPMode = function( oEditor, text, liState, node, strArray ) 112{ 113 var closeState = 0 ; 114 var blockStartTag = "<p>" ; 115 var blockEndTag = "</p>" ; 116 var lineBreakTag = "<br />" ; 117 if ( liState ) 118 { 119 blockStartTag = "<li>" ; 120 blockEndTag = "</li>" ; 121 closeState = 1 ; 122 } 123 124 // Are we currently inside a <p> tag now? 125 // If yes, close it at the next double line break. 126 while ( node && node != oEditor.FCK.EditorDocument.body ) 127 { 128 if ( node.tagName.toLowerCase() == 'p' ) 129 { 130 closeState = 1 ; 131 break; 132 } 133 node = node.parentNode ; 134 } 135 136 for ( var i = 0 ; i < text.length ; i++ ) 137 { 138 var c = text.charAt( i ) ; 139 if ( c == '\r' ) 140 continue ; 141 142 if ( c != '\n' ) 143 { 144 strArray.push( c ) ; 145 continue ; 146 } 147 148 // Now we have encountered a line break. 149 // Check if the next character is also a line break. 150 var n = text.charAt( i + 1 ) ; 151 if ( n == '\r' ) 152 { 153 i++ ; 154 n = text.charAt( i + 1 ) ; 155 } 156 if ( n == '\n' ) 157 { 158 i++ ; // ignore next character - we have already processed it. 159 if ( closeState ) 160 strArray.push( blockEndTag ) ; 161 strArray.push( blockStartTag ) ; 162 closeState = 1 ; 163 } 164 else 165 strArray.push( lineBreakTag ) ; 166 } 167} 168 169FCKTools._ProcessLineBreaksForDivMode = function( oEditor, text, liState, node, strArray ) 170{ 171 var closeState = 0 ; 172 var blockStartTag = "<div>" ; 173 var blockEndTag = "</div>" ; 174 if ( liState ) 175 { 176 blockStartTag = "<li>" ; 177 blockEndTag = "</li>" ; 178 closeState = 1 ; 179 } 180 181 // Are we currently inside a <div> tag now? 182 // If yes, close it at the next double line break. 183 while ( node && node != oEditor.FCK.EditorDocument.body ) 184 { 185 if ( node.tagName.toLowerCase() == 'div' ) 186 { 187 closeState = 1 ; 188 break ; 189 } 190 node = node.parentNode ; 191 } 192 193 for ( var i = 0 ; i < text.length ; i++ ) 194 { 195 var c = text.charAt( i ) ; 196 if ( c == '\r' ) 197 continue ; 198 199 if ( c != '\n' ) 200 { 201 strArray.push( c ) ; 202 continue ; 203 } 204 205 if ( closeState ) 206 { 207 if ( strArray[ strArray.length - 1 ] == blockStartTag ) 208 { 209 // A div tag must have some contents inside for it to be visible. 210 strArray.push( " " ) ; 211 } 212 strArray.push( blockEndTag ) ; 213 } 214 strArray.push( blockStartTag ) ; 215 closeState = 1 ; 216 } 217 if ( closeState ) 218 strArray.push( blockEndTag ) ; 219} 220 221FCKTools._ProcessLineBreaksForBrMode = function( oEditor, text, liState, node, strArray ) 222{ 223 var closeState = 0 ; 224 var blockStartTag = "<br />" ; 225 var blockEndTag = "" ; 226 if ( liState ) 227 { 228 blockStartTag = "<li>" ; 229 blockEndTag = "</li>" ; 230 closeState = 1 ; 231 } 232 233 for ( var i = 0 ; i < text.length ; i++ ) 234 { 235 var c = text.charAt( i ) ; 236 if ( c == '\r' ) 237 continue ; 238 239 if ( c != '\n' ) 240 { 241 strArray.push( c ) ; 242 continue ; 243 } 244 245 if ( closeState && blockEndTag.length ) 246 strArray.push ( blockEndTag ) ; 247 strArray.push( blockStartTag ) ; 248 closeState = 1 ; 249 } 250} 251 252FCKTools.ProcessLineBreaks = function( oEditor, oConfig, text ) 253{ 254 var enterMode = oConfig.EnterMode.toLowerCase() ; 255 var strArray = [] ; 256 257 // Is the caret or selection inside an <li> tag now? 258 var liState = 0 ; 259 var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ; 260 range.MoveToSelection() ; 261 var node = range._Range.startContainer ; 262 while ( node && node.nodeType != 1 ) 263 node = node.parentNode ; 264 if ( node && node.tagName.toLowerCase() == 'li' ) 265 liState = 1 ; 266 267 if ( enterMode == 'p' ) 268 this._ProcessLineBreaksForPMode( oEditor, text, liState, node, strArray ) ; 269 else if ( enterMode == 'div' ) 270 this._ProcessLineBreaksForDivMode( oEditor, text, liState, node, strArray ) ; 271 else if ( enterMode == 'br' ) 272 this._ProcessLineBreaksForBrMode( oEditor, text, liState, node, strArray ) ; 273 return strArray.join( "" ) ; 274} 275 276/** 277 * Adds an option to a SELECT element. 278 */ 279FCKTools.AddSelectOption = function( selectElement, optionText, optionValue ) 280{ 281 var oOption = FCKTools.GetElementDocument( selectElement ).createElement( "OPTION" ) ; 282 283 oOption.text = optionText ; 284 oOption.value = optionValue ; 285 286 selectElement.options.add(oOption) ; 287 288 return oOption ; 289} 290 291FCKTools.RunFunction = function( func, thisObject, paramsArray, timerWindow ) 292{ 293 if ( func ) 294 this.SetTimeout( func, 0, thisObject, paramsArray, timerWindow ) ; 295} 296 297FCKTools.SetTimeout = function( func, milliseconds, thisObject, paramsArray, timerWindow ) 298{ 299 return ( timerWindow || window ).setTimeout( 300 function() 301 { 302 if ( paramsArray ) 303 func.apply( thisObject, [].concat( paramsArray ) ) ; 304 else 305 func.apply( thisObject ) ; 306 }, 307 milliseconds ) ; 308} 309 310FCKTools.SetInterval = function( func, milliseconds, thisObject, paramsArray, timerWindow ) 311{ 312 return ( timerWindow || window ).setInterval( 313 function() 314 { 315 func.apply( thisObject, paramsArray || [] ) ; 316 }, 317 milliseconds ) ; 318} 319 320FCKTools.ConvertStyleSizeToHtml = function( size ) 321{ 322 return size.EndsWith( '%' ) ? size : parseInt( size, 10 ) ; 323} 324 325FCKTools.ConvertHtmlSizeToStyle = function( size ) 326{ 327 return size.EndsWith( '%' ) ? size : ( size + 'px' ) ; 328} 329 330// START iCM MODIFICATIONS 331// Amended to accept a list of one or more ascensor tag names 332// Amended to check the element itself before working back up through the parent hierarchy 333FCKTools.GetElementAscensor = function( element, ascensorTagNames ) 334{ 335// var e = element.parentNode ; 336 var e = element ; 337 var lstTags = "," + ascensorTagNames.toUpperCase() + "," ; 338 339 while ( e ) 340 { 341 if ( lstTags.indexOf( "," + e.nodeName.toUpperCase() + "," ) != -1 ) 342 return e ; 343 344 e = e.parentNode ; 345 } 346 return null ; 347} 348// END iCM MODIFICATIONS 349 350FCKTools.CreateEventListener = function( func, params ) 351{ 352 var f = function() 353 { 354 var aAllParams = [] ; 355 356 for ( var i = 0 ; i < arguments.length ; i++ ) 357 aAllParams.push( arguments[i] ) ; 358 359 func.apply( this, aAllParams.concat( params ) ) ; 360 } 361 362 return f ; 363} 364 365FCKTools.IsStrictMode = function( document ) 366{ 367 // There is no compatMode in Safari, but it seams that it always behave as 368 // CSS1Compat, so let's assume it as the default. 369 return ( 'CSS1Compat' == ( document.compatMode || 'CSS1Compat' ) ) ; 370} 371 372// Transforms a "arguments" object to an array. 373FCKTools.ArgumentsToArray = function( args, startIndex, maxLength ) 374{ 375 startIndex = startIndex || 0 ; 376 maxLength = maxLength || args.length ; 377 378 var argsArray = new Array() ; 379 380 for ( var i = startIndex ; i < startIndex + maxLength && i < args.length ; i++ ) 381 argsArray.push( args[i] ) ; 382 383 return argsArray ; 384} 385 386FCKTools.CloneObject = function( sourceObject ) 387{ 388 var fCloneCreator = function() {} ; 389 fCloneCreator.prototype = sourceObject ; 390 return new fCloneCreator ; 391} 392 393// Appends a bogus <br> at the end of the element, if not yet available. 394FCKTools.AppendBogusBr = function( element ) 395{ 396 if ( !element ) 397 return ; 398 399 var eLastChild = this.GetLastItem( element.getElementsByTagName('br') ) ; 400 401 if ( !eLastChild || ( eLastChild.getAttribute( 'type', 2 ) != '_moz' && eLastChild.getAttribute( '_moz_dirty' ) == null ) ) 402 { 403 var doc = this.GetElementDocument( element ) ; 404 405 if ( FCKBrowserInfo.IsOpera ) 406 element.appendChild( doc.createTextNode('') ) ; 407 else 408 element.appendChild( this.CreateBogusBR( doc ) ) ; 409 } 410} 411 412FCKTools.GetLastItem = function( list ) 413{ 414 if ( list.length > 0 ) 415 return list[ list.length - 1 ] ; 416 417 return null ; 418} 419 420FCKTools.GetDocumentPosition = function( w, node ) 421{ 422 var x = 0 ; 423 var y = 0 ; 424 var curNode = node ; 425 while ( curNode && curNode != w.document.body ) 426 { 427 x += curNode.offsetLeft - curNode.scrollLeft ; 428 y += curNode.offsetTop - curNode.scrollTop ; 429 curNode = curNode.offsetParent ; 430 } 431 return { "x" : x, "y" : y } ; 432} 433 434FCKTools.GetWindowPosition = function( w, node ) 435{ 436 var pos = this.GetDocumentPosition( w, node ) ; 437 var scroll = FCKTools.GetScrollPosition( w ) ; 438 pos.x -= scroll.X ; 439 pos.y -= scroll.Y ; 440 return pos ; 441} 442 443FCKTools.ProtectFormStyles = function( formNode ) 444{ 445 if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' ) 446 return [] ; 447 var hijackRecord = [] ; 448 var hijackNames = [ 'style', 'className' ] ; 449 for ( var i = 0 ; i < hijackNames.length ; i++ ) 450 { 451 var name = hijackNames[i] ; 452 if ( formNode.elements.namedItem( name ) ) 453 { 454 var hijackNode = formNode.elements.namedItem( name ) ; 455 hijackRecord.push( [ hijackNode, hijackNode.nextSibling ] ) ; 456 formNode.removeChild( hijackNode ) ; 457 } 458 } 459 return hijackRecord ; 460} 461 462FCKTools.RestoreFormStyles = function( formNode, hijackRecord ) 463{ 464 if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' ) 465 return ; 466 if ( hijackRecord.length > 0 ) 467 { 468 for ( var i = hijackRecord.length - 1 ; i >= 0 ; i-- ) 469 { 470 var node = hijackRecord[i][0] ; 471 var sibling = hijackRecord[i][1] ; 472 if ( sibling ) 473 formNode.insertBefore( node, sibling ) ; 474 else 475 formNode.appendChild( node ) ; 476 } 477 } 478} 479 480// Perform a one-step DFS walk. 481FCKTools.GetNextNode = function( node, limitNode ) 482{ 483 if ( node.firstChild ) 484 return node.firstChild ; 485 else if ( node.nextSibling ) 486 return node.nextSibling ; 487 else 488 { 489 var ancestor = node.parentNode ; 490 while ( ancestor ) 491 { 492 if ( ancestor == limitNode ) 493 return null ; 494 if ( ancestor.nextSibling ) 495 return ancestor.nextSibling ; 496 else 497 ancestor = ancestor.parentNode ; 498 } 499 } 500 return null ; 501} 502 503FCKTools.GetNextTextNode = function( textnode, limitNode, checkStop ) 504{ 505 node = this.GetNextNode( textnode, limitNode ) ; 506 if ( checkStop && node && checkStop( node ) ) 507 return null ; 508 while ( node && node.nodeType != 3 ) 509 { 510 node = this.GetNextNode( node, limitNode ) ; 511 if ( checkStop && node && checkStop( node ) ) 512 return null ; 513 } 514 return node ; 515} 516 517/** 518 * Merge all objects passed by argument into a single object. 519 */ 520FCKTools.Merge = function() 521{ 522 var args = arguments ; 523 var o = args[0] ; 524 525 for ( var i = 1 ; i < args.length ; i++ ) 526 { 527 var arg = args[i] ; 528 for ( var p in arg ) 529 o[p] = arg[p] ; 530 } 531 532 return o ; 533} 534 535/** 536 * Check if the passed argument is a real Array. It may not working when 537 * calling it cross windows. 538 */ 539FCKTools.IsArray = function( it ) 540{ 541 return ( it instanceof Array ) ; 542} 543 544/** 545 * Appends a "length" property to an object, containing the number of 546 * properties available on it, excluded the append property itself. 547 */ 548FCKTools.AppendLengthProperty = function( targetObject, propertyName ) 549{ 550 var counter = 0 ; 551 552 for ( var n in targetObject ) 553 counter++ ; 554 555 return targetObject[ propertyName || 'length' ] = counter ; 556} 557 558/** 559 * Gets the browser parsed version of a css text (style attribute value). On 560 * some cases, the browser makes changes to the css text, returning a different 561 * value. For example, hexadecimal colors get transformed to rgb(). 562 */ 563FCKTools.NormalizeCssText = function( unparsedCssText ) 564{ 565 // Injects the style in a temporary span object, so the browser parses it, 566 // retrieving its final format. 567 var tempSpan = document.createElement( 'span' ) ; 568 tempSpan.style.cssText = unparsedCssText ; 569 return tempSpan.style.cssText ; 570} 571 572/** 573 * Utility function to wrap a call to an object's method, 574 * so it can be passed for example to an event handler, 575 * and then it will be executed with 'this' being the object. 576 */ 577FCKTools.Hitch = function( obj, methodName ) 578{ 579 return function() { obj[methodName].apply(obj, arguments); } ; 580} 581