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