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