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