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, and 235 { 236 fn: null, 237 event: null, 238 deferred: null 239 }, 240 241 // Phase 2: Initial checks have passed; Initialize repository manger. 242 { 243 fn: initRepositoryManager, 244 event: null, 245 deferred: null 246 }, 247 248 // Phase 3: Repository manager is ready for use; commence 249 // initialization of all configured (or default) plugins. 250 { 251 fn: initPluginManager, 252 event: 'aloha-plugins-loaded', 253 deferred: null 254 }, 255 256 // Phase 4: Plugins have all begun their initialization process, but it 257 // is not necessary that their have completed. Start editable 258 // initializing editable, along with their scaffolding UI. 259 // Editables will not be fully initialized however, until 260 // plugins have fully finished initialization. 261 { 262 fn: initEditables, 263 event: null, 264 deferred: null 265 }, 266 267 // Phase 5: The "ready" state. Notify the system that Aloha is fully 268 // loaded, and ready for use. 269 { 270 fn: null, 271 event: 'aloha-ready', 272 deferred: null 273 } 274 ]; 275 276 277 /** 278 * Base Aloha Object 279 * @namespace Aloha 280 * @class Aloha The Aloha base object, which contains all the core functionality 281 * @singleton 282 */ 283 $.extend(true, Aloha, { 284 285 /** 286 * The Aloha Editor Version we are using 287 * It should be set by us and updated for the particular branch 288 * @property 289 */ 290 version: '${version}', 291 292 /** 293 * Array of editables that are managed by Aloha 294 * @property 295 * @type Array 296 */ 297 editables: [], 298 299 /** 300 * The currently active editable is referenced here 301 * @property 302 * @type Aloha.Editable 303 */ 304 activeEditable: null, 305 306 /** 307 * settings object, which will contain all Aloha settings 308 * @cfg {Object} object Aloha's settings 309 */ 310 settings: {}, 311 312 /** 313 * defaults object, which will contain all Aloha defaults 314 * @cfg {Object} object Aloha's settings 315 */ 316 defaults: {}, 317 318 /** 319 * Namespace for ui components 320 */ 321 ui: {}, 322 323 /** 324 * This represents the name of the users OS. Could be: 325 * 'Mac', 'Linux', 'Win', 'Unix', 'Unknown' 326 * @property 327 * @type string 328 */ 329 OSName: 'Unknown', 330 331 /** 332 * A list of loaded plugin names, available after the STAGES.PLUGINS 333 * initialization phase. 334 * 335 * @type {Array.<string>} 336 * @internal 337 */ 338 loadedPlugins: [], 339 340 /** 341 * Maps names of plugins (link) to the base URL (../plugins/common/link). 342 */ 343 _pluginBaseUrlByName: {}, 344 345 /** 346 * Start the initialization process. 347 */ 348 init: function () { 349 Aloha.initialize(phases); 350 }, 351 352 /** 353 * Returns list of loaded plugins (without Bundle name) 354 * 355 * @return array 356 */ 357 getLoadedPlugins: function () { 358 return this.loadedPlugins; 359 }, 360 361 /** 362 * Returns true if a certain plugin is loaded, false otherwise. 363 * 364 * @param {string} plugin Name of plugin 365 * @return {boolean} True if plugin with given name is load. 366 */ 367 isPluginLoaded: function (name) { 368 var loaded = false; 369 $.each(this.loadedPlugins, function (i, plugin) { 370 if (name === plugin.toString()) { 371 loaded = true; 372 return false; 373 } 374 }); 375 return loaded; 376 }, 377 378 /** 379 * Activates editable and deactivates all other Editables. 380 * 381 * @param {Editable} editable the Editable to be activated 382 */ 383 activateEditable: function (editable) { 384 // Because editables may be removed on blur, Aloha.editables.length 385 // is not cached. 386 var editables = Aloha.editables; 387 var i; 388 for (i = 0; i < editables.length; i++) { 389 if (editables[i] !== editable && editables[i].isActive) { 390 editables[i].blur(); 391 } 392 } 393 Aloha.activeEditable = editable; 394 }, 395 396 /** 397 * Returns the current Editable 398 * @return {Editable} returns the active Editable 399 */ 400 getActiveEditable: function () { 401 return Aloha.activeEditable; 402 }, 403 404 /** 405 * Deactivates the active Editable. 406 * 407 * TODO: Would be better named "deactivateActiveEditable". 408 */ 409 deactivateEditable: function () { 410 if (Aloha.activeEditable) { 411 Aloha.activeEditable.blur(); 412 Aloha.activeEditable = null; 413 } 414 }, 415 416 /** 417 * Gets an editable by an ID or null if no Editable with that ID 418 * registered. 419 * 420 * @param {string} id The element id to look for. 421 * @return {Aloha.Editable|null} An editable, or null if none if found 422 * for the given id. 423 */ 424 getEditableById: function (id) { 425 // Because if the element is a textarea, then it's necessary to 426 // route to the editable div. 427 var $editable = $('#' + id); 428 if ($editable.length 429 && 'textarea' === $editable[0].nodeName.toLowerCase()) { 430 id = id + '-aloha'; 431 } 432 var i; 433 for (i = 0; i < Aloha.editables.length; i++) { 434 if (Aloha.editables[i].getId() === id) { 435 return Aloha.editables[i]; 436 } 437 } 438 return null; 439 }, 440 441 /** 442 * Checks whether an object is a registered Aloha Editable. 443 * @param {jQuery} obj the jQuery object to be checked. 444 * @return {boolean} 445 */ 446 isEditable: function (obj) { 447 var i, editablesLength; 448 449 for (i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) { 450 if (Aloha.editables[i].originalObj.get(0) === obj) { 451 return true; 452 } 453 } 454 return false; 455 }, 456 457 /** 458 * Get the nearest editable parent of the DOM element contained in the 459 * given jQuery object. 460 * 461 * @param {jQuery} $obj jQuery unit set containing DOM element. 462 * @return {Aloha.Editable} Editable, or null if none found. 463 */ 464 getEditableHost: function ($obj) { 465 if (!$obj) { 466 return null; 467 } 468 var $editable; 469 $obj.parents().andSelf().each(function () { 470 var i; 471 for (i = 0; i < Aloha.editables.length; i++) { 472 if (Aloha.editables[i].originalObj.get(0) === this) { 473 $editable = Aloha.editables[i]; 474 return false; 475 } 476 } 477 }); 478 return $editable; 479 }, 480 481 /** 482 * Logs a message to the console. 483 * 484 * @param {string} level Level of the log 485 * ("error", "warn" or "info", "debug"). 486 * @param {object} component Component that calls the log. 487 * @param {string} message Log message. 488 * @hide 489 */ 490 log: function (level, component, message) { 491 if (typeof Aloha.Log !== 'undefined') { 492 Aloha.Log.log(level, component, message); 493 } 494 }, 495 496 /** 497 * Register the given editable. 498 * 499 * @param {Editable} editable to register. 500 * @hide 501 */ 502 registerEditable: function (editable) { 503 Aloha.editables.push(editable); 504 }, 505 506 /** 507 * Unregister the given editable. It will be deactivated and removed 508 * from editables. 509 * 510 * @param {Editable} editable The editable to unregister. 511 * @hide 512 */ 513 unregisterEditable: function (editable) { 514 var index = $.inArray(editable, Aloha.editables); 515 if (index !== -1) { 516 Aloha.editables.splice(index, 1); 517 } 518 }, 519 520 /** 521 * Check whether at least one editable was modified. 522 * 523 * @return {boolean} True when at least one editable was modified, 524 * false otherwise. 525 */ 526 isModified: function () { 527 var i; 528 for (i = 0; i < Aloha.editables.length; i++) { 529 if (Aloha.editables[i].isModified 530 && Aloha.editables[i].isModified()) { 531 return true; 532 } 533 } 534 return false; 535 }, 536 537 /** 538 * Determines the Aloha Url. 539 * 540 * @return {String} Aloha's baseUrl setting or "" if not set. 541 */ 542 getAlohaUrl: function (suffix) { 543 return Aloha.settings.baseUrl || ''; 544 }, 545 546 /** 547 * Gets the plugin's url. 548 * 549 * @param {string} name The name with which the plugin was registered 550 * with. 551 * @return {string} The fully qualified url of this plugin. 552 */ 553 getPluginUrl: function (name) { 554 if (name) { 555 return null; 556 } 557 var url = Aloha.settings._pluginBaseUrlByName[name]; 558 if (url) { 559 // Check if url is absolute and attach base url if it is not. 560 if (!url.match("^(\/|http[s]?:).*")) { 561 url = Aloha.getAlohaUrl() + '/' + url; 562 } 563 } 564 return url; 565 }, 566 567 /** 568 * Disable object resizing by executing command 'enableObjectResizing', 569 * if the browser supports this. 570 */ 571 disableObjectResizing: function () { 572 try { 573 // This will disable browsers image resizing facilities in 574 // order disable resize handles. 575 var supported; 576 try { 577 supported = document.queryCommandSupported('enableObjectResizing'); 578 } catch (e) { 579 supported = false; 580 Aloha.Log.log('enableObjectResizing is not supported.'); 581 } 582 if (supported) { 583 document.execCommand('enableObjectResizing', false, false); 584 Aloha.Log.log('enableObjectResizing disabled.'); 585 } 586 } catch (e2) { 587 Aloha.Log.error(e2, 'Could not disable enableObjectResizing'); 588 // this is just for others, who will not support disabling enableObjectResizing 589 } 590 }, 591 592 /** 593 * Human-readable string representation of this. 594 * 595 * @hide 596 */ 597 toString: function () { 598 return 'Aloha'; 599 } 600 }); 601 602 return Aloha; 603 }); 604