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.editor} class, which represents an
  8  *		editor instance.
  9  */
 10
 11 (function()
 12 {
 13 	// The counter for automatic instance names.
 14 	var nameCounter = 0;
 15
 16 	var getNewName = function()
 17 	{
 18 		var name = 'editor' + ( ++nameCounter );
 19 		return ( CKEDITOR.instances && CKEDITOR.instances[ name ] ) ? getNewName() : name;
 20 	};
 21
 22 	// ##### START: Config Privates
 23
 24 	// These function loads custom configuration files and cache the
 25 	// CKEDITOR.editorConfig functions defined on them, so there is no need to
 26 	// download them more than once for several instances.
 27 	var loadConfigLoaded = {};
 28 	var loadConfig = function( editor )
 29 	{
 30 		var customConfig = editor.config.customConfig;
 31
 32 		// Check if there is a custom config to load.
 33 		if ( !customConfig )
 34 			return false;
 35
 36 		var loadedConfig = loadConfigLoaded[ customConfig ] || ( loadConfigLoaded[ customConfig ] = {} );
 37
 38
 39 		// If the custom config has already been downloaded, reuse it.
 40 		if ( loadedConfig.fn )
 41 		{
 42 			// Call the cached CKEDITOR.editorConfig defined in the custom
 43 			// config file for the editor instance depending on it.
 44 			loadedConfig.fn.call( editor, editor.config );
 45
 46 			// If there is no other customConfig in the chain, fire the
 47 			// "configLoaded" event.
 48 			if ( editor.config.customConfig == customConfig || !loadConfig( editor ) )
 49 				editor.fireOnce( 'customConfigLoaded' );
 50 		}
 51 		else
 52 		{
 53 			// Load the custom configuration file.
 54 			CKEDITOR.scriptLoader.load( customConfig, function()
 55 				{
 56 					// If the CKEDITOR.editorConfig function has been properly
 57 					// defined in the custom configuration file, cache it.
 58 					if ( CKEDITOR.editorConfig )
 59 						loadedConfig.fn = CKEDITOR.editorConfig;
 60 					else
 61 						loadedConfig.fn = function(){};
 62
 63 					// Call the load config again. This time the custom
 64 					// config is already cached and so it will get loaded.
 65 					loadConfig( editor );
 66 				});
 67 		}
 68
 69 		return true;
 70 	};
 71
 72 	var initConfig = function( editor, instanceConfig )
 73 	{
 74 		// Setup the lister for the "customConfigLoaded" event.
 75 		editor.on( 'customConfigLoaded', function()
 76 			{
 77 				// Overwrite the settings from the in-page config.
 78 				if ( instanceConfig )
 79 					CKEDITOR.tools.extend( editor.config, instanceConfig, true );
 80
 81 				onConfigLoaded( editor );
 82 			});
 83
 84 		// The instance config may override the customConfig setting to avoid
 85 		// loading the default ~/config.js file.
 86 		if ( instanceConfig && instanceConfig.customConfig != undefined )
 87 			editor.config.customConfig = instanceConfig.customConfig;
 88
 89 		// Load configs from the custom configuration files.
 90 		if ( !loadConfig( editor ) )
 91 			editor.fireOnce( 'customConfigLoaded' );
 92 	};
 93
 94 	// ##### END: Config Privates
 95
 96 	var onConfigLoaded = function( editor )
 97 	{
 98 		// Set config related properties.
 99
100 		var skin = editor.config.skin;
101
102 		editor.skinPath = CKEDITOR.getUrl(
104 			'skins/' + skin + '/' );
105
106 		editor.skinClass = 'cke_skin_' + skin;
107
108 		// Fire the "configLoaded" event.
109 		editor.fireOnce( 'configLoaded' );
110
111 		// Load language file.
112 		loadLang( editor );
113 	};
114
115 	var loadLang = function( editor )
116 	{
117 		CKEDITOR.lang.load( editor.config.defaultLanguage, editor.config.autoLanguage, function( languageCode, lang )
118 			{
119 				editor.langCode = languageCode;
120
121 				// As we'll be adding plugin specific entries that could come
122 				// from different language code files, we need a copy of lang,
123 				// not a direct reference to it.
124 				editor.lang = CKEDITOR.tools.prototypedCopy( lang );
125
126 				loadPlugins( editor );
127 			});
128 	};
129
130 	var loadPlugins = function( editor )
131 	{
132 		// Load all plugins defined in the "plugins" setting.
133 		CKEDITOR.plugins.load( editor.config.plugins.split( ',' ), function( plugins )
134 			{
135 				// The list of plugins.
136 				var pluginsArray = [];
137
138 				// The language code to get loaded for each plugin. Null
139 				// entries will be appended for plugins with no language files.
140 				var languageCodes = [];
141
142 				// The list of URLs to language files.
143 				var languageFiles = [];
144
145 				// Cache the loaded plugin names.
146 				editor.plugins = plugins;
147
148 				// Loop through all plugins, to build the list of language
149 				// files to get loaded.
150 				for ( var pluginName in plugins )
151 				{
152 					var plugin = plugins[ pluginName ],
153 						pluginLangs = plugin.lang,
154 						pluginPath = CKEDITOR.plugins.getPath( pluginName ),
155 						lang = null;
156
157 					// Set the plugin path in the plugin.
158 					plugin.path = pluginPath;
159
160 					// If the plugin has "lang".
161 					if ( pluginLangs )
162 					{
163 						// Resolve the plugin language. If the current language
164 						// is not available, get the first one (default one).
165 						lang = ( CKEDITOR.tools.indexOf( pluginLangs, editor.langCode ) >= 0 ? editor.langCode : pluginLangs[ 0 ] );
166
167 						if ( !plugin.lang[ lang ] )
168 						{
169 							// Put the language file URL into the list of files to
170 							// get downloaded.
171 							languageFiles.push( CKEDITOR.getUrl( pluginPath + 'lang/' + lang + '.js' ) );
172 						}
173 						else
174 						{
175 							CKEDITOR.tools.extend( editor.lang, plugin.lang[ lang ] );
176 							lang = null;
177 						}
178 					}
179
180 					// Save the language code, so we know later which
181 					// language has been resolved to this plugin.
182 					languageCodes.push( lang );
183
184 					pluginsArray.push( plugin );
185 				}
186
187 				// Load all plugin specific language files in a row.
188 				CKEDITOR.scriptLoader.load( languageFiles, function()
189 					{
190 						// Initialize all plugins that have the "beforeInit" and "init" methods defined.
191 						var methods = [ 'beforeInit', 'init', 'afterInit' ];
192 						for ( var m = 0 ; m < methods.length ; m++ )
193 						{
194 							for ( var i = 0 ; i < pluginsArray.length ; i++ )
195 							{
196 								var plugin = pluginsArray[ i ];
197
198 								// Uses the first loop to update the language entries also.
199 								if ( m === 0 && languageCodes[ i ] && plugin.lang )
200 									CKEDITOR.tools.extend( editor.lang, plugin.lang[ languageCodes[ i ] ] );
201
202 								// Call the plugin method (beforeInit and init).
203 								if ( plugin[ methods[ m ] ] )
204 									plugin[ methods[ m ] ]( editor );
205 							}
206 						}
207
208 						// Load the editor skin.
209 						editor.fire( 'pluginsLoaded' );
210 						loadSkin( editor );
211 					});
212 			});
213 	};
214
215 	var loadSkin = function( editor )
216 	{
217 		CKEDITOR.skins.load( editor.config.skin, 'editor', function()
218 			{
219 				loadTheme( editor );
220 			});
221 	};
222
223 	var loadTheme = function( editor )
224 	{
225 		var theme = editor.config.theme;
226 		CKEDITOR.themes.load( theme, function()
227 			{
228 				var editorTheme = editor.theme = CKEDITOR.themes.get( theme );
229 				editorTheme.path = CKEDITOR.themes.getPath( theme );
230 				editorTheme.build( editor );
231
232 				if ( editor.config.autoUpdateElement )
233 					attachToForm( editor );
234 			});
235 	};
236
237 	var attachToForm = function( editor )
238 	{
239 		var element = editor.element;
240
241 		// If are replacing a textarea, we must
242 		if ( editor.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE && element.is( 'textarea' ) )
243 		{
244 			var form = element.$.form && new CKEDITOR.dom.element( element.$.form );
245 			if ( form )
246 			{
247 				form.on( 'submit', function()
248 					{
249 						editor.updateElement();
250 					});
251
252 				// Setup the submit function because it doesn't fire the
253 				// "submit" event.
254 				if ( !form.$.submit.nodeName )
255 				{
256 					form.$.submit = CKEDITOR.tools.override( form.$.submit, function( originalSubmit )
257 						{
258 							return function()
259 								{
260 									editor.updateElement();
261
262 									// For IE, the DOM submit function is not a
263 									// function, so we need thid check.
264 									if ( originalSubmit.apply )
265 										originalSubmit.apply( this, arguments );
266 									else
267 										originalSubmit();
268 								};
269 						});
270 				}
271 			}
272 		}
273 	};
274
275 	function updateCommandsMode()
276 	{
277 		var command,
278 			commands = this._.commands,
279 			mode = this.mode;
280
281 		for ( var name in commands )
282 		{
283 			command = commands[ name ];
284
285 			command.setState( command.modes[ mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
286 		}
287 	}
288
289 	/**
290 	 * Initializes the editor instance. This function is called by the editor
291 	 * contructor (editor_basic.js).
292 	 * @private
293 	 */
294 	CKEDITOR.editor.prototype._init = function()
295 		{
296 			// Get the properties that have been saved in the editor_base
297 			// implementation.
298 			var element			= CKEDITOR.dom.element.get( this._.element ),
299 				instanceConfig	= this._.instanceConfig;
300 			delete this._.element;
301 			delete this._.instanceConfig;
302
303 			this._.commands = {};
304 			this._.styles = [];
305
306 			/**
307 			 * The DOM element that has been replaced by this editor instance. This
308 			 * element holds the editor data on load and post.
309 			 * @name CKEDITOR.editor.prototype.element
310 			 * @type CKEDITOR.dom.element
311 			 * @example
312 			 * var editor = CKEDITOR.instances.editor1;
313 			 * alert( <b>editor.element</b>.getName() );  "textarea"
314 			 */
315 			this.element = element;
316
317 			/**
318 			 * The editor instance name. It hay be the replaced element id, name or
319 			 * a default name using a progressive counter (editor1, editor2, ...).
320 			 * @name CKEDITOR.editor.prototype.name
321 			 * @type String
322 			 * @example
323 			 * var editor = CKEDITOR.instances.editor1;
324 			 * alert( <b>editor.name</b> );  "editor1"
325 			 */
326 			this.name = ( element && ( this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE )
327 							&& ( element.getId() || element.getNameAtt() ) )
328 						|| getNewName();
329
330 			/**
331 			 * The configurations for this editor instance. It inherits all
332 			 * settings defined in (@link CKEDITOR.config}, combined with settings
333 			 * loaded from custom configuration files and those defined inline in
334 			 * the page when creating the editor.
335 			 * @name CKEDITOR.editor.prototype.config
336 			 * @type Object
337 			 * @example
338 			 * var editor = CKEDITOR.instances.editor1;
339 			 * alert( <b>editor.config.theme</b> );  "default" e.g.
340 			 */
341 			this.config = CKEDITOR.tools.prototypedCopy( CKEDITOR.config );
342
343 			/**
344 			 * Namespace containing UI features related to this editor instance.
345 			 * @name CKEDITOR.editor.prototype.ui
346 			 * @type CKEDITOR.ui
347 			 * @example
348 			 */
349 			this.ui = new CKEDITOR.ui( this );
350
351 			/**
352 			 * Controls the focus state of this editor instance. This property
353 			 * is rarely used for normal API operations. It is mainly
354 			 * destinated to developer adding UI elements to the editor interface.
355 			 * @name CKEDITOR.editor.prototype.focusManager
356 			 * @type CKEDITOR.focusManager
357 			 * @example
358 			 */
359 			this.focusManager = new CKEDITOR.focusManager( this );
360
361 			CKEDITOR.fire( 'instanceCreated', null, this );
362
363 			this.on( 'mode', updateCommandsMode );
364
365 			initConfig( this, instanceConfig );
366 		};
367 })();
368
369 CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
370 	/** @lends CKEDITOR.editor.prototype */
371 	{
372 		/**
373 		 * Adds a command definition to the editor instance. Commands added with
374 		 * this function can be later executed with {@link #execCommand}.
375 		 * @param {String} commandName The indentifier name of the command.
376 		 * @param {CKEDITOR.commandDefinition} commandDefinition The command definition.
377 		 * @example
378 		 * editorInstance.addCommand( 'sample',
379 		 * {
380 		 *     exec : function( editor )
381 		 *     {
382 		 *         alert( 'Executing a command for the editor name "' + editor.name + '"!' );
383 		 *     }
384 		 * });
385 		 */
386 		addCommand : function( commandName, commandDefinition )
387 		{
388 			return this._.commands[ commandName ] = new CKEDITOR.command( this, commandDefinition );
389 		},
390
391 		addCss : function( css )
392 		{
393 			this._.styles.push( css );
394 		},
395
396 		/**
397 		 * Destroys the editor instance, releasing all resources used by it.
398 		 * If the editor replaced an element, the element will be recovered.
399 		 * @param {Boolean} [noUpdate] If the instance is replacing a DOM
400 		 *		element, this parameter indicates whether or not to update the
401 		 *		element with the instance contents.
402 		 * @example
403 		 * alert( CKEDITOR.instances.editor1 );  e.g "object"
404 		 * <b>CKEDITOR.instances.editor1.destroy()</b>;
405 		 * alert( CKEDITOR.instances.editor1 );  "undefined"
406 		 */
407 		destroy : function( noUpdate )
408 		{
409 			if ( !noUpdate )
410 				this.updateElement();
411
412 			this.theme.destroy( this );
413 			CKEDITOR.remove( this );
414 		},
415
416 		/**
417 		 * Executes a command.
418 		 * @param {String} commandName The indentifier name of the command.
419 		 * @param {Object} [data] Data to be passed to the command
420 		 * @returns {Boolean} "true" if the command has been successfuly
421 		 *		executed, otherwise "false".
422 		 * @example
423 		 * editorInstance.execCommand( 'Bold' );
424 		 */
425 		execCommand : function( commandName, data )
426 		{
427 			var command = this.getCommand( commandName );
428
429 			var eventData =
430 			{
431 				name: commandName,
432 				commandData: data,
433 				command: command
434 			};
435
436 			if ( command && command.state != CKEDITOR.TRISTATE_DISABLED )
437 			{
438 				if ( this.fire( 'beforeCommandExec', eventData ) !== true )
439 				{
440 					eventData.returnValue = command.exec( eventData.commandData );
441
442 					if ( this.fire( 'afterCommandExec', eventData ) !== true )
443 						return eventData.returnValue;
444 				}
445 			}
446
447 			// throw 'Unknown command name "' + commandName + '"';
448 			return false;
449 		},
450
451 		/**
452 		 * Gets one of the registered commands. Note that, after registering a
453 		 * command definition with addCommand, it is transformed internally
454 		 * into an instance of {@link CKEDITOR.command}, which will be then
455 		 * returned by this function.
456 		 * @param {String} commandName The name of the command to be returned.
457 		 * This is the same used to register the command with addCommand.
458 		 * @returns {CKEDITOR.command} The command object identified by the
459 		 * provided name.
460 		 */
461 		getCommand : function( commandName )
462 		{
463 			return this._.commands[ commandName ];
464 		},
465
466 		/**
467 		 * Gets the editor data. The data will be in raw format. It is the same
468 		 * data that is posted by the editor.
469 		 * @type String
470 		 * @returns (String) The editor data.
471 		 * @example
472 		 * if ( CKEDITOR.instances.editor1.<b>getData()</b> == '' )
473 		 *     alert( 'There is no data available' );
474 		 */
475 		getData : function()
476 		{
477 			this.fire( 'beforeGetData' );
478
479 			var eventData = this._.data;
480
481 			if ( typeof eventData != 'string' )
482 			{
483 				var element = this.element;
484 				if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE )
485 					eventData = element.is( 'textarea' ) ? element.getValue() : element.getHtml();
486 				else
487 					eventData = '';
488 			}
489
490 			eventData = { dataValue : eventData };
491
492 			// Fire "getData" so data manipulation may happen.
493 			this.fire( 'getData', eventData );
494
495 			return eventData.dataValue;
496 		},
497
498 		getSnapshot : function()
499 		{
500 			var data = this.fire( 'getSnapshot' );
501
502 			if ( typeof data != 'string' )
503 			{
504 				var element = this.element;
505 				if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE )
506 					data = element.is( 'textarea' ) ? element.getValue() : element.getHtml();
507 			}
508
509 			return data;
510 		},
511
512 		loadSnapshot : function( snapshot )
513 		{
514 			this.fire( 'loadSnapshot', snapshot );
515 		},
516
517 		/**
518 		 * Sets the editor data. The data must be provided in raw format.
519 		 * @param {String} data HTML code to replace the curent content in the editor.
520 		 * @example
521 		 * CKEDITOR.instances.editor1.<b>setData( '<p>This is the editor data.</p>' )</b>;
522 		 */
523 		setData : function( data )
524 		{
525 			// Fire "setData" so data manipulation may happen.
526 			var eventData = { dataValue : data };
527 			this.fire( 'setData', eventData );
528
529 			this._.data = eventData.dataValue;
530
531 			this.fire( 'afterSetData', eventData );
532 		},
533
534 		/**
535 		 * Inserts HTML into the currently selected position in the editor.
536 		 * @param {String} data HTML code to be inserted into the editor.
537 		 * @example
538 		 * CKEDITOR.instances.editor1.<b>insertHtml( '<p>This is a new paragraph.</p>' )</b>;
539 		 */
540 		insertHtml : function( data )
541 		{
542 			this.fire( 'insertHtml', data );
543 		},
544
545 		/**
546 		 * Inserts an element into the currently selected position in the
547 		 * editor.
548 		 * @param {CKEDITOR.dom.element} element The element to be inserted
549 		 *		into the editor.
550 		 * @example
551 		 * var element = CKEDITOR.dom.element.createFromHtml( '<img src="hello.png" border="0" title="Hello" />' );
552 		 * CKEDITOR.instances.editor1.<b>insertElement( element )</b>;
553 		 */
554 		insertElement : function( element )
555 		{
556 			this.fire( 'insertElement', element );
557 		},
558
559 		checkDirty : function()
560 		{
561 			return ( this.mayBeDirty && this._.previousValue !== this.getSnapshot() );
562 		},
563
564 		resetDirty : function()
565 		{
566 			if ( this.mayBeDirty )
567 				this._.previousValue = this.getSnapshot();
568 		},
569
570 		/**
571 		 * Updates the <textarea> element that has been replaced by the editor with
572 		 * the current data available in the editor.
573 		 * @example
574 		 * CKEDITOR.instances.editor1.updateElement();
575 		 * alert( document.getElementById( 'editor1' ).value );  // The current editor data.
576 		 */
577 		updateElement : function()
578 		{
579 			var element = this.element;
580 			if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE )
581 			{
582 				if ( element.is( 'textarea' ) )
583 					element.setValue( this.getData() );
584 				else
585 					element.setHtml( this.getData() );
586 			}
587 		}
588 	});
589
590 CKEDITOR.on( 'loaded', function()
591 	{
592 		// Run the full initialization for pending editors.
593 		var pending = CKEDITOR.editor._pending;
594 		if ( pending )
595 		{
596 			delete CKEDITOR.editor._pending;
597
598 			for ( var i = 0 ; i < pending.length ; i++ )
599 				pending[ i ]._init();
600 		}
601 	});
602