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