1 /*! 2 * This file is part of Aloha Editor Project http://aloha-editor.org 3 * Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com 4 * Contributors http://aloha-editor.org/contribution.php 5 * Licensed unter the terms of http://www.aloha-editor.org/license.html 6 *//* 7 * Aloha Editor is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU Affero General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version.* 11 * 12 * Aloha Editor is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 define( 22 23 [ 24 'aloha/jquery', 25 'aloha/pluginmanager' 26 ], 27 28 function ( jQuery, PluginManager ) { 29 "use strict"; 30 31 // Aloha Editor does not support Internet Explorer 6. ExtJS style fixes for 32 // IE6 which are applied through the "ext-ie6" class cause visual bugs in 33 // IE9, and so we remove it so that IE6 fixes are not applied. 34 Aloha.ready(function() { 35 jQuery('.ext-ie6').removeClass('ext-ie6'); 36 }); 37 38 //---------------------------------------- 39 // Private variables 40 //---------------------------------------- 41 42 /** 43 * Maps the names of plugins with their urls for easy assess in the 44 * getPluginUrl method. Hash table that will be populated through the 45 * loadPlugins method. 46 */ 47 var pluginPaths = {}; 48 49 /** 50 * Base Aloha Object 51 * @namespace Aloha 52 * @class Aloha The Aloha base object, which contains all the core functionality 53 * @singleton 54 */ 55 jQuery.extend(true, Aloha, { 56 57 /** 58 * The Aloha Editor Version we are using 59 * It should be set by us and updated for the particular branch 60 * @property 61 */ 62 version: '0.20.0', 63 64 /** 65 * Array of editables that are managed by Aloha 66 * @property 67 * @type Array 68 */ 69 editables: [], 70 71 /** 72 * The currently active editable is referenced here 73 * @property 74 * @type Aloha.Editable 75 */ 76 activeEditable: null, 77 78 /** 79 * settings object, which will contain all Aloha settings 80 * @cfg {Object} object Aloha's settings 81 */ 82 settings: {}, 83 84 /** 85 * defaults object, which will contain all Aloha defaults 86 * @cfg {Object} object Aloha's settings 87 */ 88 defaults: {}, 89 90 /** 91 * Namespace for ui components 92 */ 93 ui: {}, 94 95 /** 96 * This represents the name of the users OS. Could be: 97 * 'Mac', 'Linux', 'Win', 'Unix', 'Unknown' 98 * @property 99 100 * @type string 101 */ 102 OSName: 'Unknown', 103 104 /** 105 * Which stage is the aloha init process at? 106 * @property 107 * @type string 108 */ 109 stage: 'loadingAloha', 110 111 /** 112 * A list of loaded plugin names. Available after the 113 * "loadPlugins" stage. 114 * 115 * @property 116 * @type array 117 * @internal 118 */ 119 loadedPlugins: [], 120 121 requirePaths: [], 122 /** 123 * Initialize the initialization process 124 */ 125 init: function () { 126 127 // merge defaults and settings and provide all in settings 128 Aloha.settings = jQuery.extendObjects( true, {}, Aloha.defaults, Aloha.settings ); 129 130 // initialize rangy. This is probably necessary here, 131 // because due to the current loading mechanism, rangy 132 // doesn't initialize itself in all browsers 133 if (window.rangy) { 134 window.rangy.init(); 135 } 136 137 // Load & Initialise 138 Aloha.stage = 'loadPlugins'; 139 Aloha.loadPlugins(function(){ 140 Aloha.stage = 'initAloha'; 141 Aloha.initAloha(function(){ 142 Aloha.stage = 'initPlugins'; 143 Aloha.initPlugins(function(){ 144 Aloha.stage = 'initGui'; 145 Aloha.initGui(function(){ 146 Aloha.stage = 'alohaReady'; 147 Aloha.trigger('aloha-ready'); 148 }); 149 }); 150 }); 151 }); 152 }, 153 154 /** 155 * Load Plugins 156 */ 157 loadPlugins: function (next) { 158 // contains an array like [common/format, common/block] 159 var configuredPluginsWithBundle = this.getPluginsToBeLoaded(); 160 161 if (configuredPluginsWithBundle.length) { 162 var paths = {}, 163 pluginNames = [], 164 requiredInitializers = [], 165 pathsToPlugins = {}; 166 167 // Background: We do not use CommonJS packages for our Plugins 168 // as this breaks the loading order when these modules have 169 // other dependencies. 170 // We "emulate" the commonjs modules with the path mapping. 171 /* require( 172 * { paths: { 173 * 'format': 'plugins/common/format/lib', 174 * 'format/nls': 'plugins/common/format/nls', 175 * ... for every plugin ... 176 * } 177 * }, 178 * ['format/format-plugin', ... for every plugin ...], 179 * next <-- when everything is loaded, we continue 180 */ 181 jQuery.each(configuredPluginsWithBundle, function (i, configuredPluginWithBundle) { 182 var tmp, bundleName, pluginName, bundlePath = ''; 183 184 tmp = configuredPluginWithBundle.split('/'); 185 bundleName = tmp[0]; 186 pluginName = tmp[1]; 187 188 // TODO assertion if pluginName or bundleName NULL _-> ERROR!! 189 190 if (Aloha.settings.basePath) { 191 bundlePath = Aloha.settings.basePath; 192 } 193 194 if (Aloha.settings.bundles && Aloha.settings.bundles[bundleName]) { 195 bundlePath += Aloha.settings.bundles[bundleName]; 196 } else { 197 bundlePath += '../plugins/' + bundleName; 198 } 199 200 pluginNames.push(pluginName); 201 paths[pluginName] = bundlePath + '/' + pluginName + '/lib'; 202 203 pathsToPlugins[pluginName] = bundlePath + '/' + pluginName; 204 205 // As the "nls" path lies NOT inside /lib/, but is a sibling to /lib/, we need 206 // to register it explicitely. The same goes for the "css" folder. 207 jQuery.each(['nls', 'css', 'vendor', 'res'], function() { 208 paths[pluginName + '/' + this] = bundlePath + '/' + pluginName + '/' + this; 209 }); 210 211 requiredInitializers.push(pluginName + '/' + pluginName + '-plugin'); 212 }); 213 214 this.loadedPlugins = pluginNames; 215 this.requirePaths = paths; 216 217 // Main Require.js loading call, which fetches all the plugins. 218 require( 219 { 220 context: 'aloha', 221 paths: paths, 222 locale: this.settings.locale || this.defaults.locale || 'en' 223 }, 224 requiredInitializers, 225 next 226 ); 227 228 pluginPaths = pathsToPlugins; 229 } else { 230 next(); 231 } 232 }, 233 234 /** 235 * Fetches plugins the user wants to have loaded. Returns all plugins the user 236 * has specified with the data-plugins property as array, with the bundle 237 * name in front. 238 * It's also possible to use 'Aloha.settings.plugins.load' to define the plugins 239 * to load. 240 * 241 * @return array 242 * @internal 243 */ 244 getPluginsToBeLoaded: function() { 245 // look for data-aloha-plugins attributes and load values 246 var 247 plugins = jQuery('[data-aloha-plugins]').data('aloha-plugins'); 248 249 // load aloha plugins from config 250 if ( typeof Aloha.settings.plugins != 'undefined' && typeof Aloha.settings.plugins.load != 'undefined' ) { 251 plugins = Aloha.settings.plugins.load; 252 if (plugins instanceof Array) { 253 return plugins; 254 } 255 } 256 257 // Determine Plugins 258 if ( typeof plugins === 'string' && plugins !== "") { 259 return plugins.replace(/\s+/g, '').split(','); 260 } 261 // Return 262 return []; 263 }, 264 265 /** 266 * Returns list of loaded plugins (without Bundle name) 267 * 268 * @return array 269 */ 270 getLoadedPlugins: function() { 271 return this.loadedPlugins; 272 }, 273 274 /** 275 * Returns true if a certain plugin is loaded, false otherwise. 276 */ 277 isPluginLoaded: function(pluginName) { 278 var found = false; 279 jQuery.each(this.loadedPlugins, function() { 280 if (pluginName.toString() === this.toString()) { 281 found = true; 282 } 283 }); 284 return found; 285 }, 286 287 /** 288 * Initialise Aloha 289 */ 290 initAloha: function(next){ 291 // check browser version on init 292 // this has to be revamped, as 293 if (jQuery.browser.webkit && parseFloat(jQuery.browser.version) < 532.5 || // Chrome/Safari 4 294 jQuery.browser.mozilla && parseFloat(jQuery.browser.version) < 1.9 || // FF 3.5 295 jQuery.browser.msie && jQuery.browser.version < 7 || // IE 7 296 jQuery.browser.opera && jQuery.browser.version < 11 ) { // right now, Opera needs some work 297 if (window.console && window.console.log) { 298 window.console.log( 'Your browser is not supported.' ); 299 } 300 } 301 302 // register the body click event to blur editables 303 jQuery('html').mousedown(function(e) { 304 // if an Ext JS modal is visible, we don't want to loose the focus on 305 // the editable as we assume that the user must have clicked somewhere 306 // in the modal... where else could he click? 307 // loosing the editable focus in this case hinders correct table 308 // column/row deletion, as the table module will clean it's selection 309 // as soon as the editable is deactivated. Fusubscriberthermore you'd have to 310 // refocus the editable again, which is just strange UX 311 if (Aloha.activeEditable && !Aloha.isMessageVisible() && !Aloha.eventHandled) { 312 Aloha.activeEditable.blur(); 313 Aloha.activeEditable = null; 314 } 315 }).mouseup(function(e) { 316 Aloha.eventHandled = false; 317 }); 318 319 // Initialise the base path to the aloha files 320 Aloha.settings.base = Aloha.getAlohaUrl(); 321 322 // initialize the Log 323 Aloha.Log.init(); 324 325 // initialize the error handler for general javascript errors 326 if ( Aloha.settings.errorhandling ) { 327 window.onerror = function (msg, url, linenumber) { 328 Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' + url + '\nLine Number: ' + linenumber); 329 // TODO eventually add a message to the message line? 330 return true; 331 }; 332 } 333 334 // OS detection 335 if (navigator.appVersion.indexOf('Win') != -1) { 336 Aloha.OSName = 'Win'; 337 } 338 if (navigator.appVersion.indexOf('Mac') != -1) { 339 Aloha.OSName = 'Mac'; 340 } 341 if (navigator.appVersion.indexOf('X11') != -1) { 342 Aloha.OSName = 'Unix'; 343 } 344 if (navigator.appVersion.indexOf('Linux') != -1) { 345 Aloha.OSName = 'Linux'; 346 } 347 348 try { 349 // this will disable browsers image resizing facilities 350 // disable resize handles 351 var supported; 352 try { 353 supported = document.queryCommandSupported( 'enableObjectResizing' ); 354 } catch ( e ) { 355 supported = false; 356 Aloha.Log.log( 'enableObjectResizing is not supported.' ); 357 } 358 359 if ( supported ) { 360 document.execCommand( 'enableObjectResizing', false, false); 361 Aloha.Log.log( 'enableObjectResizing disabled.' ); 362 } 363 } catch (e) { 364 Aloha.Log.error( e, 'Could not disable enableObjectResizing' ); 365 // this is just for others, who will not support disabling enableObjectResizing 366 } 367 // Forward 368 next(); 369 }, 370 371 /** 372 * Loads plugins Aloha 373 * @return void 374 */ 375 initPlugins: function (next) { 376 PluginManager.init(function(){ 377 next(); 378 }, this.getLoadedPlugins() ); 379 }, 380 381 /** 382 * Loads GUI components 383 * @return void 384 */ 385 initGui: function (next) { 386 387 Aloha.RepositoryManager.init(); 388 389 // activate registered editables 390 for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 391 if ( !Aloha.editables[i].ready ) { 392 Aloha.editables[i].init(); 393 } 394 } 395 396 // Forward 397 next(); 398 }, 399 400 /** 401 * Activates editable and deactivates all other Editables 402 * @param {Editable} editable the Editable to be activated 403 * @return void 404 */ 405 activateEditable: function (editable) { 406 407 // blur all editables, which are currently active 408 for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 409 if (Aloha.editables[i] != editable && Aloha.editables[i].isActive) { 410 Aloha.editables[i].blur(); 411 } 412 } 413 414 Aloha.activeEditable = editable; 415 }, 416 417 /** 418 * Returns the current Editable 419 * @return {Editable} returns the active Editable 420 */ 421 getActiveEditable: function() { 422 return Aloha.activeEditable; 423 }, 424 425 /** 426 * deactivated the current Editable 427 * @return void 428 */ 429 deactivateEditable: function () { 430 431 if ( typeof Aloha.activeEditable === 'undefined' || Aloha.activeEditable === null ) { 432 return; 433 } 434 435 // blur the editable 436 Aloha.activeEditable.blur(); 437 Aloha.activeEditable = null; 438 }, 439 440 /** 441 * Gets an editable by an ID or null if no Editable with that ID registered. 442 * @param {string} id the element id to look for. 443 * @return {Aloha.Editable} editable 444 */ 445 getEditableById: function (id) { 446 447 // if the element is a textarea than route to the editable div 448 if (jQuery('#'+id).get(0).nodeName.toLowerCase() === 'textarea' ) { 449 id = id + '-aloha'; 450 } 451 452 // serach all editables for id 453 for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 454 if (Aloha.editables[i].getId() == id) { 455 return Aloha.editables[i]; 456 } 457 } 458 459 return null; 460 }, 461 462 /** 463 * Checks wheater an object is a registered Aloha Editable. 464 * @param {jQuery} obj the jQuery object to be checked. 465 * @return {boolean} 466 */ 467 isEditable: function (obj) { 468 for (var i=0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 469 if ( Aloha.editables[i].originalObj.get(0) === obj ) { 470 return true; 471 } 472 } 473 return false; 474 }, 475 476 /** 477 * Logs a message to the console 478 * @param level Level of the log ("error", "warn" or "info", "debug") 479 * @param component Component that calls the log 480 * @param message log message 481 * @return void 482 * @hide 483 */ 484 log: function(level, component, message) { 485 if (typeof Aloha.Log !== "undefined") 486 Aloha.Log.log(level, component, message); 487 }, 488 489 /** 490 * Register the given editable 491 * @param editable editable to register 492 * @return void 493 * @hide 494 */ 495 registerEditable: function (editable) { 496 Aloha.editables.push(editable); 497 }, 498 499 /** 500 * Unregister the given editable. It will be deactivated and removed from editables. 501 * @param editable editable to unregister 502 * @return void 503 * @hide 504 */ 505 unregisterEditable: function (editable) { 506 507 // Find the index 508 var id = Aloha.editables.indexOf( editable ); 509 // Remove it if really found! 510 if (id != -1) { 511 Aloha.editables.splice(id, 1); 512 } 513 }, 514 515 /** 516 * String representation 517 * @hide 518 */ 519 toString: function () { 520 return 'Aloha'; 521 }, 522 523 /** 524 * Check whether at least one editable was modified 525 * @method 526 * @return {boolean} true when at least one editable was modified, false if not 527 */ 528 isModified: function () { 529 // check if something needs top be saved 530 for (var i = 0; i < Aloha.editables.length; i++) { 531 if (Aloha.editables[i].isModified && Aloha.editables[i].isModified()) { 532 return true; 533 } 534 } 535 536 return false; 537 }, 538 539 /** 540 * Determines the Aloha Url 541 * Uses Aloha.settings.baseUrl if set. 542 * @method 543 * @return {String} alohaUrl 544 */ 545 getAlohaUrl: function( suffix ) { 546 // aloha base path is defined by a script tag with 2 data attributes 547 var requireJs = jQuery('[data-aloha-plugins]'), 548 baseUrl = ( requireJs.length ) ? requireJs[0].src.replace( /\/?aloha.js$/ , '' ) : ''; 549 550 if ( typeof Aloha.settings.baseUrl === "string" ) { 551 baseUrl = Aloha.settings.baseUrl; 552 } 553 554 return baseUrl; 555 }, 556 557 /** 558 * Gets the plugin's url. 559 * 560 * @method 561 * @param {string} name The name with which the plugin was registered 562 * with. 563 * @return {string} The fully qualified url of this plugin. 564 */ 565 getPluginUrl: function (name) { 566 var url; 567 568 if (name) { 569 url = pluginPaths[name]; 570 if(url) { 571 //Check if url is absolute and attach base url if it is not 572 if(!url.match("^(\/|http[s]?:).*")) { 573 url = Aloha.getAlohaUrl() + '/' + url; 574 } 575 } 576 } 577 578 return url; 579 } 580 581 }); 582 583 return Aloha; 584 }); 585