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