/* Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.tools} object, which contains * utility functions. */ (function() { var functions = []; /** * Utility functions. * @namespace * @example */ CKEDITOR.tools = { /** * Compare the elements of two arrays. * @param {Array} arrayA An array to be compared. * @param {Array} arrayB The other array to be compared. * @returns {Boolean} "true" is the arrays have the same lenght and * their elements match. * @example * var a = [ 1, 'a', 3 ]; * var b = [ 1, 3, 'a' ]; * var c = [ 1, 'a', 3 ]; * var d = [ 1, 'a', 3, 4 ]; * * alert( CKEDITOR.tools.arrayCompare( a, b ) ); // false * alert( CKEDITOR.tools.arrayCompare( a, c ) ); // true * alert( CKEDITOR.tools.arrayCompare( a, d ) ); // false */ arrayCompare : function( arrayA, arrayB ) { if ( !arrayA && !arrayB ) return true; if ( !arrayA || !arrayB || arrayA.length != arrayB.length ) return false; for ( var i = 0 ; i < arrayA.length ; i++ ) { if ( arrayA[ i ] != arrayB[ i ] ) return false; } return true; }, /** * Creates a deep copy of an object. * Attention: there is no support for recursive references. * @param {Object} object The object to be cloned. * @returns {Object} The object clone. * @example * var obj = * { * name : 'John', * cars : * { * Mercedes : { color : 'blue' }, * Porsche : { color : 'red' } * } * }; * var clone = CKEDITOR.tools.clone( obj ); * clone.name = 'Paul'; * clone.cars.Porsche.color = 'silver'; * alert( obj.name ); // John * alert( clone.name ); // Paul * alert( obj.cars.Porsche.color ); // red * alert( clone.cars.Porsche.color ); // silver */ clone : function( obj ) { var clone; // Array. if ( obj && ( obj instanceof Array ) ) { clone = []; for ( var i = 0 ; i < obj.length ; i++ ) clone[ i ] = this.clone( obj[ i ] ); return clone; } // "Static" types. if ( obj === null || ( typeof( obj ) != 'object' ) || ( obj instanceof String ) || ( obj instanceof Number ) || ( obj instanceof Boolean ) || ( obj instanceof Date ) || ( obj instanceof RegExp) ) { return obj; } // Objects. clone = new obj.constructor(); for ( var propertyName in obj ) { var property = obj[ propertyName ]; clone[ propertyName ] = this.clone( property ); } return clone; }, /** * Turn the first letter of string to upper-case. * @param {String} str */ capitalize: function( str ) { return str.charAt( 0 ).toUpperCase() + str.substring( 1 ).toLowerCase(); }, /** * Copy the properties from one object to another. By default, properties * already present in the target object are not overwritten. * @param {Object} target The object to be extended. * @param {Object} source[,souce(n)] The objects from which copy * properties. Any number of objects can be passed to this function. * @param {Boolean} [overwrite] If 'true' is specified it indicates that * properties already present in the target object could be * overwritten by subsequent objects. * @param {Object} [properties] Only properties within the specified names * list will be received from the source object. * @returns {Object} the extended object (target). * @example * // Create the sample object. * var myObject = * { * prop1 : true * }; * * // Extend the above object with two properties. * CKEDITOR.tools.extend( myObject, * { * prop2 : true, * prop3 : true * } ); * * // Alert "prop1", "prop2" and "prop3". * for ( var p in myObject ) * alert( p ); */ extend : function( target ) { var argsLength = arguments.length, overwrite, propertiesList; if ( typeof ( overwrite = arguments[ argsLength - 1 ] ) == 'boolean') argsLength--; else if ( typeof ( overwrite = arguments[ argsLength - 2 ] ) == 'boolean' ) { propertiesList = arguments [ argsLength -1 ]; argsLength-=2; } for ( var i = 1 ; i < argsLength ; i++ ) { var source = arguments[ i ]; for ( var propertyName in source ) { // Only copy existed fields if in overwrite mode. if ( overwrite === true || target[ propertyName ] == undefined ) { // Only copy specified fields if list is provided. if ( !propertiesList || ( propertyName in propertiesList ) ) target[ propertyName ] = source[ propertyName ]; } } } return target; }, /** * Creates an object which is an instance of a class which prototype is a * predefined object. All properties defined in the source object are * automatically inherited by the resulting object, including future * changes to it. * @param {Object} source The source object to be used as the prototype for * the final object. * @returns {Object} The resulting copy. */ prototypedCopy : function( source ) { var copy = function() {}; copy.prototype = source; return new copy(); }, /** * Checks if an object is an Array. * @param {Object} object The object to be checked. * @type Boolean * @returns true if the object is an Array, otherwise false. * @example * alert( CKEDITOR.tools.isArray( [] ) ); // "true" * alert( CKEDITOR.tools.isArray( 'Test' ) ); // "false" */ isArray : function( object ) { return ( !!object && object instanceof Array ); }, isEmpty : function ( object ) { for ( var i in object ) { if ( object.hasOwnProperty( i ) ) return false; } return true; }, /** * Transforms a CSS property name to its relative DOM style name. * @param {String} cssName The CSS property name. * @returns {String} The transformed name. * @example * alert( CKEDITOR.tools.cssStyleToDomStyle( 'background-color' ) ); // "backgroundColor" * alert( CKEDITOR.tools.cssStyleToDomStyle( 'float' ) ); // "cssFloat" */ cssStyleToDomStyle : ( function() { var test = document.createElement( 'div' ).style; var cssFloat = ( typeof test.cssFloat != 'undefined' ) ? 'cssFloat' : ( typeof test.styleFloat != 'undefined' ) ? 'styleFloat' : 'float'; return function( cssName ) { if ( cssName == 'float' ) return cssFloat; else { return cssName.replace( /-./g, function( match ) { return match.substr( 1 ).toUpperCase(); }); } }; } )(), /** * Build the HTML snippet of a set of '); else retval.push(''); } return retval.join( '' ); }, /** * Replace special HTML characters in a string with their relative HTML * entity values. * @param {String} text The string to be encoded. * @returns {String} The encode string. * @example * alert( CKEDITOR.tools.htmlEncode( 'A > B & C < D' ) ); // "A &gt; B &amp; C &lt; D" */ htmlEncode : function( text ) { var standard = function( text ) { var span = new CKEDITOR.dom.element( 'span' ); span.setText( text ); return span.getHtml(); }; var fix1 = ( standard( '\n' ).toLowerCase() == '
' ) ? function( text ) { // #3874 IE and Safari encode line-break into
return standard( text ).replace( /
/gi, '\n' ); } : standard; var fix2 = ( standard( '>' ) == '>' ) ? function( text ) { // WebKit does't encode the ">" character, which makes sense, but // it's different than other browsers. return fix1( text ).replace( />/g, '>' ); } : fix1; var fix3 = ( standard( ' ' ) == '  ' ) ? function( text ) { // #3785 IE8 changes spaces (>= 2) to   return fix2( text ).replace( / /g, ' ' ); } : fix2; this.htmlEncode = fix3; return this.htmlEncode( text ); }, /** * Replace characters can't be represented through CSS Selectors string * by CSS Escape Notation where the character escape sequence consists * of a backslash character (\) followed by the orginal characters. * Ref: http://www.w3.org/TR/css3-selectors/#grammar * @param cssSelectText * @return the escaped selector text. */ escapeCssSelector : function( cssSelectText ) { return cssSelectText.replace( /[\s#:.,$*^\[\]()~=+>]/g, '\\$&' ); }, /** * Gets a unique number for this CKEDITOR execution session. It returns * progressive numbers starting at 1. * @function * @returns {Number} A unique number. * @example * alert( CKEDITOR.tools.getNextNumber() ); // "1" (e.g.) * alert( CKEDITOR.tools.getNextNumber() ); // "2" */ getNextNumber : (function() { var last = 0; return function() { return ++last; }; })(), /** * Creates a function override. * @param {Function} originalFunction The function to be overridden. * @param {Function} functionBuilder A function that returns the new * function. The original function reference will be passed to this * function. * @returns {Function} The new function. * @example * var example = * { * myFunction : function( name ) * { * alert( 'Name: ' + name ); * } * }; * * example.myFunction = CKEDITOR.tools.override( example.myFunction, function( myFunctionOriginal ) * { * return function( name ) * { * alert( 'Override Name: ' + name ); * myFunctionOriginal.call( this, name ); * }; * }); */ override : function( originalFunction, functionBuilder ) { return functionBuilder( originalFunction ); }, /** * Executes a function after specified delay. * @param {Function} func The function to be executed. * @param {Number} [milliseconds] The amount of time (millisecods) to wait * to fire the function execution. Defaults to zero. * @param {Object} [scope] The object to hold the function execution scope * (the "this" object). By default the "window" object. * @param {Object|Array} [args] A single object, or an array of objects, to * pass as arguments to the function. * @param {Object} [ownerWindow] The window that will be used to set the * timeout. By default the current "window". * @returns {Object} A value that can be used to cancel the function execution. * @example * CKEDITOR.tools.setTimeout( * function() * { * alert( 'Executed after 2 seconds' ); * }, * 2000 ); */ setTimeout : function( func, milliseconds, scope, args, ownerWindow ) { if ( !ownerWindow ) ownerWindow = window; if ( !scope ) scope = ownerWindow; return ownerWindow.setTimeout( function() { if ( args ) func.apply( scope, [].concat( args ) ) ; else func.apply( scope ) ; }, milliseconds || 0 ); }, /** * Remove spaces from the start and the end of a string. The following * characters are removed: space, tab, line break, line feed. * @function * @param {String} str The text from which remove the spaces. * @returns {String} The modified string without the boundary spaces. * @example * alert( CKEDITOR.tools.trim( ' example ' ); // "example" */ trim : (function() { // We are not using \s because we don't want "non-breaking spaces" to be caught. var trimRegex = /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g; return function( str ) { return str.replace( trimRegex, '' ) ; }; })(), /** * Remove spaces from the start (left) of a string. The following * characters are removed: space, tab, line break, line feed. * @function * @param {String} str The text from which remove the spaces. * @returns {String} The modified string excluding the removed spaces. * @example * alert( CKEDITOR.tools.ltrim( ' example ' ); // "example " */ ltrim : (function() { // We are not using \s because we don't want "non-breaking spaces" to be caught. var trimRegex = /^[ \t\n\r]+/g; return function( str ) { return str.replace( trimRegex, '' ) ; }; })(), /** * Remove spaces from the end (right) of a string. The following * characters are removed: space, tab, line break, line feed. * @function * @param {String} str The text from which remove the spaces. * @returns {String} The modified string excluding the removed spaces. * @example * alert( CKEDITOR.tools.ltrim( ' example ' ); // " example" */ rtrim : (function() { // We are not using \s because we don't want "non-breaking spaces" to be caught. var trimRegex = /[ \t\n\r]+$/g; return function( str ) { return str.replace( trimRegex, '' ) ; }; })(), /** * Returns the index of an element in an array. * @param {Array} array The array to be searched. * @param {Object} entry The element to be found. * @returns {Number} The (zero based) index of the first entry that matches * the entry, or -1 if not found. * @example * var letters = [ 'a', 'b', 0, 'c', false ]; * alert( CKEDITOR.tools.indexOf( letters, '0' ) ); "-1" because 0 !== '0' * alert( CKEDITOR.tools.indexOf( letters, false ) ); "4" because 0 !== false */ indexOf : // #2514: We should try to use Array.indexOf if it does exist. ( Array.prototype.indexOf ) ? function( array, entry ) { return array.indexOf( entry ); } : function( array, entry ) { for ( var i = 0, len = array.length ; i < len ; i++ ) { if ( array[ i ] === entry ) return i; } return -1; }, /** * Creates a function that will always execute in the context of a * specified object. * @param {Function} func The function to be executed. * @param {Object} obj The object to which bind the execution context. * @returns {Function} The function that can be used to execute the * "func" function in the context of "obj". * @example * var obj = { text : 'My Object' }; * * function alertText() * { * alert( this.text ); * } * * var newFunc = CKEDITOR.tools.bind( alertText, obj ); * newFunc(); // Alerts "My Object". */ bind : function( func, obj ) { return function() { return func.apply( obj, arguments ); }; }, /** * Class creation based on prototype inheritance, with supports of the * following features: * * @param {Object} definiton The class definiton object. * @returns {Function} A class-like JavaScript function. */ createClass : function( definition ) { var $ = definition.$, baseClass = definition.base, privates = definition.privates || definition._, proto = definition.proto, statics = definition.statics; if ( privates ) { var originalConstructor = $; $ = function() { // Create (and get) the private namespace. var _ = this._ || ( this._ = {} ); // Make some magic so "this" will refer to the main // instance when coding private functions. for ( var privateName in privates ) { var priv = privates[ privateName ]; _[ privateName ] = ( typeof priv == 'function' ) ? CKEDITOR.tools.bind( priv, this ) : priv; } originalConstructor.apply( this, arguments ); }; } if ( baseClass ) { $.prototype = this.prototypedCopy( baseClass.prototype ); $.prototype.constructor = $; $.prototype.base = function() { this.base = baseClass.prototype.base; baseClass.apply( this, arguments ); this.base = arguments.callee; }; } if ( proto ) this.extend( $.prototype, proto, true ); if ( statics ) this.extend( $, statics, true ); return $; }, /** * Creates a function reference that can be called later using * CKEDITOR.tools.callFunction. This approach is specially useful to * make DOM attribute function calls to JavaScript defined functions. * @param {Function} fn The function to be executed on call. * @param {Object} [scope] The object to have the context on "fn" execution. * @returns {Number} A unique reference to be used in conjuction with * CKEDITOR.tools.callFunction. * @example * var ref = CKEDITOR.tools.addFunction( * function() * { * alert( 'Hello!'); * }); * CKEDITOR.tools.callFunction( ref ); // Hello! */ addFunction : function( fn, scope ) { return functions.push( function() { fn.apply( scope || this, arguments ); }) - 1; }, /** * Removes the function reference created with {@see CKEDITOR.tools.addFunction}. * @param {Number} ref The function reference created with * CKEDITOR.tools.addFunction. */ removeFunction : function( ref ) { functions[ ref ] = null; }, /** * Executes a function based on the reference created with * CKEDITOR.tools.addFunction. * @param {Number} ref The function reference created with * CKEDITOR.tools.addFunction. * @param {[Any,[Any,...]} params Any number of parameters to be passed * to the executed function. * @returns {Any} The return value of the function. * @example * var ref = CKEDITOR.tools.addFunction( * function() * { * alert( 'Hello!'); * }); * CKEDITOR.tools.callFunction( ref ); // Hello! */ callFunction : function( ref ) { var fn = functions[ ref ]; return fn && fn.apply( window, Array.prototype.slice.call( arguments, 1 ) ); }, cssLength : (function() { var decimalRegex = /^\d+(?:\.\d+)?$/; return function( length ) { return length + ( decimalRegex.test( length ) ? 'px' : '' ); }; })(), repeat : function( str, times ) { return new Array( times + 1 ).join( str ); }, tryThese : function() { var returnValue; for ( var i = 0, length = arguments.length; i < length; i++ ) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) {} } return returnValue; } }; })(); // PACKAGER_RENAME( CKEDITOR.tools )