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