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 0 < $target.closest('.aloha-dialog').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 Aloha.OSName = 'Win'; 124 } else if (navigator.appVersion.indexOf('Mac') !== -1) { 125 Aloha.OSName = 'Mac'; 126 } else if (navigator.appVersion.indexOf('X11') !== -1) { 127 Aloha.OSName = 'Unix'; 128 } else if (navigator.appVersion.indexOf('Linux') !== -1) { 129 Aloha.OSName = 'Linux'; 130 } 131 132 registerEvents(); 133 Aloha.settings.base = Aloha.getAlohaUrl(); 134 Aloha.Log.init(); 135 136 // Initialize error handler for general javascript errors. 137 if (Aloha.settings.errorhandling) { 138 window.onerror = function (msg, url, line) { 139 Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' + 140 url + '\nLine Number: ' + line); 141 return true; 142 }; 143 } 144 145 event(); 146 next(); 147 } 148 149 /** 150 * Initialize managers 151 * 152 * @param {function} event Event to trigger after completing tasks. 153 * @param {function} next Function to call after completing tasks. 154 */ 155 function initRepositoryManager(event, next) { 156 Aloha.RepositoryManager.init(); 157 event(); 158 next(); 159 } 160 161 /** 162 * Initialize Aloha plugins. 163 * 164 * 165 * @param {function} event Event to trigger after completing tasks. 166 * @param {function} next Callback that will be invoked after all plugins 167 * have been initialized. Whereas plugins are loaded 168 * synchronously, plugins may initialize 169 * asynchronously. 170 */ 171 function initPluginManager(event, next) { 172 // Because if there are no loadedPlugins specified, then the default is 173 // to initialized all available plugins. 174 if (0 === Aloha.loadedPlugins.length) { 175 var plugins = PluginManager.plugins; 176 var plugin; 177 for (plugin in plugins) { 178 if (plugins.hasOwnProperty(plugin)) { 179 Aloha.loadedPlugins.push(plugin); 180 } 181 } 182 } 183 184 var fired = false; 185 186 PluginManager.init(function () { 187 if (!fired) { 188 event(); 189 fired = true; 190 } 191 next(); 192 }, Aloha.loadedPlugins); 193 194 if (!fired) { 195 event(); 196 fired = true; 197 } 198 } 199 200 /** 201 * Begin initialize editables. 202 * 203 * @param {function} event Event to trigger after completing tasks. 204 * @param {function} next Function to call after completing tasks. 205 */ 206 function initEditables(event, next) { 207 var i; 208 for (i = 0; i < Aloha.editables.length; i++) { 209 if (!Aloha.editables[i].ready) { 210 Aloha.editables[i].init(); 211 } 212 } 213 event(); 214 next(); 215 } 216 217 /** 218 * Initialization phases. 219 * 220 * These stages denote the initialization states which Aloha will go 221 * through from loading to ready. 222 * 223 * Each phase object contains the following properties: 224 * fn : The process that is to be invoked during this phase. 225 * Will take two functions: event() and next(). 226 * event : The event name, which if provided, will be fired after the 227 * phase has been started (optional). 228 * deferred : A $.Deferred() object to hold event handlers until that 229 * initialization phase has been done (optional). 230 * 231 * @type {Array.<phase>} 232 */ 233 var phases = [ 234 // Phase 0: Waiting for initialization to begin. This is the state at 235 // load-time. 236 { 237 fn: null, 238 event: null, 239 deferred: null 240 }, 241 242 // Phase 1: DOM is ready; performing compatibility checks, registering 243 // basic events, and initializing logging. 244 { 245 fn: initAloha, 246 event: null, 247 deferred: null 248 }, 249 250 // Phase 2: Initial checks have passed; Initializing repository manger. 251 { 252 fn: initRepositoryManager, 253 event: null, 254 deferred: null 255 }, 256 257 // Phase 3: Repository manager is ready for use; commence 258 // initialization of configured or default plugins. 259 { 260 fn: initPluginManager, 261 event: 'aloha-plugins-loaded', 262 deferred: null 263 }, 264 265 // Phase 4: Plugins have all begun their initialization process, but it 266 // is not necessary that they have completed. Start 267 // initializing editable, along with their scaffolding UI. 268 // Editables will not be fully initialized however, until 269 // plugins have finished initialization. 270 { 271 fn: initEditables, 272 event: null, 273 deferred: null 274 }, 275 276 // Phase 5: The "ready" state. Notify the system that Aloha is fully 277 // loaded, and ready for use. 278 { 279 fn: null, 280 event: 'aloha-ready', 281 deferred: null 282 } 283 ]; 284 285 286 /** 287 * Base Aloha Object 288 * @namespace Aloha 289 * @class Aloha The Aloha base object, which contains all the core functionality 290 * @singleton 291 */ 292 $.extend(true, Aloha, { 293 294 /** 295 * The Aloha Editor Version we are using 296 * It should be set by us and updated for the particular branch 297 * @property 298 */ 299 version: '${version}', 300 301 /** 302 * Array of editables that are managed by Aloha 303 * @property 304 * @type Array 305 */ 306 editables: [], 307 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