1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 /**
  7  * @fileOverview Defines the {@link CKEDITOR.tools} object, which contains
  8  *		utility functions.
  9  */
 10
 11 (function()
 12 {
 13 	var functions = [];
 14
 15 	/**
 16 	 * Utility functions.
 17 	 * @namespace
 18 	 * @example
 19 	 */
 20 	CKEDITOR.tools =
 21 	{
 22 		arrayCompare : function( arrayA, arrayB )
 23 		{
 24 			if ( !arrayA && !arrayB )
 25 				return true;
 26
 27 			if ( !arrayA || !arrayB || arrayA.length != arrayB.length )
 28 				return false;
 29
 30 			for ( var i = 0 ; i < arrayA.length ; i++ )
 31 			{
 32 				if ( arrayA[ i ] != arrayB[ i ] )
 33 					return false;
 34 			}
 35
 36 			return true;
 37 		},
 38
 39 		/**
 40 		 * Creates a deep copy of an object.
 41 		 * Attention: there is no support for deep copy of Array properties and
 42 		 * for recursive references.
 43 		 * @param {Object} object The object to be cloned.
 44 		 * @returns {Object} The object clone.
 45 		 * @example
 46 		 * var obj =
 47 		 *     {
 48 		 *         name : 'John',
 49 		 *         cars :
 50 		 *             {
 51 		 *                 Mercedes : { color : 'blue' },
 52 		 *                 Porsche : { color : 'red' }
 53 		 *             }
 54 		 *     };
 55 		 * var clone = CKEDITOR.tools.clone( obj );
 56 		 * clone.name = 'Paul';
 57 		 * clone.cars.Porsche.color = 'silver';
 58 		 * alert( obj.name );	// John
 59 		 * alert( clone.name );	// Paul
 60 		 * alert( obj.cars.Porsche.color );	// red
 61 		 * alert( clone.cars.Porsche.color );	// silver
 62 		 */
 63 		clone : function( object )
 64 		{
 65 			var clone = {};
 66
 67 			for ( var propertyName in object )
 68 			{
 69 				var property = object[ propertyName ];
 70
 71 				if ( typeof property == 'object' )
 72 					property = this.clone( property );
 73
 74 				clone[ propertyName ] = property;
 75 			}
 76
 77 			return clone;
 78 		},
 79
 80 		/**
 81 		 * Copy the properties from one object to another. By default, properties
 82 		 * already present in the target object <strong>are not</strong> overwritten.
 83 		 * @param {Object} target The object to be extended.
 84 		 * @param {Object} source[,souce(n)] The objects from which copy
 85 		 *		properties. Any number of objects can be passed to this function.
 86 		 * @param {Boolean} [overwrite] If 'true' is specified it indicates that
 87 		 *            properties already present in the target object could be
 88 		 *            overwritten by subsequent objects.
 89 		 * @param {Object} [properties] Only properties within the specified names
 90 		 *            list will be received from the source object.
 91 		 * @returns {Object} the extended object (target).
 92 		 * @example
 93 		 * // Create the sample object.
 94 		 * var myObject =
 95 		 * {
 96 		 *     prop1 : true
 97 		 * };
 98 		 *
 99 		 * // Extend the above object with two properties.
100 		 * CKEDITOR.tools.extend( myObject,
101 		 *     {
102 		 *         prop2 : true,
103 		 *         prop3 : true
104 		 *     } );
105 		 *
106 		 * // Alert "prop1", "prop2" and "prop3".
107 		 * for ( var p in myObject )
108 		 *     alert( p );
109 		 */
110 		extend : function( target )
111 		{
112 			var argsLength = arguments.length,
113 				overwrite, propertiesList;
114
115 			if ( typeof ( overwrite = arguments[ argsLength - 1 ] ) == 'boolean')
116 				argsLength--;
117 			else if ( typeof ( overwrite = arguments[ argsLength - 2 ] ) == 'boolean' )
118 			{
119 				propertiesList = arguments [ argsLength -1 ];
120 				argsLength-=2;
121 			}
122 			for ( var i = 1 ; i < argsLength ; i++ )
123 			{
124 				var source = arguments[ i ];
125 				for ( var propertyName in source )
126 				{
127 					// Only copy existed fields if in overwrite mode.
128 					if ( overwrite === true || target[ propertyName ] == undefined )
129 					{
130 						// Only copy  specified fields if list is provided.
131 						if ( !propertiesList || ( propertyName in propertiesList ) )
132 							target[ propertyName ] = source[ propertyName ];
133
134 					}
135 				}
136 			}
137
138 			return target;
139 		},
140
141 		/**
142 		 * Creates an object which is an instance of a class which prototype is a
143 		 * predefined object. All properties defined in the source object are
144 		 * automatically inherited by the resulting object, including future
145 		 * changes to it.
146 		 * @param {Object} source The source object to be used as the prototype for
147 		 *		the final object.
148 		 * @returns {Object} The resulting copy.
149 		 */
150 		prototypedCopy : function( source )
151 		{
152 			var copy = function()
153 			{};
154 			copy.prototype = source;
155 			return new copy();
156 		},
157
158 		/**
159 		 * Checks if an object is an Array.
160 		 * @param {Object} object The object to be checked.
161 		 * @type Boolean
162 		 * @returns <i>true</i> if the object is an Array, otherwise <i>false</i>.
163 		 * @example
164 		 * alert( CKEDITOR.tools.isArray( [] ) );      // "true"
165 		 * alert( CKEDITOR.tools.isArray( 'Test' ) );  // "false"
166 		 */
167 		isArray : function( object )
168 		{
169 			return ( !!object && object instanceof Array );
170 		},
171
172 		/**
173 		 * Transforms a CSS property name to its relative DOM style name.
174 		 * @param {String} cssName The CSS property name.
175 		 * @returns {String} The transformed name.
176 		 * @example
177 		 * alert( CKEDITOR.tools.cssStyleToDomStyle( 'background-color' ) );  // "backgroundColor"
178 		 * alert( CKEDITOR.tools.cssStyleToDomStyle( 'float' ) );             // "cssFloat"
179 		 */
180 		cssStyleToDomStyle : function( cssName )
181 		{
182 			if ( cssName == 'float' )
183 				return 'cssFloat';
184 			else
185 			{
186 				return cssName.replace( /-./g, function( match )
187 					{
188 						return match.substr( 1 ).toUpperCase();
189 					});
190 			}
191 		},
192
193 		/**
194 		 * Replace special HTML characters in a string with their relative HTML
195 		 * entity values.
196 		 * @param {String} text The string to be encoded.
197 		 * @returns {String} The encode string.
198 		 * @example
199 		 * alert( CKEDITOR.tools.htmlEncode( 'A > B & C < D' ) );  // "A &gt; B &amp; C &lt; D"
200 		 */
201 		htmlEncode : function( text )
202 		{
203 			var standard = function( text )
204 			{
205 				var span = new CKEDITOR.dom.element( 'span' );
206 				span.setText( text );
207 				return span.getHtml();
208 			};
209
210 			this.htmlEncode = ( standard( '>' ) == '>' ) ?
211 				function( text )
212 				{
213 					// WebKit does't encode the ">" character, which makes sense, but
214 					// it's different than other browsers.
215 					return standard( text ).replace( />/g, '>' );
216 				} :
217 				standard;
218
219 			return this.htmlEncode( text );
220 		},
221
222 		/**
223 		 * Gets a unique number for this CKEDITOR execution session. It returns
224 		 * progressive numbers starting at 1.
225 		 * @function
226 		 * @returns {Number} A unique number.
227 		 * @example
228 		 * alert( CKEDITOR.tools.<b>getNextNumber()</b> );  // "1" (e.g.)
229 		 * alert( CKEDITOR.tools.<b>getNextNumber()</b> );  // "2"
230 		 */
231 		getNextNumber : (function()
232 		{
233 			var last = 0;
234 			return function()
235 			{
236 				return ++last;
237 			};
238 		})(),
239
240 		/**
241 		 * Creates a function override.
242 		 * @param {Function} originalFunction The function to be overridden.
243 		 * @param {Function} functionBuilder A function that returns the new
244 		 *		function. The original function reference will be passed to this
245 		 *		function.
246 		 * @returns {Function} The new function.
247 		 * @example
248 		 * var example =
249 		 * {
250 		 *     myFunction : function( name )
251 		 *     {
252 		 *         alert( 'Name: ' + name );
253 		 *     }
254 		 * };
255 		 *
256 		 * example.myFunction = CKEDITOR.tools.override( example.myFunction, function( myFunctionOriginal )
257 		 *     {
258 		 *         return function( name )
259 		 *             {
260 		 *                 alert( 'Override Name: ' + name );
261 		 *                 myFunctionOriginal.call( this, name );
262 		 *             };
263 		 *     });
264 		 */
265 		override : function( originalFunction, functionBuilder )
266 		{
267 			return functionBuilder( originalFunction );
268 		},
269
270 		/**
271 		 * Executes a function after specified delay.
272 		 * @param {Function} func The function to be executed.
273 		 * @param {Number} [milliseconds] The amount of time (millisecods) to wait
274 		 *		to fire the function execution. Defaults to zero.
275 		 * @param {Object} [scope] The object to hold the function execution scope
276 		 *		(the "this" object). By default the "window" object.
277 		 * @param {Object|Array} [args] A single object, or an array of objects, to
278 		 *		pass as arguments to the function.
279 		 * @param {Object} [ownerWindow] The window that will be used to set the
280 		 *		timeout. By default the current "window".
281 		 * @returns {Object} A value that can be used to cancel the function execution.
282 		 * @example
283 		 * CKEDITOR.tools.<b>setTimeout(
284 		 *     function()
285 		 *     {
286 		 *         alert( 'Executed after 2 seconds' );
287 		 *     },
288 		 *     2000 )</b>;
289 		 */
290 		setTimeout : function( func, milliseconds, scope, args, ownerWindow )
291 		{
292 			if ( !ownerWindow )
293 				ownerWindow = window;
294
295 			if ( !scope )
296 				scope = ownerWindow;
297
298 			return ownerWindow.setTimeout(
299 				function()
300 				{
301 					if ( args )
302 						func.apply( scope, [].concat( args ) ) ;
303 					else
304 						func.apply( scope ) ;
305 				},
306 				milliseconds || 0 );
307 		},
308
309 		/**
310 		 * Remove spaces from the start and the end of a string. The following
311 		 * characters are removed: space, tab, line break, line feed.
312 		 * @function
313 		 * @param {String} str The text from which remove the spaces.
314 		 * @returns {String} The modified string without the boundary spaces.
315 		 * @example
316 		 * alert( CKEDITOR.tools.trim( '  example ' );  // "example"
317 		 */
318 		trim : (function()
319 		{
320 			// We are not using \s because we don't want "non-breaking spaces" to be caught.
321 			var trimRegex = /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;
322 			return function( str )
323 			{
324 				return str.replace( trimRegex, '' ) ;
325 			};
326 		})(),
327
328 		/**
329 		 * Remove spaces from the start (left) of a string. The following
330 		 * characters are removed: space, tab, line break, line feed.
331 		 * @function
332 		 * @param {String} str The text from which remove the spaces.
333 		 * @returns {String} The modified string excluding the removed spaces.
334 		 * @example
335 		 * alert( CKEDITOR.tools.ltrim( '  example ' );  // "example "
336 		 */
337 		ltrim : (function()
338 		{
339 			// We are not using \s because we don't want "non-breaking spaces" to be caught.
340 			var trimRegex = /^[ \t\n\r]+/g;
341 			return function( str )
342 			{
343 				return str.replace( trimRegex, '' ) ;
344 			};
345 		})(),
346
347 		/**
348 		 * Remove spaces from the end (right) of a string. The following
349 		 * characters are removed: space, tab, line break, line feed.
350 		 * @function
351 		 * @param {String} str The text from which remove the spaces.
352 		 * @returns {String} The modified string excluding the removed spaces.
353 		 * @example
354 		 * alert( CKEDITOR.tools.ltrim( '  example ' );  // "  example"
355 		 */
356 		rtrim : (function()
357 		{
358 			// We are not using \s because we don't want "non-breaking spaces" to be caught.
359 			var trimRegex = /[ \t\n\r]+$/g;
360 			return function( str )
361 			{
362 				return str.replace( trimRegex, '' ) ;
363 			};
364 		})(),
365
366 		/**
367 		 * Returns the index of an element in an array.
368 		 * @param {Array} array The array to be searched.
369 		 * @param {Object} entry The element to be found.
370 		 * @returns {Number} The (zero based) index of the first entry that matches
371 		 *		the entry, or -1 if not found.
372 		 * @example
373 		 * var letters = [ 'a', 'b', 0, 'c', false ];
374 		 * alert( CKEDITOR.tools.indexOf( letters, '0' ) );  "-1" because 0 !== '0'
375 		 * alert( CKEDITOR.tools.indexOf( letters, false ) );  "4" because 0 !== false
376 		 */
377 		indexOf :
378 			// #2514: We should try to use Array.indexOf if it does exist.
379 			( Array.prototype.indexOf ) ?
380 				function( array, entry )
381 					{
382 						return array.indexOf( entry );
383 					}
384 			:
385 				function( array, entry )
386 				{
387 					for ( var i = 0, len = array.length ; i < len ; i++ )
388 					{
389 						if ( array[ i ] === entry )
390 							return i;
391 					}
392 					return -1;
393 				},
394
395 		bind : function( func, obj )
396 		{
397 			return function() { return func.apply( obj, arguments ); };
398 		},
399
400 		/**
401 		 * Class creation based on prototype inheritance, with supports of the
402 		 * following features:
403 		 * <ul>
404 		 * <li> Static fields </li>
405 		 * <li> Private fields </li>
406 		 * <li> Public(prototype) fields </li>
407 		 * <li> Chainable base class constructor </li>
408 		 * </ul>
409 		 *
410 		 * @param {Object} definiton (Optional)The class definiton object.
411 		 */
412 		createClass : function( definition )
413 		{
414 			var $ = definition.$,
415 				baseClass = definition.base,
416 				privates = definition.privates || definition._,
417 				proto = definition.proto,
418 				statics = definition.statics;
419
420 			if ( privates )
421 			{
422 				var originalConstructor = $;
423 				$ = function()
424 				{
425 					// Create (and get) the private namespace.
426 					var _ = this._ || ( this._ = {} );
427
428 					// Make some magic so "this" will refer to the main
429 					// instance when coding private functions.
430 					for ( var privateName in privates )
431 					{
432 						var priv = privates[ privateName ];
433
434 						_[ privateName ] =
435 							( typeof priv == 'function' ) ? CKEDITOR.tools.bind( priv, this ) : priv;
436 					}
437
438 					originalConstructor.apply( this, arguments );
439 				};
440 			}
441
442 			if ( baseClass )
443 			{
444 				$.prototype = this.prototypedCopy( baseClass.prototype );
445 				$.prototype.constructor = $;
446 				$.prototype.base = function()
447 				{
448 					this.base = baseClass.prototype.base;
449 					baseClass.apply( this, arguments );
450 					this.base = arguments.callee;
451 				};
452 			}
453
454 			if ( proto )
455 				this.extend( $.prototype, proto, true );
456
457 			if ( statics )
458 				this.extend( $, statics, true );
459
460 			return $;
461 		},
462
463 		addFunction : function( fn, scope )
464 		{
465 			return functions.push( function()
466 				{
467 					fn.apply( scope || this, arguments );
468 				}) - 1;
469 		},
470
471 		callFunction : function( index )
472 		{
473 			var fn = functions[ index ];
474 			return fn.apply( window, Array.prototype.slice.call( arguments, 1 ) );
475 		},
476
477 		cssLength : (function()
478 		{
479 			var decimalRegex = /^\d+(?:\.\d+)?$/;
480 			return function( length )
481 			{
482 				return length + ( decimalRegex.test( length ) ? 'px' : '' );
483 			};
484 		})()
485 	};
486 })();
487
488 // PACKAGER_RENAME( CKEDITOR.tools )
489