1 /*core.js is part of Aloha Editor project http://aloha-editor.org 2 * 3 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 4 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria. 5 * Contributors http://aloha-editor.org/contribution.php 6 * 7 * Aloha Editor is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or 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 General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * 21 * As an additional permission to the GNU GPL version 2, you may distribute 22 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor 23 * source code without the copy of the GNU GPL normally required, 24 * provided you include this license notice and a URL through which 25 * recipients can access the Corresponding Source. 26 */ 27 define([ 28 'jquery', 29 'aloha/pluginmanager' 30 ], function ( 31 $, 32 PluginManager 33 ) { 34 'use strict'; 35 36 var Aloha = window.Aloha; 37 38 /** 39 * Checks whether the current user agent is supported. 40 * 41 * @return {boolean} True if Aloha supports the current browser. 42 */ 43 function isBrowserSupported() { 44 var browser = $.browser; 45 var version = browser.version; 46 return !( 47 // Chrome/Safari 4 48 (browser.webkit && parseFloat(version) < 532.5) || 49 // FF 3.5 50 (browser.mozilla && parseFloat(version) < 1.9) || 51 // IE 7 52 (browser.msie && version < 7) || 53 // Right now Opera needs some work 54 (browser.opera && version < 11) 55 ); 56 } 57 58 /** 59 * Checks whether the given jQuery event originates from an Aloha dialog 60 * element. 61 * 62 * This is used to facilitate a hackish way of preventing blurring 63 * editables when interacting with Aloha UI modals. 64 * 65 * @param {jQuery<Event>} $event 66 * @return {boolean} True if $event is initiated from within an Aloha 67 * dialog element. 68 */ 69 function originatesFromDialog($event) { 70 var $target = $($event.target); 71 return $target.is('.aloha-dialog') 72 || $target.closest('.aloha').length; 73 } 74 75 /** 76 * Registers events on the documents to cause editables to be blurred when 77 * clicking outside of editables. 78 * 79 * Hack: Except when the click originates from a modal dialog. 80 */ 81 function registerEvents() { 82 $('html').mousedown(function ($event) { 83 if (Aloha.activeEditable && !Aloha.eventHandled 84 && !originatesFromDialog($event)) { 85 Aloha.deactivateEditable(); 86 } 87 }).mouseup(function () { 88 Aloha.eventHandled = false; 89 }); 90 } 91 92 /** 93 * Initialize Aloha. 94 * 95 * @param {function} event Event to trigger after completing tasks. 96 * @param {function} next Function to call after completing tasks. 97 */ 98 function initAloha(event, next) { 99 if (!isBrowserSupported()) { 100 var console = window.console; 101 if (console) { 102 var fn = console.error ? 'error' : console.log ? 'log' : null; 103 if (fn) { 104 console[fn]('This browser is not supported'); 105 } 106 } 107 return; 108 } 109 110 // Because different css is to be applied based on what the user-agent 111 // supports. For example: outlines do not render in IE7. 112 if ($.browser.webkit) { 113 $('html').addClass('aloha-webkit'); 114 } else if ($.browser.opera) { 115 $('html').addClass('aloha-opera'); 116 } else if ($.browser.msie) { 117 $('html').addClass('aloha-ie' + parseInt($.browser.version, 10)); 118 } else if ($.browser.mozilla) { 119 $('html').addClass('aloha-mozilla'); 120 } 121 122 if (navigator.appVersion.indexOf('Win') !== -1) { 123 124 Aloha.OSName = 'Win'; 125 } else if (navigator.appVersion.indexOf('Mac') !== -1) { 126 Aloha.OSName = 'Mac'; 127 } else if (navigator.appVersion.indexOf('X11') !== -1) { 128 Aloha.OSName = 'Unix'; 129 } else if (navigator.appVersion.indexOf('Linux') !== -1) { 130 Aloha.OSName = 'Linux'; 131 } 132 133 registerEvents(); 134 Aloha.settings.base = Aloha.getAlohaUrl(); 135 Aloha.Log.init(); 136 137 // Initialize error handler for general javascript errors. 138 if (Aloha.settings.errorhandling) { 139 window.onerror = function (msg, url, line) { 140 Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' + 141 url + '\nLine Number: ' + line); 142 return true; 143 }; 144 } 145 146 event(); 147 next(); 148 } 149 150 /** 151 * Initialize managers 152 * 153 * @param {function} event Event to trigger after completing tasks. 154 * @param {function} next Function to call after completing tasks. 155 */ 156 function initRepositoryManager(event, next) { 157 Aloha.RepositoryManager.init(); 158 event(); 159 next(); 160 } 161 162 /** 163 * Initialize Aloha plugins. 164 * 165 * 166 * @param {function} event Event to trigger after completing tasks. 167 * @param {function} next Callback that will be invoked after all plugins 168 * have been initialized. Whereas plugins are loaded 169 * synchronously, plugins may initialize 170 * asynchronously. 171 */ 172 function initPluginManager(event, next) { 173 // Because if there are no loadedPlugins specified, then the default is 174 // to initialized all available plugins. 175 if (0 === Aloha.loadedPlugins.length) { 176 var plugins = PluginManager.plugins; 177 var plugin; 178 for (plugin in plugins) { 179 if (plugins.hasOwnProperty(plugin)) { 180 Aloha.loadedPlugins.push(plugin); 181 } 182 } 183 } 184 185 var fired = false; 186 187 PluginManager.init(function () { 188 if (!fired) { 189 event(); 190 fired = true; 191 } 192 next(); 193 }, Aloha.loadedPlugins); 194 195 if (!fired) { 196 event(); 197 fired = true; 198 } 199 } 200 201 /** 202 * Begin initialize editables. 203 * 204 * @param {function} event Event to trigger after completing tasks. 205 * @param {function} next Function to call after completing tasks. 206 */ 207 function initEditables(event, next) { 208 var i; 209 for (i = 0; i < Aloha.editables.length; i++) { 210 if (!Aloha.editables[i].ready) { 211 Aloha.editables[i].init(); 212 } 213 } 214 event(); 215 next(); 216 } 217 218 /** 219 * Initialization phases. 220 * 221 * These stages denote the initialization states which Aloha will go 222 * through from loading to ready. 223 * 224 * Each phase object contains the following properties: 225 * fn : The process that is to be invoked during this phase. 226 * Will take two functions: event() and next(). 227 * event : The event name, which if provided, will be fired after the 228 * phase has been started (optional). 229 * deferred : A $.Deferred() object to hold event handlers until that 230 * initialization phase has been done (optional). 231 * 232 * @type {Array.<phase>} 233 */ 234 var phases = [ 235 // Phase 0: Waiting for initialization to begin. This is the state at 236 // load-time. 237 { 238 fn: null, 239 event: null, 240 deferred: null 241 }, 242 243 // Phase 1: DOM is ready; performing compatibility checks, registering 244 // basic events, and initializing logging. 245 { 246 fn: initAloha, 247 event: null, 248 deferred: null 249 }, 250 251 // Phase 2: Initial checks have passed; Initializing repository manger. 252 { 253 fn: initRepositoryManager, 254 event: null, 255 deferred: null 256 }, 257 258 // Phase 3: Repository manager is ready for use; commence 259 // initialization of configured or default plugins. 260 { 261 fn: initPluginManager, 262 event: 'aloha-plugins-loaded', 263 deferred: null 264 }, 265 266 // Phase 4: Plugins have all begun their initialization process, but it 267 // is not necessary that they have completed. Start 268 // initializing editable, along with their scaffolding UI. 269 // Editables will not be fully initialized however, until 270 // plugins have finished initialization. 271 { 272 fn: initEditables, 273 event: null, 274 deferred: null 275 }, 276 277 // Phase 5: The "ready" state. Notify the system that Aloha is fully 278 // loaded, and ready for use. 279 { 280 fn: null, 281 event: 'aloha-ready', 282 deferred: null 283 } 284 ]; 285 286 287 /** 288 * Base Aloha Object 289 * @namespace Aloha 290 * @class Aloha The Aloha base object, which contains all the core functionality 291 * @singleton 292 */ 293 $.extend(true, Aloha, { 294 295 /** 296 * The Aloha Editor Version we are using 297 * It should be set by us and updated for the particular branch 298 * @property 299 */ 300 version: '${version}', 301 302 /** 303 * Array of editables that are managed by Aloha 304 * @property 305 * @type Array 306 */ 307 editables: [], 308 309 /** 310 * The currently active editable is referenced here 311 * @property 312 * @type Aloha.Editable 313 */ 314 activeEditable: null, 315 316 /** 317 * settings object, which will contain all Aloha settings 318 * @cfg {Object} object Aloha's settings 319 */ 320 settings: {}, 321 322 /** 323 * defaults object, which will contain all Aloha defaults 324 * @cfg {Object} object Aloha's settings 325 */ 326 defaults: {}, 327 328 /** 329 * Namespace for ui components 330 */ 331 ui: {}, 332 333 /** 334 * This represents the name of the users OS. Could be: 335 * 'Mac', 'Linux', 'Win', 'Unix', 'Unknown' 336 * @property 337 * @type string 338 */ 339 OSName: 'Unknown', 340 341 /** 342 * A list of loaded plugin names, available after the STAGES.PLUGINS 343 * initialization phase. 344 * 345 * @type {Array.<string>} 346 * @internal 347 */ 348 loadedPlugins: [], 349 350 /** 351 * Maps names of plugins (link) to the base URL (../plugins/common/link). 352 */ 353 _pluginBaseUrlByName: {}, 354 355 /** 356 * Start the initialization process. 357 */ 358 init: function () { 359 Aloha.initialize(phases); 360 }, 361 362 /** 363 * Returns list of loaded plugins (without Bundle name) 364 * 365 * @return array 366 */ 367 getLoadedPlugins: function () { 368 return this.loadedPlugins; 369 }, 370 371 /** 372 * Returns true if a certain plugin is loaded, false otherwise. 373 * 374 * @param {string} plugin Name of plugin 375 * @return {boolean} True if plugin with given name is load. 376 */ 377 isPluginLoaded: function (name) { 378 var loaded = false; 379 $.each(this.loadedPlugins, function (i, plugin) { 380 if (name === plugin.toString()) { 381 loaded = true; 382 return false; 383 } 384 }); 385 return loaded; 386 }, 387 388 /** 389 * Activates editable and deactivates all other Editables. 390 * 391 * @param {Editable} editable the Editable to be activated 392 */ 393 activateEditable: function (editable) { 394 // Because editables may be removed on blur, Aloha.editables.length 395 // is not cached. 396 var editables = Aloha.editables; 397 var i; 398 for (i = 0; i < editables.length; i++) { 399 if (editables[i] !== editable && editables[i].isActive) { 400 editables[i].blur(); 401 } 402 } 403 Aloha.activeEditable = editable; 404 }, 405 406 /** 407 * Returns the current Editable 408 * @return {Editable} returns the active Editable 409 */ 410 getActiveEditable: function () { 411 return Aloha.activeEditable; 412 }, 413 414 /** 415 * Deactivates the active Editable. 416 * 417 * TODO: Would be better named "deactivateActiveEditable". 418 */ 419 deactivateEditable: function () { 420 if (Aloha.activeEditable) { 421 Aloha.activeEditable.blur(); 422 Aloha.activeEditable = null; 423 } 424 }, 425 426 /** 427 * Gets an editable by an ID or null if no Editable with that ID 428 * registered. 429 * 430 * @param {string} id The element id to look for. 431 * @return {Aloha.Editable|null} An editable, or null if none if found 432 * for the given id. 433 */ 434 getEditableById: function (id) { 435 // Because if the element is a textarea, then it's necessary to 436 // route to the editable div. 437 var $editable = $('#' + id); 438 if ($editable.length 439 && 'textarea' === $editable[0].nodeName.toLowerCase()) { 440 id = id + '-aloha'; 441 } 442 var i; 443 for (i = 0; i < Aloha.editables.length; i++) { 444 if (Aloha.editables[i].getId() === id) { 445 return Aloha.editables[i]; 446 } 447 } 448 return null; 449 }, 450 451 /** 452 * Checks whether an object is a registered Aloha Editable. 453 * @param {jQuery} obj the jQuery object to be checked. 454 * @return {boolean} 455 */ 456 isEditable: function (obj) { 457 var i, editablesLength; 458 459 for (i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 460 if (Aloha.editables[i].originalObj.get(0) === obj) { 461 return true; 462 } 463 } 464 return false; 465 }, 466 467 /** 468 * Gets the nearest editable parent of the DOM element contained in the 469 * given jQuery object. 470 * 471 * @param {jQuery} $element jQuery unit set containing DOM element. 472 * @return {Aloha.Editable} Editable, or null if none found. 473 */ 474 getEditableHost: (function () { 475 var getEditableOf = function (editable) { 476 var i; 477 for (i = 0; i < Aloha.editables.length; i++) { 478 if (Aloha.editables[i].originalObj[0] === editable) { 479 return Aloha.editables[i]; 480 } 481 } 482 return null; 483 }; 484 485 return function ($element) { 486 if (!$element || 0 === $element.length) { 487 return null; 488 } 489 var editable = getEditableOf($element[0]); 490 if (!editable) { 491 $element.parents().each(function (__unused__, node) { 492 editable = getEditableOf(node); 493 if (editable) { 494 return false; 495 } 496 }); 497 } 498 return editable; 499 }; 500 }()), 501 502 /** 503 * Logs a message to the console. 504 * 505 * @param {string} level Level of the log 506 * ("error", "warn" or "info", "debug"). 507 * @param {object} component Component that calls the log. 508 * @param {string} message Log message. 509 * @hide 510 */ 511 log: function (level, component, message) { 512 if (typeof Aloha.Log !== 'undefined') { 513 Aloha.Log.log(level, component, message); 514 } 515 }, 516 517 /** 518 * Register the given editable. 519 * 520 * @param {Editable} editable to register. 521 * @hide 522 */ 523 registerEditable: function (editable) { 524 Aloha.editables.push(editable); 525 }, 526 527 /** 528 * Unregister the given editable. It will be deactivated and removed 529 * from editables. 530 * 531 * @param {Editable} editable The editable to unregister. 532 * @hide 533 */ 534 unregisterEditable: function (editable) { 535 var index = $.inArray(editable, Aloha.editables); 536 if (index !== -1) { 537 Aloha.editables.splice(index, 1); 538 } 539 }, 540 541 /** 542 * Check whether at least one editable was modified. 543 * 544 * @return {boolean} True when at least one editable was modified, 545 * false otherwise. 546 */ 547 isModified: function () { 548 var i; 549 for (i = 0; i < Aloha.editables.length; i++) { 550 if (Aloha.editables[i].isModified 551 && Aloha.editables[i].isModified()) { 552 return true; 553 } 554 } 555 return false; 556 }, 557 558 /** 559 * Determines the Aloha Url. 560 * 561 * @return {String} Aloha's baseUrl setting or "" if not set. 562 */ 563 getAlohaUrl: function (suffix) { 564 return Aloha.settings.baseUrl || ''; 565 }, 566 567 /** 568 * Gets the plugin's url. 569 * 570 * @param {string} name The name with which the plugin was registered 571 * with. 572 * @return {string} The fully qualified url of this plugin. 573 */ 574 getPluginUrl: function (name) { 575 if (!name) { 576 return null; 577 } 578 var url = Aloha.settings._pluginBaseUrlByName[name]; 579 if (url) { 580 // Check if url is absolute and attach base url if it is not. 581 if (!url.match("^(\/|http[s]?:).*")) { 582 url = Aloha.getAlohaUrl() + '/' + url; 583 } 584 } 585 return url; 586 }, 587 588 /** 589 * Disable object resizing by executing command 'enableObjectResizing', 590 * if the browser supports this. 591 */ 592 disableObjectResizing: function () { 593 try { 594 // This will disable browsers image resizing facilities in 595 // order disable resize handles. 596 var supported; 597 try { 598 supported = document.queryCommandSupported('enableObjectResizing'); 599 } catch (e) { 600 supported = false; 601 Aloha.Log.log('enableObjectResizing is not supported.'); 602 } 603 if (supported) { 604 document.execCommand('enableObjectResizing', false, false); 605 Aloha.Log.log('enableObjectResizing disabled.'); 606 } 607 } catch (e2) { 608 Aloha.Log.error(e2, 'Could not disable enableObjectResizing'); 609 // this is just for others, who will not support disabling enableObjectResizing 610 } 611 }, 612 613 /** 614 * Human-readable string representation of this. 615 * 616 * @hide 617 */ 618 toString: function () { 619 return 'Aloha'; 620 } 621 }); 622 623 return Aloha; 624 }); 625