JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.6.1
[ckeditor.git] / _source / core / tools.js
index 6f95428..a529c6a 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
-Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
@@ -12,6 +12,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 {\r
        var functions = [];\r
 \r
+       CKEDITOR.on( 'reset', function()\r
+               {\r
+                       functions = [];\r
+               });\r
+\r
        /**\r
         * Utility functions.\r
         * @namespace\r
@@ -19,6 +24,22 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
         */\r
        CKEDITOR.tools =\r
        {\r
+               /**\r
+                * Compare the elements of two arrays.\r
+                * @param {Array} arrayA An array to be compared.\r
+                * @param {Array} arrayB The other array to be compared.\r
+                * @returns {Boolean} "true" is the arrays have the same lenght and\r
+                *              their elements match.\r
+                * @example\r
+                * var a = [ 1, 'a', 3 ];\r
+                * var b = [ 1, 3, 'a' ];\r
+                * var c = [ 1, 'a', 3 ];\r
+                * var d = [ 1, 'a', 3, 4 ];\r
+                *\r
+                * alert( CKEDITOR.tools.arrayCompare( a, b ) );  // false\r
+                * alert( CKEDITOR.tools.arrayCompare( a, c ) );  // true\r
+                * alert( CKEDITOR.tools.arrayCompare( a, d ) );  // false\r
+                */\r
                arrayCompare : function( arrayA, arrayB )\r
                {\r
                        if ( !arrayA && !arrayB )\r
@@ -80,7 +101,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                || ( obj instanceof String )\r
                                || ( obj instanceof Number )\r
                                || ( obj instanceof Boolean )\r
-                               || ( obj instanceof Date ) )\r
+                               || ( obj instanceof Date )\r
+                               || ( obj instanceof RegExp) )\r
                        {\r
                                return obj;\r
                        }\r
@@ -98,6 +120,15 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                },\r
 \r
                /**\r
+                * Turn the first letter of string to upper-case.\r
+                * @param {String} str\r
+                */\r
+               capitalize: function( str )\r
+               {\r
+                       return str.charAt( 0 ).toUpperCase() + str.substring( 1 ).toLowerCase();\r
+               },\r
+\r
+               /**\r
                 * Copy the properties from one object to another. By default, properties\r
                 * already present in the target object <strong>are not</strong> overwritten.\r
                 * @param {Object} target The object to be extended.\r
@@ -190,6 +221,20 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                },\r
 \r
                /**\r
+                * Whether the object contains no properties of it's own.\r
+                * @param object\r
+                */\r
+               isEmpty : function ( object )\r
+               {\r
+                       for ( var i in object )\r
+                       {\r
+                               if ( object.hasOwnProperty( i ) )\r
+                                       return false;\r
+                       }\r
+                       return true;\r
+               },\r
+\r
+               /**\r
                 * Transforms a CSS property name to its relative DOM style name.\r
                 * @param {String} cssName The CSS property name.\r
                 * @returns {String} The transformed name.\r
@@ -220,6 +265,27 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                } )(),\r
 \r
                /**\r
+                * Build the HTML snippet of a set of &lt;style>/&lt;link>.\r
+                * @param css {String|Array} Each of which are url (absolute) of a CSS file or\r
+                * a trunk of style text.\r
+                */\r
+               buildStyleHtml : function ( css )\r
+               {\r
+                       css = [].concat( css );\r
+                       var item, retval = [];\r
+                       for ( var i = 0; i < css.length; i++ )\r
+                       {\r
+                               item = css[ i ];\r
+                               // Is CSS style text ?\r
+                               if ( /@import|[{}]/.test(item) )\r
+                                       retval.push('<style>' + item + '</style>');\r
+                               else\r
+                                       retval.push('<link type="text/css" rel=stylesheet href="' + item + '">');\r
+                       }\r
+                       return retval.join( '' );\r
+               },\r
+\r
+               /**\r
                 * Replace special HTML characters in a string with their relative HTML\r
                 * entity values.\r
                 * @param {String} text The string to be encoded.\r
@@ -267,6 +333,19 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                },\r
 \r
                /**\r
+                * Replace special HTML characters in HTMLElement's attribute with their relative HTML entity values.\r
+                * @param {String} The attribute's value to be encoded.\r
+                * @returns {String} The encode value.\r
+                * @example\r
+                * element.setAttribute( 'title', '<a " b >' );\r
+                * alert( CKEDITOR.tools.htmlEncodeAttr( element.getAttribute( 'title' ) );  // "&gt;a &quot; b &lt;"\r
+                */\r
+               htmlEncodeAttr : function( text )\r
+               {\r
+                       return text.replace( /"/g, '&quot;' ).replace( /</g, '&lt;' ).replace( />/g, '&gt;' );\r
+               },\r
+\r
+               /**\r
                 * Gets a unique number for this CKEDITOR execution session. It returns\r
                 * progressive numbers starting at 1.\r
                 * @function\r
@@ -285,6 +364,20 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                })(),\r
 \r
                /**\r
+                * Gets a unique ID for CKEditor's interface elements. It returns a\r
+                * string with the "cke_" prefix and a progressive number.\r
+                * @function\r
+                * @returns {String} A unique ID.\r
+                * @example\r
+                * alert( CKEDITOR.tools.<b>getNextId()</b> );  // "cke_1" (e.g.)\r
+                * alert( CKEDITOR.tools.<b>getNextId()</b> );  // "cke_2"\r
+                */\r
+               getNextId : function()\r
+               {\r
+                       return 'cke_' + this.getNextNumber();\r
+               },\r
+\r
+               /**\r
                 * Creates a function override.\r
                 * @param {Function} originalFunction The function to be overridden.\r
                 * @param {Function} functionBuilder A function that returns the new\r
@@ -439,6 +532,24 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                        return -1;\r
                                },\r
 \r
+               /**\r
+                * Creates a function that will always execute in the context of a\r
+                * specified object.\r
+                * @param {Function} func The function to be executed.\r
+                * @param {Object} obj The object to which bind the execution context.\r
+                * @returns {Function} The function that can be used to execute the\r
+                *              "func" function in the context of "obj".\r
+                * @example\r
+                * var obj = { text : 'My Object' };\r
+                *\r
+                * function alertText()\r
+                * {\r
+                *     alert( this.text );\r
+                * }\r
+                *\r
+                * var newFunc = <b>CKEDITOR.tools.bind( alertText, obj )</b>;\r
+                * newFunc();  // Alerts "My Object".\r
+                */\r
                bind : function( func, obj )\r
                {\r
                        return function() { return func.apply( obj, arguments ); };\r
@@ -450,11 +561,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                 * <ul>\r
                 * <li> Static fields </li>\r
                 * <li> Private fields </li>\r
-                * <li> Public(prototype) fields </li>\r
+                * <li> Public (prototype) fields </li>\r
                 * <li> Chainable base class constructor </li>\r
                 * </ul>\r
-                *\r
-                * @param {Object} definiton (Optional)The class definiton object.\r
+                * @param {Object} definition The class definition object.\r
+                * @returns {Function} A class-like JavaScript function.\r
                 */\r
                createClass : function( definition )\r
                {\r
@@ -507,33 +618,220 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        return $;\r
                },\r
 \r
+               /**\r
+                * Creates a function reference that can be called later using\r
+                * CKEDITOR.tools.callFunction. This approach is specially useful to\r
+                * make DOM attribute function calls to JavaScript defined functions.\r
+                * @param {Function} fn The function to be executed on call.\r
+                * @param {Object} [scope] The object to have the context on "fn" execution.\r
+                * @returns {Number} A unique reference to be used in conjuction with\r
+                *              CKEDITOR.tools.callFunction.\r
+                * @example\r
+                * var ref = <b>CKEDITOR.tools.addFunction</b>(\r
+                *     function()\r
+                *     {\r
+                *         alert( 'Hello!');\r
+                *     });\r
+                * CKEDITOR.tools.callFunction( ref );  // Hello!\r
+                */\r
                addFunction : function( fn, scope )\r
                {\r
                        return functions.push( function()\r
                                {\r
-                                       fn.apply( scope || this, arguments );\r
+                                       return fn.apply( scope || this, arguments );\r
                                }) - 1;\r
                },\r
 \r
-               callFunction : function( index )\r
+               /**\r
+                * Removes the function reference created with {@see CKEDITOR.tools.addFunction}.\r
+                * @param {Number} ref The function reference created with\r
+                *              CKEDITOR.tools.addFunction.\r
+                */\r
+               removeFunction : function( ref )\r
+               {\r
+                       functions[ ref ] = null;\r
+               },\r
+\r
+               /**\r
+                * Executes a function based on the reference created with\r
+                * CKEDITOR.tools.addFunction.\r
+                * @param {Number} ref The function reference created with\r
+                *              CKEDITOR.tools.addFunction.\r
+                * @param {[Any,[Any,...]} params Any number of parameters to be passed\r
+                *              to the executed function.\r
+                * @returns {Any} The return value of the function.\r
+                * @example\r
+                * var ref = CKEDITOR.tools.addFunction(\r
+                *     function()\r
+                *     {\r
+                *         alert( 'Hello!');\r
+                *     });\r
+                * <b>CKEDITOR.tools.callFunction( ref )</b>;  // Hello!\r
+                */\r
+               callFunction : function( ref )\r
                {\r
-                       var fn = functions[ index ];\r
-                       return fn.apply( window, Array.prototype.slice.call( arguments, 1 ) );\r
+                       var fn = functions[ ref ];\r
+                       return fn && fn.apply( window, Array.prototype.slice.call( arguments, 1 ) );\r
                },\r
 \r
+               /**\r
+                * Append the 'px' length unit to the size if it's missing.\r
+                * @param length\r
+                */\r
                cssLength : (function()\r
                {\r
-                       var decimalRegex = /^\d+(?:\.\d+)?$/;\r
                        return function( length )\r
                        {\r
-                               return length + ( decimalRegex.test( length ) ? 'px' : '' );\r
+                               return length + ( !length || isNaN( Number( length ) ) ? '' : 'px' );\r
                        };\r
                })(),\r
 \r
+               /**\r
+                * Convert the specified CSS length value to the calculated pixel length inside this page.\r
+                * <strong>Note:</strong> Percentage based value is left intact.\r
+                * @param {String} cssLength CSS length value.\r
+                */\r
+               convertToPx : ( function ()\r
+                       {\r
+                               var calculator;\r
+\r
+                               return function( cssLength )\r
+                               {\r
+                                       if ( !calculator )\r
+                                       {\r
+                                               calculator = CKEDITOR.dom.element.createFromHtml(\r
+                                                               '<div style="position:absolute;left:-9999px;' +\r
+                                                               'top:-9999px;margin:0px;padding:0px;border:0px;"' +\r
+                                                               '></div>', CKEDITOR.document );\r
+                                               CKEDITOR.document.getBody().append( calculator );\r
+                                       }\r
+\r
+                                       if ( !(/%$/).test( cssLength ) )\r
+                                       {\r
+                                               calculator.setStyle( 'width', cssLength );\r
+                                               return calculator.$.clientWidth;\r
+                                       }\r
+\r
+                                       return cssLength;\r
+                               };\r
+                       } )(),\r
+\r
+               /**\r
+                * String specified by {@param str} repeats {@param times} times.\r
+                * @param str\r
+                * @param times\r
+                */\r
                repeat : function( str, times )\r
                {\r
                        return new Array( times + 1 ).join( str );\r
+               },\r
+\r
+               /**\r
+                * Return the first successfully executed function's return value that\r
+                * doesn't throw any exception.\r
+                */\r
+               tryThese : function()\r
+               {\r
+                       var returnValue;\r
+                       for ( var i = 0, length = arguments.length; i < length; i++ )\r
+                       {\r
+                               var lambda = arguments[i];\r
+                               try\r
+                               {\r
+                                       returnValue = lambda();\r
+                                       break;\r
+                               }\r
+                               catch (e) {}\r
+                       }\r
+                       return returnValue;\r
+               },\r
+\r
+               /**\r
+                * Generate a combined key from a series of params.\r
+                * @param {String} subKey One or more string used as sub keys.\r
+                * @example\r
+                * var key = CKEDITOR.tools.genKey( 'key1', 'key2', 'key3' );\r
+                * alert( key );                // "key1-key2-key3".\r
+                */\r
+               genKey : function()\r
+               {\r
+                       return Array.prototype.slice.call( arguments ).join( '-' );\r
+               },\r
+\r
+               /**\r
+                * Try to avoid differences in the style attribute.\r
+                *\r
+                * @param {String} styleText The style data to be normalized.\r
+                * @param {Boolean} [nativeNormalize=false] Parse the data using the browser.\r
+                * @returns {String} The normalized value.\r
+                */\r
+               normalizeCssText: function( styleText, nativeNormalize ) {\r
+                       var props = [],\r
+                               name,\r
+                               parsedProps = CKEDITOR.tools.parseCssText( styleText, true, nativeNormalize );\r
+\r
+                       for ( name in parsedProps )\r
+                               props.push( name + ':' + parsedProps[ name ] );\r
+\r
+                       props.sort();\r
+\r
+                       return props.length ? ( props.join( ';' ) + ';' ) : '';\r
+               },\r
+\r
+               /**\r
+                * Find and convert <code>rgb(x,x,x)</code> colors definition to hexadecimal notation.\r
+                * @param {String} styleText The style data (or just a string containing rgb colors) to be converted.\r
+                * @returns {String} The style data with rgb colors converted to hexadecimal equivalents.\r
+                */\r
+               convertRgbToHex: function( styleText ) {\r
+                       return styleText.replace( /(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi, function( match, red, green, blue ) {\r
+                               var color = [ red, green, blue ];\r
+                               // Add padding zeros if the hex value is less than 0x10.\r
+                               for ( var i = 0; i < 3; i++ )\r
+                                       color[ i ] = ( '0' + parseInt( color[ i ], 10 ).toString( 16 ) ).slice( -2 );\r
+                               return '#' + color.join( '' );\r
+                       });\r
+               },\r
+\r
+               /**\r
+                * Turn inline style text properties into one hash.\r
+                *\r
+                * @param {String} styleText The style data to be parsed.\r
+                * @param {Boolean} [normalize=false] Normalize properties and values\r
+                * (e.g. trim spaces, convert to lower case).\r
+                * @param {Boolean} [nativeNormalize=false] Parse the data using the browser.\r
+                * @returns {String} The object containing parsed properties.\r
+                */\r
+               parseCssText: function( styleText, normalize, nativeNormalize ) {\r
+                       var retval = {};\r
+\r
+                       if ( nativeNormalize ) {\r
+                               // Injects the style in a temporary span object, so the browser parses it,\r
+                               // retrieving its final format.\r
+                               var temp = new CKEDITOR.dom.element( 'span' );\r
+                               temp.setAttribute( 'style', styleText );\r
+                               styleText = CKEDITOR.tools.convertRgbToHex( temp.getAttribute( 'style' ) || '' );\r
+                       }\r
+\r
+                       // IE will leave a single semicolon when failed to parse the style text. (#3891)\r
+                       if ( !styleText || styleText == ';' )\r
+                               return retval;\r
+\r
+                       styleText.replace( /&quot;/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {\r
+                               if ( normalize ) {\r
+                                       name = name.toLowerCase();\r
+                                       // Normalize font-family property, ignore quotes and being case insensitive. (#7322)\r
+                                       // http://www.w3.org/TR/css3-fonts/#font-family-the-font-family-property\r
+                                       if ( name == 'font-family' )\r
+                                               value = value.toLowerCase().replace( /["']/g, '' ).replace( /\s*,\s*/g, ',' );\r
+                                       value = CKEDITOR.tools.trim( value );\r
+                               }\r
+\r
+                               retval[ name ] = value;\r
+                       });\r
+                       return retval;\r
                }\r
+\r
        };\r
 })();\r
 \r