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