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 * 238 * @return array 239 * @internal 240 */ 241 getPluginsToBeLoaded: function() { 242 // look for data-aloha-plugins attributes and load values 243 var 244 plugins = jQuery('[data-aloha-plugins]').data('aloha-plugins'); 245 246 // Determine Plugins 247 if ( typeof plugins === 'string' && plugins !== "") { 248 return plugins.replace(/\s+/g, '').split(','); 249 } 250 // Return 251 return []; 252 253 }, 254 255 /** 256 * Returns list of loaded plugins (without Bundle name) 257 * 258 * @return array 259 */ 260 getLoadedPlugins: function() { 261 return this.loadedPlugins; 262 }, 263 264 /** 265 * Returns true if a certain plugin is loaded, false otherwise. 266 */ 267 isPluginLoaded: function(pluginName) { 268 var found = false; 269 jQuery.each(this.loadedPlugins, function() { 270 if (pluginName.toString() === this.toString()) { 271 found = true; 272 } 273 }); 274 return found; 275 }, 276 277 /** 278 * Initialise Aloha 279 */ 280 initAloha: function(next){ 281 // check browser version on init 282 // this has to be revamped, as 283 if (jQuery.browser.webkit && parseFloat(jQuery.browser.version) < 532.5 || // Chrome/Safari 4 284 jQuery.browser.mozilla && parseFloat(jQuery.browser.version) < 1.9 || // FF 3.5 285 jQuery.browser.msie && jQuery.browser.version < 7 || // IE 7 286 jQuery.browser.opera && jQuery.browser.version < 11 ) { // right now, Opera needs some work 287 if (window.console && window.console.log) { 288 window.console.log( 'Your browser is not supported.' ); 289 } 290 } 291 292 // register the body click event to blur editables 293 jQuery('html').mousedown(function(e) { 294 // if an Ext JS modal is visible, we don't want to loose the focus on 295 // the editable as we assume that the user must have clicked somewhere 296 // in the modal... where else could he click? 297 // loosing the editable focus in this case hinders correct table 298 // column/row deletion, as the table module will clean it's selection 299 // as soon as the editable is deactivated. Fusubscriberthermore you'd have to 300 // refocus the editable again, which is just strange UX 301 if (Aloha.activeEditable && !Aloha.isMessageVisible() && !Aloha.eventHandled) { 302 Aloha.activeEditable.blur(); 303 Aloha.activeEditable = null; 304 } 305 }).mouseup(function(e) { 306 Aloha.eventHandled = false; 307 }); 308 309 // Initialise the base path to the aloha files 310 Aloha.settings.base = Aloha.getAlohaUrl(); 311 312 // initialize the Log 313 Aloha.Log.init(); 314 315 // initialize the error handler for general javascript errors 316 if ( Aloha.settings.errorhandling ) { 317 window.onerror = function (msg, url, linenumber) { 318 Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' + url + '\nLine Number: ' + linenumber); 319 // TODO eventually add a message to the message line? 320 return true; 321 }; 322 } 323 324 // OS detection 325 if (navigator.appVersion.indexOf('Win') != -1) { 326 Aloha.OSName = 'Win'; 327 } 328 if (navigator.appVersion.indexOf('Mac') != -1) { 329 Aloha.OSName = 'Mac'; 330 } 331 if (navigator.appVersion.indexOf('X11') != -1) { 332 Aloha.OSName = 'Unix'; 333 } 334 if (navigator.appVersion.indexOf('Linux') != -1) { 335 Aloha.OSName = 'Linux'; 336 } 337 338 try { 339 // this will disable browsers image resizing facilities 340 // disable resize handles 341 var supported; 342 try { 343 supported = document.queryCommandSupported( 'enableObjectResizing' ); 344 } catch ( e ) { 345 supported = false; 346 Aloha.Log.log( 'enableObjectResizing is not supported.' ); 347 } 348 349 if ( supported ) { 350 document.execCommand( 'enableObjectResizing', false, false); 351 Aloha.Log.log( 'enableObjectResizing disabled.' ); 352 } 353 } catch (e) { 354 Aloha.Log.error( e, 'Could not disable enableObjectResizing' ); 355 // this is just for others, who will not support disabling enableObjectResizing 356 } 357 // Forward 358 next(); 359 }, 360 361 /** 362 * Loads plugins Aloha 363 * @return void 364 */ 365 initPlugins: function (next) { 366 PluginManager.init(function(){ 367 next(); 368 }, this.getLoadedPlugins() ); 369 }, 370 371 /** 372 * Loads GUI components 373 * @return void 374 */ 375 initGui: function (next) { 376 377 Aloha.RepositoryManager.init(); 378 379 // activate registered editables 380 for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 381 if ( !Aloha.editables[i].ready ) { 382 Aloha.editables[i].init(); 383 } 384 } 385 386 // Forward 387 next(); 388 }, 389 390 /** 391 * Activates editable and deactivates all other Editables 392 * @param {Editable} editable the Editable to be activated 393 * @return void 394 */ 395 activateEditable: function (editable) { 396 397 // blur all editables, which are currently active 398 for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 399 if (Aloha.editables[i] != editable && Aloha.editables[i].isActive) { 400 Aloha.editables[i].blur(); 401 } 402 } 403 404 Aloha.activeEditable = editable; 405 }, 406 407 /** 408 * Returns the current Editable 409 * @return {Editable} returns the active Editable 410 */ 411 getActiveEditable: function() { 412 return Aloha.activeEditable; 413 }, 414 415 /** 416 * deactivated the current Editable 417 * @return void 418 */ 419 deactivateEditable: function () { 420 421 if ( typeof Aloha.activeEditable === 'undefined' || Aloha.activeEditable === null ) { 422 return; 423 } 424 425 // blur the editable 426 Aloha.activeEditable.blur(); 427 Aloha.activeEditable = null; 428 }, 429 430 /** 431 * Gets an editable by an ID or null if no Editable with that ID registered. 432 * @param {string} id the element id to look for. 433 * @return {Aloha.Editable} editable 434 */ 435 getEditableById: function (id) { 436 437 // if the element is a textarea than route to the editable div 438 if (jQuery('#'+id).get(0).nodeName.toLowerCase() === 'textarea' ) { 439 id = id + '-aloha'; 440 } 441 442 // serach all editables for id 443 for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 444 if (Aloha.editables[i].getId() == id) { 445 return Aloha.editables[i]; 446 } 447 } 448 449 return null; 450 }, 451 452 /** 453 * Checks wheater an object is a registered Aloha Editable. 454 * @param {jQuery} obj the jQuery object to be checked. 455 * @return {boolean} 456 */ 457 isEditable: function (obj) { 458 for (var i=0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 459 if ( Aloha.editables[i].originalObj.get(0) === obj ) { 460 return true; 461 } 462 } 463 return false; 464 }, 465 466 /** 467 * Logs a message to the console 468 * @param level Level of the log ("error", "warn" or "info", "debug") 469 * @param component Component that calls the log 470 * @param message log message 471 * @return void 472 * @hide 473 */ 474 log: function(level, component, message) { 475 if (typeof Aloha.Log !== "undefined") 476 Aloha.Log.log(level, component, message); 477 }, 478 479 /** 480 * Register the given editable 481 * @param editable editable to register 482 * @return void 483 * @hide 484 */ 485 registerEditable: function (editable) { 486 Aloha.editables.push(editable); 487 }, 488 489 /** 490 * Unregister the given editable. It will be deactivated and removed from editables. 491 * @param editable editable to unregister 492 * @return void 493 * @hide 494 */ 495 496 unregisterEditable: function (editable) { 497 498 // Find the index 499 var id = Aloha.editables.indexOf( editable ); 500 // Remove it if really found! 501 if (id != -1) { 502 Aloha.editables.splice(id, 1); 503 } 504 }, 505 506 /** 507 * String representation 508 * @hide 509 */ 510 toString: function () { 511 return 'Aloha'; 512 }, 513 514 /** 515 * Check whether at least one editable was modified 516 * @method 517 * @return {boolean} true when at least one editable was modified, false if not 518 */ 519 isModified: function () { 520 // check if something needs top be saved 521 for (var i = 0; i < Aloha.editables.length; i++) { 522 if (Aloha.editables[i].isModified && Aloha.editables[i].isModified()) { 523 return true; 524 } 525 } 526 527 return false; 528 }, 529 530 /** 531 * Determines the Aloha Url 532 * @method 533 * @return {String} alohaUrl 534 */ 535 getAlohaUrl: function( suffix ) { 536 // aloha base path is defined by a script tag with 2 data attributes 537 var requireJs = jQuery('[data-aloha-plugins]'), 538 baseUrl = ( requireJs.length ) ? requireJs[0].src.replace( /\/?aloha.js$/ , '' ) : ''; 539 540 return baseUrl; 541 }, 542 543 /** 544 * Gets the Plugin Url 545 * @method 546 * @param {String} name 547 * @return {String} url 548 */ 549 getPluginUrl: function (name) { 550 var url; 551 552 if (name) { 553 url = pluginPaths[name]; 554 if(url) { 555 //Check if url is absolute and attach base url if it is not 556 if(!url.match("^(\/|http[s]?:).*")) { 557 url = Aloha.getAlohaUrl() + '/' + url; 558 } 559 } 560 } 561 562 return url; 563 } 564 565 }); 566 567 return Aloha; 568 }); 569