2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
\r
3 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
\r
5 * == BEGIN LICENSE ==
\r
7 * Licensed under the terms of any of the following licenses at your
\r
10 * - GNU General Public License Version 2 or later (the "GPL")
\r
11 * http://www.gnu.org/licenses/gpl.html
\r
13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
\r
14 * http://www.gnu.org/licenses/lgpl.html
\r
16 * - Mozilla Public License Version 1.1 or later (the "MPL")
\r
17 * http://www.mozilla.org/MPL/MPL-1.1.html
\r
21 * Utility functions.
\r
24 var FCKTools = new Object() ;
\r
26 FCKTools.CreateBogusBR = function( targetDocument )
\r
28 var eBR = targetDocument.createElement( 'br' ) ;
\r
29 // eBR.setAttribute( '_moz_editor_bogus_node', 'TRUE' ) ;
\r
30 eBR.setAttribute( 'type', '_moz' ) ;
\r
35 * Fixes relative URL entries defined inside CSS styles by appending a prefix
\r
37 * @param (String) cssStyles The CSS styles definition possibly containing url()
\r
39 * @param (String) urlFixPrefix The prefix to append to relative URLs.
\r
41 FCKTools.FixCssUrls = function( urlFixPrefix, cssStyles )
\r
43 if ( !urlFixPrefix || urlFixPrefix.length == 0 )
\r
46 return cssStyles.replace( /url\s*\(([\s'"]*)(.*?)([\s"']*)\)/g, function( match, opener, path, closer )
\r
48 if ( /^\/|^\w?:/.test( path ) )
\r
51 return 'url(' + opener + urlFixPrefix + path + closer + ')' ;
\r
55 FCKTools._GetUrlFixedCss = function( cssStyles, urlFixPrefix )
\r
57 var match = cssStyles.match( /^([^|]+)\|([\s\S]*)/ ) ;
\r
60 return FCKTools.FixCssUrls( match[1], match[2] ) ;
\r
66 * Appends a <link css> or <style> element to the document.
\r
67 * @param (Object) documentElement The DOM document object to which append the
\r
69 * @param (Variant) cssFileOrDef A String pointing to the CSS file URL or an
\r
70 * Array with many CSS file URLs or the CSS definitions for the <style>
\r
72 * @return {Array} An array containing all elements created in the target
\r
73 * document. It may include <link> or <style> elements, depending on the
\r
74 * value passed with cssFileOrDef.
\r
76 FCKTools.AppendStyleSheet = function( domDocument, cssFileOrArrayOrDef )
\r
78 if ( !cssFileOrArrayOrDef )
\r
81 if ( typeof( cssFileOrArrayOrDef ) == 'string' )
\r
83 // Test if the passed argument is an URL.
\r
84 if ( /[\\\/\.][^{}]*$/.test( cssFileOrArrayOrDef ) )
\r
86 // The string may have several URLs separated by comma.
\r
87 return this.AppendStyleSheet( domDocument, cssFileOrArrayOrDef.split(',') ) ;
\r
90 return [ this.AppendStyleString( domDocument, FCKTools._GetUrlFixedCss( cssFileOrArrayOrDef ) ) ] ;
\r
95 for ( var i = 0 ; i < cssFileOrArrayOrDef.length ; i++ )
\r
96 styles.push( this._AppendStyleSheet( domDocument, cssFileOrArrayOrDef[i] ) ) ;
\r
101 FCKTools.GetStyleHtml = (function()
\r
103 var getStyle = function( styleDef, markTemp )
\r
105 if ( styleDef.length == 0 )
\r
108 var temp = markTemp ? ' _fcktemp="true"' : '' ;
\r
109 return '<' + 'style type="text/css"' + temp + '>' + styleDef + '<' + '/style>' ;
\r
112 var getLink = function( cssFileUrl, markTemp )
\r
114 if ( cssFileUrl.length == 0 )
\r
117 var temp = markTemp ? ' _fcktemp="true"' : '' ;
\r
118 return '<' + 'link href="' + cssFileUrl + '" type="text/css" rel="stylesheet" ' + temp + '/>' ;
\r
121 return function( cssFileOrArrayOrDef, markTemp )
\r
123 if ( !cssFileOrArrayOrDef )
\r
126 if ( typeof( cssFileOrArrayOrDef ) == 'string' )
\r
128 // Test if the passed argument is an URL.
\r
129 if ( /[\\\/\.][^{}]*$/.test( cssFileOrArrayOrDef ) )
\r
131 // The string may have several URLs separated by comma.
\r
132 return this.GetStyleHtml( cssFileOrArrayOrDef.split(','), markTemp ) ;
\r
135 return getStyle( this._GetUrlFixedCss( cssFileOrArrayOrDef ), markTemp ) ;
\r
141 for ( var i = 0 ; i < cssFileOrArrayOrDef.length ; i++ )
\r
142 html += getLink( cssFileOrArrayOrDef[i], markTemp ) ;
\r
149 FCKTools.GetElementDocument = function ( element )
\r
151 return element.ownerDocument || element.document ;
\r
154 // Get the window object where the element is placed in.
\r
155 FCKTools.GetElementWindow = function( element )
\r
157 return this.GetDocumentWindow( this.GetElementDocument( element ) ) ;
\r
160 FCKTools.GetDocumentWindow = function( document )
\r
162 // With Safari, there is not way to retrieve the window from the document, so we must fix it.
\r
163 if ( FCKBrowserInfo.IsSafari && !document.parentWindow )
\r
164 this.FixDocumentParentWindow( window.top ) ;
\r
166 return document.parentWindow || document.defaultView ;
\r
170 This is a Safari specific function that fix the reference to the parent
\r
171 window from the document object.
\r
173 FCKTools.FixDocumentParentWindow = function( targetWindow )
\r
175 if ( targetWindow.document )
\r
176 targetWindow.document.parentWindow = targetWindow ;
\r
178 for ( var i = 0 ; i < targetWindow.frames.length ; i++ )
\r
179 FCKTools.FixDocumentParentWindow( targetWindow.frames[i] ) ;
\r
182 FCKTools.HTMLEncode = function( text )
\r
187 text = text.replace( /&/g, '&' ) ;
\r
188 text = text.replace( /</g, '<' ) ;
\r
189 text = text.replace( />/g, '>' ) ;
\r
194 FCKTools.HTMLDecode = function( text )
\r
199 text = text.replace( />/g, '>' ) ;
\r
200 text = text.replace( /</g, '<' ) ;
\r
201 text = text.replace( /&/g, '&' ) ;
\r
206 FCKTools._ProcessLineBreaksForPMode = function( oEditor, text, liState, node, strArray )
\r
208 var closeState = 0 ;
\r
209 var blockStartTag = "<p>" ;
\r
210 var blockEndTag = "</p>" ;
\r
211 var lineBreakTag = "<br />" ;
\r
214 blockStartTag = "<li>" ;
\r
215 blockEndTag = "</li>" ;
\r
219 // Are we currently inside a <p> tag now?
\r
220 // If yes, close it at the next double line break.
\r
221 while ( node && node != oEditor.FCK.EditorDocument.body )
\r
223 if ( node.tagName.toLowerCase() == 'p' )
\r
228 node = node.parentNode ;
\r
231 for ( var i = 0 ; i < text.length ; i++ )
\r
233 var c = text.charAt( i ) ;
\r
239 strArray.push( c ) ;
\r
243 // Now we have encountered a line break.
\r
244 // Check if the next character is also a line break.
\r
245 var n = text.charAt( i + 1 ) ;
\r
249 n = text.charAt( i + 1 ) ;
\r
253 i++ ; // ignore next character - we have already processed it.
\r
255 strArray.push( blockEndTag ) ;
\r
256 strArray.push( blockStartTag ) ;
\r
260 strArray.push( lineBreakTag ) ;
\r
264 FCKTools._ProcessLineBreaksForDivMode = function( oEditor, text, liState, node, strArray )
\r
266 var closeState = 0 ;
\r
267 var blockStartTag = "<div>" ;
\r
268 var blockEndTag = "</div>" ;
\r
271 blockStartTag = "<li>" ;
\r
272 blockEndTag = "</li>" ;
\r
276 // Are we currently inside a <div> tag now?
\r
277 // If yes, close it at the next double line break.
\r
278 while ( node && node != oEditor.FCK.EditorDocument.body )
\r
280 if ( node.tagName.toLowerCase() == 'div' )
\r
285 node = node.parentNode ;
\r
288 for ( var i = 0 ; i < text.length ; i++ )
\r
290 var c = text.charAt( i ) ;
\r
296 strArray.push( c ) ;
\r
302 if ( strArray[ strArray.length - 1 ] == blockStartTag )
\r
304 // A div tag must have some contents inside for it to be visible.
\r
305 strArray.push( " " ) ;
\r
307 strArray.push( blockEndTag ) ;
\r
309 strArray.push( blockStartTag ) ;
\r
313 strArray.push( blockEndTag ) ;
\r
316 FCKTools._ProcessLineBreaksForBrMode = function( oEditor, text, liState, node, strArray )
\r
318 var closeState = 0 ;
\r
319 var blockStartTag = "<br />" ;
\r
320 var blockEndTag = "" ;
\r
323 blockStartTag = "<li>" ;
\r
324 blockEndTag = "</li>" ;
\r
328 for ( var i = 0 ; i < text.length ; i++ )
\r
330 var c = text.charAt( i ) ;
\r
336 strArray.push( c ) ;
\r
340 if ( closeState && blockEndTag.length )
\r
341 strArray.push ( blockEndTag ) ;
\r
342 strArray.push( blockStartTag ) ;
\r
347 FCKTools.ProcessLineBreaks = function( oEditor, oConfig, text )
\r
349 var enterMode = oConfig.EnterMode.toLowerCase() ;
\r
350 var strArray = [] ;
\r
352 // Is the caret or selection inside an <li> tag now?
\r
354 var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
\r
355 range.MoveToSelection() ;
\r
356 var node = range._Range.startContainer ;
\r
357 while ( node && node.nodeType != 1 )
\r
358 node = node.parentNode ;
\r
359 if ( node && node.tagName.toLowerCase() == 'li' )
\r
362 if ( enterMode == 'p' )
\r
363 this._ProcessLineBreaksForPMode( oEditor, text, liState, node, strArray ) ;
\r
364 else if ( enterMode == 'div' )
\r
365 this._ProcessLineBreaksForDivMode( oEditor, text, liState, node, strArray ) ;
\r
366 else if ( enterMode == 'br' )
\r
367 this._ProcessLineBreaksForBrMode( oEditor, text, liState, node, strArray ) ;
\r
368 return strArray.join( "" ) ;
\r
372 * Adds an option to a SELECT element.
\r
374 FCKTools.AddSelectOption = function( selectElement, optionText, optionValue )
\r
376 var oOption = FCKTools.GetElementDocument( selectElement ).createElement( "OPTION" ) ;
\r
378 oOption.text = optionText ;
\r
379 oOption.value = optionValue ;
\r
381 selectElement.options.add(oOption) ;
\r
386 FCKTools.RunFunction = function( func, thisObject, paramsArray, timerWindow )
\r
389 this.SetTimeout( func, 0, thisObject, paramsArray, timerWindow ) ;
\r
392 FCKTools.SetTimeout = function( func, milliseconds, thisObject, paramsArray, timerWindow )
\r
394 return ( timerWindow || window ).setTimeout(
\r
398 func.apply( thisObject, [].concat( paramsArray ) ) ;
\r
400 func.apply( thisObject ) ;
\r
405 FCKTools.SetInterval = function( func, milliseconds, thisObject, paramsArray, timerWindow )
\r
407 return ( timerWindow || window ).setInterval(
\r
410 func.apply( thisObject, paramsArray || [] ) ;
\r
415 FCKTools.ConvertStyleSizeToHtml = function( size )
\r
417 return size.EndsWith( '%' ) ? size : parseInt( size, 10 ) ;
\r
420 FCKTools.ConvertHtmlSizeToStyle = function( size )
\r
422 return size.EndsWith( '%' ) ? size : ( size + 'px' ) ;
\r
425 // START iCM MODIFICATIONS
\r
426 // Amended to accept a list of one or more ascensor tag names
\r
427 // Amended to check the element itself before working back up through the parent hierarchy
\r
428 FCKTools.GetElementAscensor = function( element, ascensorTagNames )
\r
430 // var e = element.parentNode ;
\r
432 var lstTags = "," + ascensorTagNames.toUpperCase() + "," ;
\r
436 if ( lstTags.indexOf( "," + e.nodeName.toUpperCase() + "," ) != -1 )
\r
443 // END iCM MODIFICATIONS
\r
445 FCKTools.CreateEventListener = function( func, params )
\r
449 var aAllParams = [] ;
\r
451 for ( var i = 0 ; i < arguments.length ; i++ )
\r
452 aAllParams.push( arguments[i] ) ;
\r
454 func.apply( this, aAllParams.concat( params ) ) ;
\r
460 FCKTools.IsStrictMode = function( document )
\r
462 // There is no compatMode in Safari, but it seams that it always behave as
\r
463 // CSS1Compat, so let's assume it as the default for that browser.
\r
464 return ( 'CSS1Compat' == ( document.compatMode || ( FCKBrowserInfo.IsSafari ? 'CSS1Compat' : null ) ) ) ;
\r
467 // Transforms a "arguments" object to an array.
\r
468 FCKTools.ArgumentsToArray = function( args, startIndex, maxLength )
\r
470 startIndex = startIndex || 0 ;
\r
471 maxLength = maxLength || args.length ;
\r
473 var argsArray = new Array() ;
\r
475 for ( var i = startIndex ; i < startIndex + maxLength && i < args.length ; i++ )
\r
476 argsArray.push( args[i] ) ;
\r
481 FCKTools.CloneObject = function( sourceObject )
\r
483 var fCloneCreator = function() {} ;
\r
484 fCloneCreator.prototype = sourceObject ;
\r
485 return new fCloneCreator ;
\r
488 // Appends a bogus <br> at the end of the element, if not yet available.
\r
489 FCKTools.AppendBogusBr = function( element )
\r
494 var eLastChild = this.GetLastItem( element.getElementsByTagName('br') ) ;
\r
496 if ( !eLastChild || ( eLastChild.getAttribute( 'type', 2 ) != '_moz' && eLastChild.getAttribute( '_moz_dirty' ) == null ) )
\r
498 var doc = this.GetElementDocument( element ) ;
\r
500 if ( FCKBrowserInfo.IsOpera )
\r
501 element.appendChild( doc.createTextNode('') ) ;
\r
503 element.appendChild( this.CreateBogusBR( doc ) ) ;
\r
507 FCKTools.GetLastItem = function( list )
\r
509 if ( list.length > 0 )
\r
510 return list[ list.length - 1 ] ;
\r
515 FCKTools.GetDocumentPosition = function( w, node )
\r
519 var curNode = node ;
\r
520 var prevNode = null ;
\r
521 var curWindow = FCKTools.GetElementWindow( curNode ) ;
\r
522 while ( curNode && !( curWindow == w && ( curNode == w.document.body || curNode == w.document.documentElement ) ) )
\r
524 x += curNode.offsetLeft - curNode.scrollLeft ;
\r
525 y += curNode.offsetTop - curNode.scrollTop ;
\r
527 if ( ! FCKBrowserInfo.IsOpera )
\r
529 var scrollNode = prevNode ;
\r
530 while ( scrollNode && scrollNode != curNode )
\r
532 x -= scrollNode.scrollLeft ;
\r
533 y -= scrollNode.scrollTop ;
\r
534 scrollNode = scrollNode.parentNode ;
\r
538 prevNode = curNode ;
\r
539 if ( curNode.offsetParent )
\r
540 curNode = curNode.offsetParent ;
\r
543 if ( curWindow != w )
\r
545 curNode = curWindow.frameElement ;
\r
548 curWindow = curNode.contentWindow.parent ;
\r
555 // document.body is a special case when it comes to offsetTop and offsetLeft values.
\r
556 // 1. It matters if document.body itself is a positioned element;
\r
557 // 2. It matters is when we're in IE and the element has no positioned ancestor.
\r
558 // Otherwise the values should be ignored.
\r
559 if ( FCKDomTools.GetCurrentElementStyle( w.document.body, 'position') != 'static'
\r
560 || ( FCKBrowserInfo.IsIE && FCKDomTools.GetPositionedAncestor( node ) == null ) )
\r
562 x += w.document.body.offsetLeft ;
\r
563 y += w.document.body.offsetTop ;
\r
566 return { "x" : x, "y" : y } ;
\r
569 FCKTools.GetWindowPosition = function( w, node )
\r
571 var pos = this.GetDocumentPosition( w, node ) ;
\r
572 var scroll = FCKTools.GetScrollPosition( w ) ;
\r
573 pos.x -= scroll.X ;
\r
574 pos.y -= scroll.Y ;
\r
578 FCKTools.ProtectFormStyles = function( formNode )
\r
580 if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
\r
582 var hijackRecord = [] ;
\r
583 var hijackNames = [ 'style', 'className' ] ;
\r
584 for ( var i = 0 ; i < hijackNames.length ; i++ )
\r
586 var name = hijackNames[i] ;
\r
587 if ( formNode.elements.namedItem( name ) )
\r
589 var hijackNode = formNode.elements.namedItem( name ) ;
\r
590 hijackRecord.push( [ hijackNode, hijackNode.nextSibling ] ) ;
\r
591 formNode.removeChild( hijackNode ) ;
\r
594 return hijackRecord ;
\r
597 FCKTools.RestoreFormStyles = function( formNode, hijackRecord )
\r
599 if ( !formNode || formNode.nodeType != 1 || formNode.tagName.toLowerCase() != 'form' )
\r
601 if ( hijackRecord.length > 0 )
\r
603 for ( var i = hijackRecord.length - 1 ; i >= 0 ; i-- )
\r
605 var node = hijackRecord[i][0] ;
\r
606 var sibling = hijackRecord[i][1] ;
\r
608 formNode.insertBefore( node, sibling ) ;
\r
610 formNode.appendChild( node ) ;
\r
615 // Perform a one-step DFS walk.
\r
616 FCKTools.GetNextNode = function( node, limitNode )
\r
618 if ( node.firstChild )
\r
619 return node.firstChild ;
\r
620 else if ( node.nextSibling )
\r
621 return node.nextSibling ;
\r
624 var ancestor = node.parentNode ;
\r
627 if ( ancestor == limitNode )
\r
629 if ( ancestor.nextSibling )
\r
630 return ancestor.nextSibling ;
\r
632 ancestor = ancestor.parentNode ;
\r
638 FCKTools.GetNextTextNode = function( textnode, limitNode, checkStop )
\r
640 node = this.GetNextNode( textnode, limitNode ) ;
\r
641 if ( checkStop && node && checkStop( node ) )
\r
643 while ( node && node.nodeType != 3 )
\r
645 node = this.GetNextNode( node, limitNode ) ;
\r
646 if ( checkStop && node && checkStop( node ) )
\r
653 * Merge all objects passed by argument into a single object.
\r
655 FCKTools.Merge = function()
\r
657 var args = arguments ;
\r
660 for ( var i = 1 ; i < args.length ; i++ )
\r
662 var arg = args[i] ;
\r
663 for ( var p in arg )
\r
671 * Check if the passed argument is a real Array. It may not working when
\r
672 * calling it cross windows.
\r
674 FCKTools.IsArray = function( it )
\r
676 return ( it instanceof Array ) ;
\r
680 * Appends a "length" property to an object, containing the number of
\r
681 * properties available on it, excluded the append property itself.
\r
683 FCKTools.AppendLengthProperty = function( targetObject, propertyName )
\r
687 for ( var n in targetObject )
\r
690 return targetObject[ propertyName || 'length' ] = counter ;
\r
694 * Gets the browser parsed version of a css text (style attribute value). On
\r
695 * some cases, the browser makes changes to the css text, returning a different
\r
696 * value. For example, hexadecimal colors get transformed to rgb().
\r
698 FCKTools.NormalizeCssText = function( unparsedCssText )
\r
700 // Injects the style in a temporary span object, so the browser parses it,
\r
701 // retrieving its final format.
\r
702 var tempSpan = document.createElement( 'span' ) ;
\r
703 tempSpan.style.cssText = unparsedCssText ;
\r
704 return tempSpan.style.cssText ;
\r
708 * Binding the "this" reference to an object for a function.
\r
710 FCKTools.Bind = function( subject, func )
\r
712 return function(){ return func.apply( subject, arguments ) ; } ;
\r
716 * Retrieve the correct "empty iframe" URL for the current browser, which
\r
717 * causes the minimum fuzz (e.g. security warnings in HTTPS, DNS error in
\r
718 * IE5.5, etc.) for that browser, making the iframe ready to DOM use whithout
\r
719 * having to loading an external file.
\r
721 FCKTools.GetVoidUrl = function()
\r
723 if ( FCK_IS_CUSTOM_DOMAIN )
\r
724 return "javascript: void( function(){" +
\r
725 "document.open();" +
\r
726 "document.write('<html><head><title></title></head><body></body></html>');" +
\r
727 "document.domain = '" + FCK_RUNTIME_DOMAIN + "';" +
\r
728 "document.close();" +
\r
731 if ( FCKBrowserInfo.IsIE )
\r
733 if ( FCKBrowserInfo.IsIE7 || !FCKBrowserInfo.IsIE6 )
\r
734 return "" ; // IE7+ / IE5.5
\r
736 return "javascript: '';" ; // IE6+
\r
739 return "javascript: void(0);" ; // All other browsers.
\r
742 FCKTools.ResetStyles = function( element )
\r
744 element.style.cssText = 'margin:0;' +
\r
747 'background-color:transparent;' +
\r
748 'background-image:none;' ;
\r