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