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 227 // before at 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 initialize logging. 236 { 237 fn: initAloha, 238 event: null, 239 deferred: null 240 }, 241 242 // Phase 2: Initial checks have passed; Initialize 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 all 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 their have completed. Start editable 259 // initializing editable, along with their scaffolding UI. 260 // Editables will not be fully initialized however, until 261 // plugins have fully 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 Aloha.activeEditable = null; 414 } 415 }, 416 417 /** 418 * Gets an editable by an ID or null if no Editable with that ID 419 * registered. 420 * 421 * @param {string} id The element id to look for. 422 * @return {Aloha.Editable|null} An editable, or null if none if found 423 * for the given id. 424 */ 425 getEditableById: function (id) { 426 // Because if the element is a textarea, then it's necessary to 427 // route to the editable div. 428 var $editable = $('#' + id); 429 if ($editable.length 430 && 'textarea' === $editable[0].nodeName.toLowerCase()) { 431 id = id + '-aloha'; 432 } 433 var i; 434 for (i = 0; i < Aloha.editables.length; i++) { 435 if (Aloha.editables[i].getId() === id) { 436 return Aloha.editables[i]; 437 } 438 } 439 return null; 440 }, 441 442 /** 443 * Checks whether an object is a registered Aloha Editable. 444 * @param {jQuery} obj the jQuery object to be checked. 445 * @return {boolean} 446 */ 447 isEditable: function (obj) { 448 var i, editablesLength; 449 450 for (i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 451 if (Aloha.editables[i].originalObj.get(0) === obj) { 452 return true; 453 454 } 455 } 456 return false; 457 }, 458 459 /** 460 * Get the nearest editable parent of the DOM element contained in the 461 * given jQuery object. 462 * 463 * @param {jQuery} $obj jQuery unit set containing DOM element. 464 * @return {Aloha.Editable} Editable, or null if none found. 465 */ 466 getEditableHost: function ($obj) { 467 if (!$obj) { 468 return null; 469 } 470 var $editable; 471 $obj.parents().andSelf().each(function () { 472 var i; 473 for (i = 0; i < Aloha.editables.length; i++) { 474 if (Aloha.editables[i].originalObj.get(0) === this) { 475 $editable = Aloha.editables[i]; 476 return false; 477 } 478 } 479 }); 480 return $editable; 481 }, 482 483 /** 484 * Logs a message to the console. 485 * 486 * @param {string} level Level of the log 487 * ("error", "warn" or "info", "debug"). 488 * @param {object} component Component that calls the log. 489 * @param {string} message Log message. 490 * @hide 491 */ 492 log: function (level, component, message) { 493 if (typeof Aloha.Log !== 'undefined') { 494 Aloha.Log.log(level, component, message); 495 } 496 }, 497 498 /** 499 * Register the given editable. 500 * 501 * @param {Editable} editable to register. 502 * @hide 503 */ 504 registerEditable: function (editable) { 505 Aloha.editables.push(editable); 506 }, 507 508 /** 509 * Unregister the given editable. It will be deactivated and removed 510 * from editables. 511 * 512 * @param {Editable} editable The editable to unregister. 513 * @hide 514 */ 515 unregisterEditable: function (editable) { 516 var index = $.inArray(editable, Aloha.editables); 517 if (index !== -1) { 518 Aloha.editables.splice(index, 1); 519 } 520 }, 521 522 /** 523 * Check whether at least one editable was modified. 524 * 525 * @return {boolean} True when at least one editable was modified, 526 * false otherwise. 527 */ 528 isModified: function () { 529 var i; 530 for (i = 0; i < Aloha.editables.length; i++) { 531 if (Aloha.editables[i].isModified 532 && Aloha.editables[i].isModified()) { 533 return true; 534 } 535 } 536 return false; 537 }, 538 539 /** 540 * Determines the Aloha Url. 541 * 542 * @return {String} Aloha's baseUrl setting or "" if not set. 543 */ 544 getAlohaUrl: function (suffix) { 545 return Aloha.settings.baseUrl || ''; 546 }, 547 548 /** 549 * Gets the plugin's url. 550 * 551 * @param {string} name The name with which the plugin was registered 552 * with. 553 * @return {string} The fully qualified url of this plugin. 554 */ 555 getPluginUrl: function (name) { 556 if (name) { 557 return null; 558 } 559 var url = Aloha.settings._pluginBaseUrlByName[name]; 560 if (url) { 561 // Check if url is absolute and attach base url if it is not. 562 if (!url.match("^(\/|http[s]?:).*")) { 563 url = Aloha.getAlohaUrl() + '/' + url; 564 } 565 } 566 return url; 567 }, 568 569 /** 570 * Disable object resizing by executing command 'enableObjectResizing', 571 * if the browser supports this. 572 */ 573 disableObjectResizing: function () { 574 try { 575 // This will disable browsers image resizing facilities in 576 // order disable resize handles. 577 var supported; 578 try { 579 supported = document.queryCommandSupported('enableObjectResizing'); 580 } catch (e) { 581 supported = false; 582 Aloha.Log.log('enableObjectResizing is not supported.'); 583 } 584 if (supported) { 585 document.execCommand('enableObjectResizing', false, false); 586 Aloha.Log.log('enableObjectResizing disabled.'); 587 } 588 } catch (e2) { 589 Aloha.Log.error(e2, 'Could not disable enableObjectResizing'); 590 // this is just for others, who will not support disabling enableObjectResizing 591 } 592 }, 593 594 /** 595 * Human-readable string representation of this. 596 * 597 * @hide 598 */ 599 toString: function () { 600 return 'Aloha'; 601 } 602 }); 603 604 return Aloha; 605 }); 606