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