text: // 4.1.9 (2015-03-10)
  
  /**
   * Compiled inline version. (Library mode)
   */
  
  /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
  /*globals $code */
  
  (function(exports, undefined) {
  	"use strict";
  
  	var modules = {};
  
  	function require(ids, callback) {
  		var module, defs = [];
  
  		for (var i = 0; i < ids.length; ++i) {
  			module = modules[ids[i]] || resolve(ids[i]);
  			if (!module) {
  				throw 'module definition dependecy not found: ' + ids[i];
  			}
  
  			defs.push(module);
  		}
  
  		callback.apply(null, defs);
  	}
  
  	function define(id, dependencies, definition) {
  		if (typeof id !== 'string') {
  			throw 'invalid module definition, module id must be defined and be a string';
  		}
  
  		if (dependencies === undefined) {
  			throw 'invalid module definition, dependencies must be specified';
  		}
  
  		if (definition === undefined) {
  			throw 'invalid module definition, definition function must be specified';
  		}
  
  		require(dependencies, function() {
  			modules[id] = definition.apply(null, arguments);
  		});
  	}
  
  	function defined(id) {
  		return !!modules[id];
  	}
  
  	function resolve(id) {
  		var target = exports;
  		var fragments = id.split(/[.\/]/);
  
  		for (var fi = 0; fi < fragments.length; ++fi) {
  			if (!target[fragments[fi]]) {
  				return;
  			}
  
  			target = target[fragments[fi]];
  		}
  
  		return target;
  	}
  
  	function expose(ids) {
  		for (var i = 0; i < ids.length; i++) {
  			var target = exports;
  			var id = ids[i];
  			var fragments = id.split(/[.\/]/);
  
  			for (var fi = 0; fi < fragments.length - 1; ++fi) {
  				if (target[fragments[fi]] === undefined) {
  					target[fragments[fi]] = {};
  				}
  
  				target = target[fragments[fi]];
  			}
  
  			target[fragments[fragments.length - 1]] = modules[id];
  		}
  	}
  
  // Included from: js/maya/classes/dom/EventUtils.js
  
  /**
   * EventUtils.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*jshint loopfunc:true*/
  /*eslint no-loop-func:0 */
  
  /**
   * This class wraps the browsers native event logic with more convenient methods.
   *
   * @class maya.dom.EventUtils
   */
  define("maya/dom/EventUtils", [], function() {
  	"use strict";
  
  	var eventExpandoPrefix = "mce-data-";
  	var mouseEventRe = /^(?:mouse|contextmenu)|click/;
  	var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1};
  
  	/**
  	 * Binds a native event to a callback on the speified target.
  	 */
  	function addEvent(target, name, callback, capture) {
  		if (target.addEventListener) {
  			target.addEventListener(name, callback, capture || false);
  		} else if (target.attachEvent) {
  			target.attachEvent('on' + name, callback);
  		}
  	}
  
  	/**
  	 * Unbinds a native event callback on the specified target.
  	 */
  	function removeEvent(target, name, callback, capture) {
  		if (target.removeEventListener) {
  			target.removeEventListener(name, callback, capture || false);
  		} else if (target.detachEvent) {
  			target.detachEvent('on' + name, callback);
  		}
  	}
  
  	/**
  	 * Normalizes a native event object or just adds the event specific methods on a custom event.
  	 */
  	function fix(originalEvent, data) {
  		var name, event = data || {}, undef;
  
  		// Dummy function that gets replaced on the delegation state functions
  		function returnFalse() {
  			return false;
  		}
  
  		// Dummy function that gets replaced on the delegation state functions
  		function returnTrue() {
  			return true;
  		}
  
  		// Copy all properties from the original event
  		for (name in originalEvent) {
  			// layerX/layerY is deprecated in Chrome and produces a warning
  			if (!deprecated[name]) {
  				event[name] = originalEvent[name];
  			}
  		}
  
  		// Normalize target IE uses srcElement
  		if (!event.target) {
  			event.target = event.srcElement || document;
  		}
  
  		// Calculate pageX/Y if missing and clientX/Y available
  		if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
  			var eventDoc = event.target.ownerDocument || document;
  			var doc = eventDoc.documentElement;
  			var body = eventDoc.body;
  
  			event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
  				(doc && doc.clientLeft || body && body.clientLeft || 0);
  
  			event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
  				(doc && doc.clientTop || body && body.clientTop || 0);
  		}
  
  		// Add preventDefault method
  		event.preventDefault = function() {
  			event.isDefaultPrevented = returnTrue;
  
  			// Execute preventDefault on the original event object
  			if (originalEvent) {
  				if (originalEvent.preventDefault) {
  					originalEvent.preventDefault();
  				} else {
  					originalEvent.returnValue = false; // IE
  				}
  			}
  		};
  
  		// Add stopPropagation
  		event.stopPropagation = function() {
  			event.isPropagationStopped = returnTrue;
  
  			// Execute stopPropagation on the original event object
  			if (originalEvent) {
  				if (originalEvent.stopPropagation) {
  					originalEvent.stopPropagation();
  				} else {
  					originalEvent.cancelBubble = true; // IE
  				}
  			}
  		};
  
  		// Add stopImmediatePropagation
  		event.stopImmediatePropagation = function() {
  			event.isImmediatePropagationStopped = returnTrue;
  			event.stopPropagation();
  		};
  
  		// Add event delegation states
  		if (!event.isDefaultPrevented) {
  			event.isDefaultPrevented = returnFalse;
  			event.isPropagationStopped = returnFalse;
  			event.isImmediatePropagationStopped = returnFalse;
  		}
  
  		return event;
  	}
  
  	/**
  	 * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
  	 * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
  	 */
  	function bindOnReady(win, callback, eventUtils) {
  		var doc = win.document, event = {type: 'ready'};
  
  		if (eventUtils.domLoaded) {
  			callback(event);
  			return;
  		}
  
  		// Gets called when the DOM is ready
  		function readyHandler() {
  			if (!eventUtils.domLoaded) {
  				eventUtils.domLoaded = true;
  				callback(event);
  			}
  		}
  
  		function waitForDomLoaded() {
  			// Check complete or interactive state if there is a body
  			// element on some iframes IE 8 will produce a null body
  			if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
  				removeEvent(doc, "readystatechange", waitForDomLoaded);
  				readyHandler();
  			}
  		}
  
  		function tryScroll() {
  			try {
  				// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
  				// http://javascript.nwbox.com/IEContentLoaded/
  				doc.documentElement.doScroll("left");
  			} catch (ex) {
  				setTimeout(tryScroll, 0);
  				return;
  			}
  
  			readyHandler();
  		}
  
  		// Use W3C method
  		if (doc.addEventListener) {
  			if (doc.readyState === "complete") {
  				readyHandler();
  			} else {
  				addEvent(win, 'DOMContentLoaded', readyHandler);
  			}
  		} else {
  			// Use IE method
  			addEvent(doc, "readystatechange", waitForDomLoaded);
  
  			// Wait until we can scroll, when we can the DOM is initialized
  			if (doc.documentElement.doScroll && win.self === win.top) {
  				tryScroll();
  			}
  		}
  
  		// Fallback if any of the above methods should fail for some odd reason
  		addEvent(win, 'load', readyHandler);
  	}
  
  	/**
  	 * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
  	 */
  	function EventUtils() {
  		var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
  
  		expando = eventExpandoPrefix + (+new Date()).toString(32);
  		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
  		hasFocusIn = "onfocusin" in document.documentElement;
  		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
  		count = 1;
  
  		// State if the DOMContentLoaded was executed or not
  		self.domLoaded = false;
  		self.events = events;
  
  		/**
  		 * Executes all event handler callbacks for a specific event.
  		 *
  		 * @private
  		 * @param {Event} evt Event object.
  		 * @param {String} id Expando id value to look for.
  		 */
  		function executeHandlers(evt, id) {
  			var callbackList, i, l, callback, container = events[id];
  
  			callbackList = container && container[evt.type];
  			if (callbackList) {
  				for (i = 0, l = callbackList.length; i < l; i++) {
  					callback = callbackList[i];
  
  					// Check if callback exists might be removed if a unbind is called inside the callback
  					if (callback && callback.func.call(callback.scope, evt) === false) {
  						evt.preventDefault();
  					}
  
  					// Should we stop propagation to immediate listeners
  					if (evt.isImmediatePropagationStopped()) {
  						return;
  					}
  				}
  			}
  		}
  
  		/**
  		 * Binds a callback to an event on the specified target.
  		 *
  		 * @method bind
  		 * @param {Object} target Target node/window or custom object.
  		 * @param {String} names Name of the event to bind.
  		 * @param {function} callback Callback function to execute when the event occurs.
  		 * @param {Object} scope Scope to call the callback function on, defaults to target.
  		 * @return {function} Callback function that got bound.
  		 */
  		self.bind = function(target, names, callback, scope) {
  			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
  
  			// Native event handler function patches the event and executes the callbacks for the expando
  			function defaultNativeHandler(evt) {
  				executeHandlers(fix(evt || win.event), id);
  			}
  
  			// Don't bind to text nodes or comments
  			if (!target || target.nodeType === 3 || target.nodeType === 8) {
  				return;
  			}
  
  			// Create or get events id for the target
  			if (!target[expando]) {
  				id = count++;
  				target[expando] = id;
  				events[id] = {};
  			} else {
  				id = target[expando];
  			}
  
  			// Setup the specified scope or use the target as a default
  			scope = scope || target;
  
  			// Split names and bind each event, enables you to bind multiple events with one call
  			names = names.split(' ');
  			i = names.length;
  			while (i--) {
  				name = names[i];
  				nativeHandler = defaultNativeHandler;
  				fakeName = capture = false;
  
  				// Use ready instead of DOMContentLoaded
  				if (name === "DOMContentLoaded") {
  					name = "ready";
  				}
  
  				// DOM is already ready
  				if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
  					callback.call(scope, fix({type: name}));
  					continue;
  				}
  
  				// Handle mouseenter/mouseleaver
  				if (!hasMouseEnterLeave) {
  					fakeName = mouseEnterLeave[name];
  
  					if (fakeName) {
  						nativeHandler = function(evt) {
  							var current, related;
  
  							current = evt.currentTarget;
  							related = evt.relatedTarget;
  
  							// Check if related is inside the current target if it's not then the event should
  							// be ignored since it's a mouseover/mouseout inside the element
  							if (related && current.contains) {
  								// Use contains for performance
  								related = current.contains(related);
  							} else {
  								while (related && related !== current) {
  									related = related.parentNode;
  								}
  							}
  
  							// Fire fake event
  							if (!related) {
  								evt = fix(evt || win.event);
  								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
  								evt.target = current;
  								executeHandlers(evt, id);
  							}
  						};
  					}
  				}
  
  				// Fake bubbeling of focusin/focusout
  				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
  					capture = true;
  					fakeName = name === "focusin" ? "focus" : "blur";
  					nativeHandler = function(evt) {
  						evt = fix(evt || win.event);
  						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
  						executeHandlers(evt, id);
  					};
  				}
  
  				// Setup callback list and bind native event
  				callbackList = events[id][name];
  				if (!callbackList) {
  					events[id][name] = callbackList = [{func: callback, scope: scope}];
  					callbackList.fakeName = fakeName;
  					callbackList.capture = capture;
  					//callbackList.callback = callback;
  
  					// Add the nativeHandler to the callback list so that we can later unbind it
  					callbackList.nativeHandler = nativeHandler;
  
  					// Check if the target has native events support
  
  					if (name === "ready") {
  						bindOnReady(target, nativeHandler, self);
  					} else {
  						addEvent(target, fakeName || name, nativeHandler, capture);
  					}
  				} else {
  					if (name === "ready" && self.domLoaded) {
  						callback({type: name});
  					} else {
  						// If it already has an native handler then just push the callback
  						callbackList.push({func: callback, scope: scope});
  					}
  				}
  			}
  
  			target = callbackList = 0; // Clean memory for IE
  
  			return callback;
  		};
  
  		/**
  		 * Unbinds the specified event by name, name and callback or all events on the target.
  		 *
  		 * @method unbind
  		 * @param {Object} target Target node/window or custom object.
  		 * @param {String} names Optional event name to unbind.
  		 * @param {function} callback Optional callback function to unbind.
  		 * @return {EventUtils} Event utils instance.
  		 */
  		self.unbind = function(target, names, callback) {
  			var id, callbackList, i, ci, name, eventMap;
  
  			// Don't bind to text nodes or comments
  			if (!target || target.nodeType === 3 || target.nodeType === 8) {
  				return self;
  			}
  
  			// Unbind event or events if the target has the expando
  			id = target[expando];
  			if (id) {
  				eventMap = events[id];
  
  				// Specific callback
  				if (names) {
  					names = names.split(' ');
  					i = names.length;
  					while (i--) {
  						name = names[i];
  						callbackList = eventMap[name];
  
  						// Unbind the event if it exists in the map
  						if (callbackList) {
  							// Remove specified callback
  							if (callback) {
  								ci = callbackList.length;
  								while (ci--) {
  									if (callbackList[ci].func === callback) {
  										var nativeHandler = callbackList.nativeHandler;
  										var fakeName = callbackList.fakeName, capture = callbackList.capture;
  
  										// Clone callbackList since unbind inside a callback would otherwise break the handlers loop
  										callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
  										callbackList.nativeHandler = nativeHandler;
  										callbackList.fakeName = fakeName;
  										callbackList.capture = capture;
  
  										eventMap[name] = callbackList;
  									}
  								}
  							}
  
  							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
  							if (!callback || callbackList.length === 0) {
  								delete eventMap[name];
  								removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
  							}
  						}
  					}
  				} else {
  					// All events for a specific element
  					for (name in eventMap) {
  						callbackList = eventMap[name];
  						removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
  					}
  
  					eventMap = {};
  				}
  
  				// Check if object is empty, if it isn't then we won't remove the expando map
  				for (name in eventMap) {
  					return self;
  				}
  
  				// Delete event object
  				delete events[id];
  
  				// Remove expando from target
  				try {
  					// IE will fail here since it can't delete properties from window
  					delete target[expando];
  				} catch (ex) {
  					// IE will set it to null
  					target[expando] = null;
  				}
  			}
  
  			return self;
  		};
  
  		/**
  		 * Fires the specified event on the specified target.
  		 *
  		 * @method fire
  		 * @param {Object} target Target node/window or custom object.
  		 * @param {String} name Event name to fire.
  		 * @param {Object} args Optional arguments to send to the observers.
  		 * @return {EventUtils} Event utils instance.
  		 */
  		self.fire = function(target, name, args) {
  			var id;
  
  			// Don't bind to text nodes or comments
  			if (!target || target.nodeType === 3 || target.nodeType === 8) {
  				return self;
  			}
  
  			// Build event object by patching the args
  			args = fix(null, args);
  			args.type = name;
  			args.target = target;
  
  			do {
  				// Found an expando that means there is listeners to execute
  				id = target[expando];
  				if (id) {
  					executeHandlers(args, id);
  				}
  
  				// Walk up the DOM
  				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
  			} while (target && !args.isPropagationStopped());
  
  			return self;
  		};
  
  		/**
  		 * Removes all bound event listeners for the specified target. This will also remove any bound
  		 * listeners to child nodes within that target.
  		 *
  		 * @method clean
  		 * @param {Object} target Target node/window object.
  		 * @return {EventUtils} Event utils instance.
  		 */
  		self.clean = function(target) {
  			var i, children, unbind = self.unbind;
  
  			// Don't bind to text nodes or comments
  			if (!target || target.nodeType === 3 || target.nodeType === 8) {
  				return self;
  			}
  
  			// Unbind any element on the specificed target
  			if (target[expando]) {
  				unbind(target);
  			}
  
  			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
  			if (!target.getElementsByTagName) {
  				target = target.document;
  			}
  
  			// Remove events from each child element
  			if (target && target.getElementsByTagName) {
  				unbind(target);
  
  				children = target.getElementsByTagName('*');
  				i = children.length;
  				while (i--) {
  					target = children[i];
  
  					if (target[expando]) {
  						unbind(target);
  					}
  				}
  			}
  
  			return self;
  		};
  
  		/**
  		 * Destroys the event object. Call this on IE to remove memory leaks.
  		 */
  		self.destroy = function() {
  			events = {};
  		};
  
  		// Legacy function for canceling events
  		self.cancel = function(e) {
  			if (e) {
  				e.preventDefault();
  				e.stopImmediatePropagation();
  			}
  
  			return false;
  		};
  	}
  
  	EventUtils.Event = new EventUtils();
  	EventUtils.Event.bind(window, 'ready', function() {});
  
  	return EventUtils;
  });
  
  // Included from: js/maya/classes/dom/Sizzle.jQuery.js
  
  /**
   * Sizzle.jQuery.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*global jQuery:true */
  
  /*
   * Fake Sizzle using jQuery.
   */
  define("maya/dom/Sizzle", [], function() {
  	// Detect if jQuery is loaded
  	if (!window.jQuery) {
  		throw new Error("Load jQuery first");
  	}
  
  	return jQuery.find;
  });
  
  // Included from: js/maya/classes/Env.js
  
  /**
   * Env.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class contains various environment constants like browser versions etc.
   * Normally you don't want to sniff specific browser versions but sometimes you have
   * to when it's impossible to feature detect. So use this with care.
   *
   * @class maya.Env
   * @static
   */
  define("maya/Env", [], function() {
  	var nav = navigator, userAgent = nav.userAgent;
  	var opera, webkit, ie, ie11, gecko, mac, iDevice, android;
  
  	opera = window.opera && window.opera.buildNumber;
  	android = /Android/.test(userAgent);
  	webkit = /WebKit/.test(userAgent);
  	ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
  	ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
  	ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
  	ie = ie || ie11;
  	gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
  	mac = userAgent.indexOf('Mac') != -1;
  	iDevice = /(iPad|iPhone)/.test(userAgent);
  
  	// Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
  	// says it has contentEditable support but there is no visible caret.
  	var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
  
  	return {
  		/**
  		 * Constant that is true if the browser is Opera.
  		 *
  		 * @property opera
  		 * @type Boolean
  		 * @final
  		 */
  		opera: opera,
  
  		/**
  		 * Constant that is true if the browser is WebKit (Safari/Chrome).
  		 *
  		 * @property webKit
  		 * @type Boolean
  		 * @final
  		 */
  		webkit: webkit,
  
  		/**
  		 * Constant that is more than zero if the browser is IE.
  		 *
  		 * @property ie
  		 * @type Boolean
  		 * @final
  		 */
  		ie: ie,
  
  		/**
  		 * Constant that is true if the browser is Gecko.
  		 *
  		 * @property gecko
  		 * @type Boolean
  		 * @final
  		 */
  		gecko: gecko,
  
  		/**
  		 * Constant that is true if the os is Mac OS.
  		 *
  		 * @property mac
  		 * @type Boolean
  		 * @final
  		 */
  		mac: mac,
  
  		/**
  		 * Constant that is true if the os is iOS.
  		 *
  		 * @property iOS
  		 * @type Boolean
  		 * @final
  		 */
  		iOS: iDevice,
  
  		/**
  		 * Constant that is true if the os is android.
  		 *
  		 * @property android
  		 * @type Boolean
  		 * @final
  		 */
  		android: android,
  
  		/**
  		 * Constant that is true if the browser supports editing.
  		 *
  		 * @property contentEditable
  		 * @type Boolean
  		 * @final
  		 */
  		contentEditable: contentEditable,
  
  		/**
  		 * Transparent image data url.
  		 *
  		 * @property transparentSrc
  		 * @type Boolean
  		 * @final
  		 */
  		transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
  
  		/**
  		 * Returns true/false if the browser can or can't place the caret after a inline block like an image.
  		 *
  		 * @property noCaretAfter
  		 * @type Boolean
  		 * @final
  		 */
  		caretAfter: ie != 8,
  
  		/**
  		 * Constant that is true if the browser supports native DOM Ranges. IE 9+.
  		 *
  		 * @property range
  		 * @type Boolean
  		 */
  		range: window.getSelection && "Range" in window,
  
  		/**
  		 * Returns the IE document mode for non IE browsers this will fake IE 10.
  		 *
  		 * @property documentMode
  		 * @type Number
  		 */
  		documentMode: ie ? (document.documentMode || 7) : 10
  	};
  });
  
  // Included from: js/maya/classes/util/Tools.js
  
  /**
   * Tools.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class contains various utlity functions. These are also exposed
   * directly on the maya namespace.
   *
   * @class maya.util.Tools
   */
  define("maya/util/Tools", [
  	"maya/Env"
  ], function(Env) {
  	/**
  	 * Removes whitespace from the beginning and end of a string.
  	 *
  	 * @method trim
  	 * @param {String} s String to remove whitespace from.
  	 * @return {String} New string with removed whitespace.
  	 */
  	var whiteSpaceRegExp = /^\s*|\s*$/g;
  
  	function trim(str) {
  		return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  	}
  
  	/**
  	 * Returns true/false if the object is an array or not.
  	 *
  	 * @method isArray
  	 * @param {Object} obj Object to check.
  	 * @return {boolean} true/false state if the object is an array or not.
  	 */
  	var isArray = Array.isArray || function(obj) {
  		return Object.prototype.toString.call(obj) === "[object Array]";
  	};
  
  	/**
  	 * Checks if a object is of a specific type for example an array.
  	 *
  	 * @method is
  	 * @param {Object} obj Object to check type of.
  	 * @param {string} type Optional type to check for.
  	 * @return {Boolean} true/false if the object is of the specified type.
  	 */
  	function is(obj, type) {
  		if (!type) {
  			return obj !== undefined;
  		}
  
  		if (type == 'array' && isArray(obj)) {
  			return true;
  		}
  
  		return typeof obj == type;
  	}
  
  	/**
  	 * Converts the specified object into a real JavaScript array.
  	 *
  	 * @method toArray
  	 * @param {Object} obj Object to convert into array.
  	 * @return {Array} Array object based in input.
  	 */
  	function toArray(obj) {
  		var array = obj, i, l;
  
  		if (!isArray(obj)) {
  			array = [];
  			for (i = 0, l = obj.length; i < l; i++) {
  				array[i] = obj[i];
  			}
  		}
  
  		return array;
  	}
  
  	/**
  	 * Makes a name/object map out of an array with names.
  	 *
  	 * @method makeMap
  	 * @param {Array/String} items Items to make map out of.
  	 * @param {String} delim Optional delimiter to split string by.
  	 * @param {Object} map Optional map to add items to.
  	 * @return {Object} Name/value map of items.
  	 */
  	function makeMap(items, delim, map) {
  		var i;
  
  		items = items || [];
  		delim = delim || ',';
  
  		if (typeof items == "string") {
  			items = items.split(delim);
  		}
  
  		map = map || {};
  
  		i = items.length;
  		while (i--) {
  			map[items[i]] = {};
  		}
  
  		return map;
  	}
  
  	/**
  	 * Performs an iteration of all items in a collection such as an object or array. This method will execure the
  	 * callback function for each item in the collection, if the callback returns false the iteration will terminate.
  	 * The callback has the following format: cb(value, key_or_index).
  	 *
  	 * @method each
  	 * @param {Object} o Collection to iterate.
  	 * @param {function} cb Callback function to execute for each item.
  	 * @param {Object} s Optional scope to execute the callback in.
  	 * @example
  	 * // Iterate an array
  	 * maya.each([1,2,3], function(v, i) {
  	 *     console.debug("Value: " + v + ", Index: " + i);
  	 * });
  	 *
  	 * // Iterate an object
  	 * maya.each({a: 1, b: 2, c: 3], function(v, k) {
  	 *     console.debug("Value: " + v + ", Key: " + k);
  	 * });
  	 */
  	function each(o, cb, s) {
  		var n, l;
  
  		if (!o) {
  			return 0;
  		}
  
  		s = s || o;
  
  		if (o.length !== undefined) {
  			// Indexed arrays, needed for Safari
  			for (n = 0, l = o.length; n < l; n++) {
  				if (cb.call(s, o[n], n, o) === false) {
  					return 0;
  				}
  			}
  		} else {
  			// Hashtables
  			for (n in o) {
  				if (o.hasOwnProperty(n)) {
  					if (cb.call(s, o[n], n, o) === false) {
  						return 0;
  					}
  				}
  			}
  		}
  
  		return 1;
  	}
  
  	/**
  	 * Creates a new array by the return value of each iteration function call. This enables you to convert
  	 * one array list into another.
  	 *
  	 * @method map
  	 * @param {Array} array Array of items to iterate.
  	 * @param {function} callback Function to call for each item. It's return value will be the new value.
  	 * @return {Array} Array with new values based on function return values.
  	 */
  	function map(array, callback) {
  		var out = [];
  
  		each(array, function(item) {
  			out.push(callback(item));
  		});
  
  		return out;
  	}
  
  	/**
  	 * Filters out items from the input array by calling the specified function for each item.
  	 * If the function returns false the item will be excluded if it returns true it will be included.
  	 *
  	 * @method grep
  	 * @param {Array} a Array of items to loop though.
  	 * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
  	 * @return {Array} New array with values imported and filtered based in input.
  	 * @example
  	 * // Filter out some items, this will return an array with 4 and 5
  	 * var items = maya.grep([1,2,3,4,5], function(v) {return v > 3;});
  	 */
  	function grep(a, f) {
  		var o = [];
  
  		each(a, function(v) {
  			if (!f || f(v)) {
  				o.push(v);
  			}
  		});
  
  		return o;
  	}
  
  	/**
  	 * Creates a class, subclass or static singleton.
  	 * More details on this method can be found in the Wiki.
  	 *
  	 * @method create
  	 * @param {String} s Class name, inheritage and prefix.
  	 * @param {Object} p Collection of methods to add to the class.
  	 * @param {Object} root Optional root object defaults to the global window object.
  	 * @example
  	 * // Creates a basic class
  	 * maya.create('maya.somepackage.SomeClass', {
  	 *     SomeClass: function() {
  	 *         // Class constructor
  	 *     },
  	 *
  	 *     method: function() {
  	 *         // Some method
  	 *     }
  	 * });
  	 *
  	 * // Creates a basic subclass class
  	 * maya.create('maya.somepackage.SomeSubClass:maya.somepackage.SomeClass', {
  	 *     SomeSubClass: function() {
  	 *         // Class constructor
  	 *         this.parent(); // Call parent constructor
  	 *     },
  	 *
  	 *     method: function() {
  	 *         // Some method
  	 *         this.parent(); // Call parent method
  	 *     },
  	 *
  	 *     'static': {
  	 *         staticMethod: function() {
  	 *             // Static method
  	 *         }
  	 *     }
  	 * });
  	 *
  	 * // Creates a singleton/static class
  	 * maya.create('static maya.somepackage.SomeSingletonClass', {
  	 *     method: function() {
  	 *         // Some method
  	 *     }
  	 * });
  	 */
  	function create(s, p, root) {
  		var self = this, sp, ns, cn, scn, c, de = 0;
  
  		// Parse : <prefix> <class>:<super class>
  		s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
  		cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
  
  		// Create namespace for new class
  		ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);
  
  		// Class already exists
  		if (ns[cn]) {
  			return;
  		}
  
  		// Make pure static class
  		if (s[2] == 'static') {
  			ns[cn] = p;
  
  			if (this.onCreate) {
  				this.onCreate(s[2], s[3], ns[cn]);
  			}
  
  			return;
  		}
  
  		// Create default constructor
  		if (!p[cn]) {
  			p[cn] = function() {};
  			de = 1;
  		}
  
  		// Add constructor and methods
  		ns[cn] = p[cn];
  		self.extend(ns[cn].prototype, p);
  
  		// Extend
  		if (s[5]) {
  			sp = self.resolve(s[5]).prototype;
  			scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
  
  			// Extend constructor
  			c = ns[cn];
  			if (de) {
  				// Add passthrough constructor
  				ns[cn] = function() {
  					return sp[scn].apply(this, arguments);
  				};
  			} else {
  				// Add inherit constructor
  				ns[cn] = function() {
  					this.parent = sp[scn];
  					return c.apply(this, arguments);
  				};
  			}
  			ns[cn].prototype[cn] = ns[cn];
  
  			// Add super methods
  			self.each(sp, function(f, n) {
  				ns[cn].prototype[n] = sp[n];
  			});
  
  			// Add overridden methods
  			self.each(p, function(f, n) {
  				// Extend methods if needed
  				if (sp[n]) {
  					ns[cn].prototype[n] = function() {
  						this.parent = sp[n];
  						return f.apply(this, arguments);
  					};
  				} else {
  					if (n != cn) {
  						ns[cn].prototype[n] = f;
  					}
  				}
  			});
  		}
  
  		// Add static methods
  		/*jshint sub:true*/
  		/*eslint dot-notation:0*/
  		self.each(p['static'], function(f, n) {
  			ns[cn][n] = f;
  		});
  	}
  
  	/**
  	 * Returns the index of a value in an array, this method will return -1 if the item wasn't found.
  	 *
  	 * @method inArray
  	 * @param {Array} a Array/Object to search for value in.
  	 * @param {Object} v Value to check for inside the array.
  	 * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
  	 * @example
  	 * // Get index of value in array this will alert 1 since 2 is at that index
  	 * alert(maya.inArray([1,2,3], 2));
  	 */
  	function inArray(a, v) {
  		var i, l;
  
  		if (a) {
  			for (i = 0, l = a.length; i < l; i++) {
  				if (a[i] === v) {
  					return i;
  				}
  			}
  		}
  
  		return -1;
  	}
  
  	function extend(obj, ext) {
  		var i, l, name, args = arguments, value;
  
  		for (i = 1, l = args.length; i < l; i++) {
  			ext = args[i];
  			for (name in ext) {
  				if (ext.hasOwnProperty(name)) {
  					value = ext[name];
  
  					if (value !== undefined) {
  						obj[name] = value;
  					}
  				}
  			}
  		}
  
  		return obj;
  	}
  
  	/**
  	 * Executed the specified function for each item in a object tree.
  	 *
  	 * @method walk
  	 * @param {Object} o Object tree to walk though.
  	 * @param {function} f Function to call for each item.
  	 * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
  	 * @param {String} s Optional scope to execute the function in.
  	 */
  	function walk(o, f, n, s) {
  		s = s || this;
  
  		if (o) {
  			if (n) {
  				o = o[n];
  			}
  
  			each(o, function(o, i) {
  				if (f.call(s, o, i, n) === false) {
  					return false;
  				}
  
  				walk(o, f, n, s);
  			});
  		}
  	}
  
  	/**
  	 * Creates a namespace on a specific object.
  	 *
  	 * @method createNS
  	 * @param {String} n Namespace to create for example a.b.c.d.
  	 * @param {Object} o Optional object to add namespace to, defaults to window.
  	 * @return {Object} New namespace object the last item in path.
  	 * @example
  	 * // Create some namespace
  	 * maya.createNS('maya.somepackage.subpackage');
  	 *
  	 * // Add a singleton
  	 * var maya.somepackage.subpackage.SomeSingleton = {
  	 *     method: function() {
  	 *         // Some method
  	 *     }
  	 * };
  	 */
  	function createNS(n, o) {
  		var i, v;
  
  		o = o || window;
  
  		n = n.split('.');
  		for (i = 0; i < n.length; i++) {
  			v = n[i];
  
  			if (!o[v]) {
  				o[v] = {};
  			}
  
  			o = o[v];
  		}
  
  		return o;
  	}
  
  	/**
  	 * Resolves a string and returns the object from a specific structure.
  	 *
  	 * @method resolve
  	 * @param {String} n Path to resolve for example a.b.c.d.
  	 * @param {Object} o Optional object to search though, defaults to window.
  	 * @return {Object} Last object in path or null if it couldn't be resolved.
  	 * @example
  	 * // Resolve a path into an object reference
  	 * var obj = maya.resolve('a.b.c.d');
  	 */
  	function resolve(n, o) {
  		var i, l;
  
  		o = o || window;
  
  		n = n.split('.');
  		for (i = 0, l = n.length; i < l; i++) {
  			o = o[n[i]];
  
  			if (!o) {
  				break;
  			}
  		}
  
  		return o;
  	}
  
  	/**
  	 * Splits a string but removes the whitespace before and after each value.
  	 *
  	 * @method explode
  	 * @param {string} s String to split.
  	 * @param {string} d Delimiter to split by.
  	 * @example
  	 * // Split a string into an array with a,b,c
  	 * var arr = maya.explode('a, b,   c');
  	 */
  	function explode(s, d) {
  		if (!s || is(s, 'array')) {
  			return s;
  		}
  
  		return map(s.split(d || ','), trim);
  	}
  
  	function _addCacheSuffix(url) {
  		var cacheSuffix = Env.cacheSuffix;
  
  		if (cacheSuffix) {
  			url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix;
  		}
  
  		return url;
  	}
  
  	return {
  		trim: trim,
  		isArray: isArray,
  		is: is,
  		toArray: toArray,
  		makeMap: makeMap,
  		each: each,
  		map: map,
  		grep: grep,
  		inArray: inArray,
  		extend: extend,
  		create: create,
  		walk: walk,
  		createNS: createNS,
  		resolve: resolve,
  		explode: explode,
  		_addCacheSuffix: _addCacheSuffix
  	};
  });
  
  // Included from: js/maya/classes/dom/DomQuery.js
  
  /**
   * DomQuery.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class mimics most of the jQuery API:
   *
   * This is whats currently implemented:
   * - Utility functions
   * - DOM traversial
   * - DOM manipulation
   * - Event binding
   *
   * This is not currently implemented:
   * - Dimension
   * - Ajax
   * - Animation
   * - Advanced chaining
   *
   * @example
   * var $ = maya.dom.DomQuery;
   * $('p').attr('attr', 'value').addClass('class');
   *
   * @class maya.dom.DomQuery
   */
  define("maya/dom/DomQuery", [
  	"maya/dom/EventUtils",
  	"maya/dom/Sizzle",
  	"maya/util/Tools",
  	"maya/Env"
  ], function(EventUtils, Sizzle, Tools, Env) {
  	var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
  	var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
  	var Event = EventUtils.Event, undef;
  
  	function isDefined(obj) {
  		return typeof obj !== 'undefined';
  	}
  
  	function isString(obj) {
  		return typeof obj === 'string';
  	}
  
  	function isWindow(obj) {
  		return obj && obj == obj.window;
  	}
  
  	function createFragment(html, fragDoc) {
  		var frag, node, container;
  
  		fragDoc = fragDoc || doc;
  		container = fragDoc.createElement('div');
  		frag = fragDoc.createDocumentFragment();
  		container.innerHTML = html;
  
  		while ((node = container.firstChild)) {
  			frag.appendChild(node);
  		}
  
  		return frag;
  	}
  
  	function domManipulate(targetNodes, sourceItem, callback, reverse) {
  		var i;
  
  		if (isString(sourceItem)) {
  			sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0]));
  		} else if (sourceItem.length && !sourceItem.nodeType) {
  			sourceItem = DomQuery.makeArray(sourceItem);
  
  			if (reverse) {
  				for (i = sourceItem.length - 1; i >= 0; i--) {
  					domManipulate(targetNodes, sourceItem[i], callback, reverse);
  				}
  			} else {
  				for (i = 0; i < sourceItem.length; i++) {
  					domManipulate(targetNodes, sourceItem[i], callback, reverse);
  				}
  			}
  
  			return targetNodes;
  		}
  
  		if (sourceItem.nodeType) {
  			i = targetNodes.length;
  			while (i--) {
  				callback.call(targetNodes[i], sourceItem);
  			}
  		}
  
  		return targetNodes;
  	}
  
  	function hasClass(node, className) {
  		return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
  	}
  
  	function wrap(elements, wrapper, all) {
  		var lastParent, newWrapper;
  
  		wrapper = DomQuery(wrapper)[0];
  
  		elements.each(function() {
  			var self = this;
  
  			if (!all || lastParent != self.parentNode) {
  				lastParent = self.parentNode;
  				newWrapper = wrapper.cloneNode(false);
  				self.parentNode.insertBefore(newWrapper, self);
  				newWrapper.appendChild(self);
  			} else {
  				newWrapper.appendChild(self);
  			}
  		});
  
  		return elements;
  	}
  
  	var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
  	var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref noshade nowrap readonly selected', ' ');
  	var propFix = {
  		'for': 'htmlFor',
  		'class': 'className',
  		'readonly': 'readOnly'
  	};
  	var cssFix = {
  		'float': 'cssFloat'
  	};
  
  	var attrHooks = {}, cssHooks = {};
  
  	function DomQuery(selector, context) {
  		/*eslint new-cap:0 */
  		return new DomQuery.fn.init(selector, context);
  	}
  
  	function inArray(item, array) {
  		var i;
  
  		if (array.indexOf) {
  			return array.indexOf(item);
  		}
  
  		i = array.length;
  		while (i--) {
  			if (array[i] === item) {
  				return i;
  			}
  		}
  
  		return -1;
  	}
  
  	var whiteSpaceRegExp = /^\s*|\s*$/g;
  
  	function trim(str) {
  		return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  	}
  
  	function each(obj, callback) {
  		var length, key, i, undef, value;
  
  		if (obj) {
  			length = obj.length;
  
  			if (length === undef) {
  				// Loop object items
  				for (key in obj) {
  					if (obj.hasOwnProperty(key)) {
  						value = obj[key];
  						if (callback.call(value, key, value) === false) {
  							break;
  						}
  					}
  				}
  			} else {
  				// Loop array items
  				for (i = 0; i < length; i++) {
  					value = obj[i];
  					if (callback.call(value, i, value) === false) {
  						break;
  					}
  				}
  			}
  		}
  
  		return obj;
  	}
  
  	function grep(array, callback) {
  		var out = [];
  
  		each(array, function(i, item) {
  			if (callback(item, i)) {
  				out.push(item);
  			}
  		});
  
  		return out;
  	}
  
  	function getElementDocument(element) {
  		if (!element) {
  			return doc;
  		}
  
  		if (element.nodeType == 9) {
  			return element;
  		}
  
  		return element.ownerDocument;
  	}
  
  	DomQuery.fn = DomQuery.prototype = {
  		constructor: DomQuery,
  
  		/**
  		 * Selector for the current set.
  		 *
  		 * @property selector
  		 * @type String
  		 */
  		selector: "",
  
  		/**
  		 * Context used to create the set.
  		 *
  		 * @property context
  		 * @type Element
  		 */
  		context: null,
  
  		/**
  		 * Number of items in the current set.
  		 *
  		 * @property length
  		 * @type Number
  		 */
  		length: 0,
  
  		/**
  		 * Constructs a new DomQuery instance with the specified selector or context.
  		 *
  		 * @constructor
  		 * @method init
  		 * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string.
  		 * @param {Document/Element} context Optional context to search in.
  		 */
  		init: function(selector, context) {
  			var self = this, match, node;
  
  			if (!selector) {
  				return self;
  			}
  
  			if (selector.nodeType) {
  				self.context = self[0] = selector;
  				self.length = 1;
  
  				return self;
  			}
  
  			if (context && context.nodeType) {
  				self.context = context;
  			} else {
  				if (context) {
  					return DomQuery(selector).attr(context);
  				} else {
  					self.context = context = document;
  				}
  			}
  
  			if (isString(selector)) {
  				self.selector = selector;
  
  				if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
  					match = [null, selector, null];
  				} else {
  					match = rquickExpr.exec(selector);
  				}
  
  				if (match) {
  					if (match[1]) {
  						node = createFragment(selector, getElementDocument(context)).firstChild;
  
  						while (node) {
  							push.call(self, node);
  							node = node.nextSibling;
  						}
  					} else {
  						node = getElementDocument(context).getElementById(match[2]);
  
  						if (!node) {
  							return self;
  						}
  
  						if (node.id !== match[2]) {
  							return self.find(selector);
  						}
  
  						self.length = 1;
  						self[0] = node;
  					}
  				} else {
  					return DomQuery(context).find(selector);
  				}
  			} else {
  				this.add(selector, false);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Converts the current set to an array.
  		 *
  		 * @method toArray
  		 * @param {Array} Array of all nodes in set.
  		 */
  		toArray: function() {
  			return Tools.toArray(this);
  		},
  
  		/**
  		 * Adds new nodes to the set.
  		 *
  		 * @method add
  		 * @param {Array/maya.dom.DomQuery} items Array of all nodes to add to set.
  		 * @return {maya.dom.DomQuery} New instance with nodes added.
  		 */
  		add: function(items, sort) {
  			var self = this, nodes, i;
  
  			if (isString(items)) {
  				return self.add(DomQuery(items));
  			}
  
  			if (sort !== false) {
  				nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items)));
  				self.length = nodes.length;
  				for (i = 0; i < nodes.length; i++) {
  					self[i] = nodes[i];
  				}
  			} else {
  				push.apply(self, DomQuery.makeArray(items));
  			}
  
  			return self;
  		},
  
  		/**
  		 * Sets/gets attributes on the elements in the current set.
  		 *
  		 * @method attr
  		 * @param {String/Object} name Name of attribute to get or an object with attributes to set.
  		 * @param {String} value Optional value to set.
  		 * @return {maya.dom.DomQuery/String} Current set or the specified attribute when only the name is specified.
  		 */
  		attr: function(name, value) {
  			var self = this, hook;
  
  			if (typeof name === "object") {
  				each(name, function(name, value) {
  					self.attr(name, value);
  				});
  			} else if (isDefined(value)) {
  				this.each(function() {
  					var hook;
  
  					if (this.nodeType === 1) {
  						hook = attrHooks[name];
  						if (hook && hook.set) {
  							hook.set(this, value);
  							return;
  						}
  
  						if (value === null) {
  							this.removeAttribute(name, 2);
  						} else {
  							this.setAttribute(name, value, 2);
  						}
  					}
  				});
  			} else {
  				if (self[0] && self[0].nodeType === 1) {
  					hook = attrHooks[name];
  					if (hook && hook.get) {
  						return hook.get(self[0], name);
  					}
  
  					if (booleanMap[name]) {
  						return self.prop(name) ? name : undef;
  					}
  
  					value = self[0].getAttribute(name, 2);
  
  					if (value === null) {
  						value = undef;
  					}
  				}
  
  				return value;
  			}
  
  			return self;
  		},
  
  		/**
  		 * Removes attributse on the elements in the current set.
  		 *
  		 * @method removeAttr
  		 * @param {String/Object} name Name of attribute to remove.
  		 * @return {maya.dom.DomQuery/String} Current set.
  		 */
  		removeAttr: function(name) {
  			return this.attr(name, null);
  		},
  
  		/**
  		 * Sets/gets properties on the elements in the current set.
  		 *
  		 * @method attr
  		 * @param {String/Object} name Name of property to get or an object with properties to set.
  		 * @param {String} value Optional value to set.
  		 * @return {maya.dom.DomQuery/String} Current set or the specified property when only the name is specified.
  		 */
  		prop: function(name, value) {
  			var self = this;
  
  			name = propFix[name] || name;
  
  			if (typeof name === "object") {
  				each(name, function(name, value) {
  					self.prop(name, value);
  				});
  			} else if (isDefined(value)) {
  				this.each(function() {
  					if (this.nodeType == 1) {
  						this[name] = value;
  					}
  				});
  			} else {
  				if (self[0] && self[0].nodeType && name in self[0]) {
  					return self[0][name];
  				}
  
  				return value;
  			}
  
  			return self;
  		},
  
  		/**
  		 * Sets/gets styles on the elements in the current set.
  		 *
  		 * @method css
  		 * @param {String/Object} name Name of style to get or an object with styles to set.
  		 * @param {String} value Optional value to set.
  		 * @return {maya.dom.DomQuery/String} Current set or the specified style when only the name is specified.
  		 */
  		css: function(name, value) {
  			var self = this, elm, hook;
  
  			function camel(name) {
  				return name.replace(/-(\D)/g, function(a, b) {
  					return b.toUpperCase();
  				});
  			}
  
  			function dashed(name) {
  				return name.replace(/[A-Z]/g, function(a) {
  					return '-' + a;
  				});
  			}
  
  			if (typeof name === "object") {
  				each(name, function(name, value) {
  					self.css(name, value);
  				});
  			} else {
  				if (isDefined(value)) {
  					name = camel(name);
  
  					// Default px suffix on these
  					if (typeof value === 'number' && !numericCssMap[name]) {
  						value += 'px';
  					}
  
  					self.each(function() {
  						var style = this.style;
  
  						hook = cssHooks[name];
  						if (hook && hook.set) {
  							hook.set(this, value);
  							return;
  						}
  
  						try {
  							this.style[cssFix[name] || name] = value;
  						} catch (ex) {
  							// Ignore
  						}
  
  						if (value === null || value === '') {
  							if (style.removeProperty) {
  								style.removeProperty(dashed(name));
  							} else {
  								style.removeAttribute(name);
  							}
  						}
  					});
  				} else {
  					elm = self[0];
  
  					hook = cssHooks[name];
  					if (hook && hook.get) {
  						return hook.get(elm);
  					}
  
  					if (elm.ownerDocument.defaultView) {
  						try {
  							return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name));
  						} catch (ex) {
  							return undef;
  						}
  					} else if (elm.currentStyle) {
  						return elm.currentStyle[camel(name)];
  					}
  				}
  			}
  
  			return self;
  		},
  
  		/**
  		 * Removes all nodes in set from the document.
  		 *
  		 * @method remove
  		 * @return {maya.dom.DomQuery} Current set with the removed nodes.
  		 */
  		remove: function() {
  			var self = this, node, i = this.length;
  
  			while (i--) {
  				node = self[i];
  				Event.clean(node);
  
  				if (node.parentNode) {
  					node.parentNode.removeChild(node);
  				}
  			}
  
  			return this;
  		},
  
  		/**
  		 * Empties all elements in set.
  		 *
  		 * @method empty
  		 * @return {maya.dom.DomQuery} Current set with the empty nodes.
  		 */
  		empty: function() {
  			var self = this, node, i = this.length;
  
  			while (i--) {
  				node = self[i];
  				while (node.firstChild) {
  					node.removeChild(node.firstChild);
  				}
  			}
  
  			return this;
  		},
  
  		/**
  		 * Sets or gets the HTML of the current set or first set node.
  		 *
  		 * @method html
  		 * @param {String} value Optional innerHTML value to set on each element.
  		 * @return {maya.dom.DomQuery/String} Current set or the innerHTML of the first element.
  		 */
  		html: function(value) {
  			var self = this, i;
  
  			if (isDefined(value)) {
  				i = self.length;
  
  				try {
  					while (i--) {
  						self[i].innerHTML = value;
  					}
  				} catch (ex) {
  					// Workaround for "Unknown runtime error" when DIV is added to P on IE
  					DomQuery(self[i]).empty().append(value);
  				}
  
  				return self;
  			}
  
  			return self[0] ? self[0].innerHTML : '';
  		},
  
  		/**
  		 * Sets or gets the text of the current set or first set node.
  		 *
  		 * @method text
  		 * @param {String} value Optional innerText value to set on each element.
  		 * @return {maya.dom.DomQuery/String} Current set or the innerText of the first element.
  		 */
  		text: function(value) {
  			var self = this, i;
  
  			if (isDefined(value)) {
  				i = self.length;
  				while (i--) {
  					if ("innerText" in self[i]) {
  						self[i].innerText = value;
  					} else {
  						self[0].textContent = value;
  					}
  				}
  
  				return self;
  			}
  
  			return self[0] ? (self[0].innerText || self[0].textContent) : '';
  		},
  
  		/**
  		 * Appends the specified node/html or node set to the current set nodes.
  		 *
  		 * @method append
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to append to each element in set.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		append: function() {
  			return domManipulate(this, arguments, function(node) {
  				if (this.nodeType === 1) {
  					this.appendChild(node);
  				}
  			});
  		},
  
  		/**
  		 * Prepends the specified node/html or node set to the current set nodes.
  		 *
  		 * @method prepend
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to prepend to each element in set.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		prepend: function() {
  			return domManipulate(this, arguments, function(node) {
  				if (this.nodeType === 1) {
  					this.insertBefore(node, this.firstChild);
  				}
  			}, true);
  		},
  
  		/**
  		 * Adds the specified elements before current set nodes.
  		 *
  		 * @method before
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to add before to each element in set.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		before: function() {
  			var self = this;
  
  			if (self[0] && self[0].parentNode) {
  				return domManipulate(self, arguments, function(node) {
  					this.parentNode.insertBefore(node, this);
  				});
  			}
  
  			return self;
  		},
  
  		/**
  		 * Adds the specified elements after current set nodes.
  		 *
  		 * @method after
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to add after to each element in set.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		after: function() {
  			var self = this;
  
  			if (self[0] && self[0].parentNode) {
  				return domManipulate(self, arguments, function(node) {
  					this.parentNode.insertBefore(node, this.nextSibling);
  				}, true);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Appends the specified set nodes to the specified selector/instance.
  		 *
  		 * @method appendTo
  		 * @param {String/Element/Array/maya.dom.DomQuery} val Item to append the current set to.
  		 * @return {maya.dom.DomQuery} Current set with the appended nodes.
  		 */
  		appendTo: function(val) {
  			DomQuery(val).append(this);
  
  			return this;
  		},
  
  		/**
  		 * Prepends the specified set nodes to the specified selector/instance.
  		 *
  		 * @method prependTo
  		 * @param {String/Element/Array/maya.dom.DomQuery} val Item to prepend the current set to.
  		 * @return {maya.dom.DomQuery} Current set with the prepended nodes.
  		 */
  		prependTo: function(val) {
  			DomQuery(val).prepend(this);
  
  			return this;
  		},
  
  		/**
  		 * Replaces the nodes in set with the specified content.
  		 *
  		 * @method replaceWith
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to replace nodes with.
  		 * @return {maya.dom.DomQuery} Set with replaced nodes.
  		 */
  		replaceWith: function(content) {
  			return this.before(content).remove();
  		},
  
  		/**
  		 * Wraps all elements in set with the specified wrapper.
  		 *
  		 * @method wrap
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to wrap nodes with.
  		 * @return {maya.dom.DomQuery} Set with wrapped nodes.
  		 */
  		wrap: function(wrapper) {
  			return wrap(this, wrapper);
  		},
  
  		/**
  		 * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them
  		 * will be wrapped in the same wrapper.
  		 *
  		 * @method wrapAll
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to wrap nodes with.
  		 * @return {maya.dom.DomQuery} Set with wrapped nodes.
  		 */
  		wrapAll: function(wrapper) {
  			return wrap(this, wrapper, true);
  		},
  
  		/**
  		 * Wraps all elements inner contents in set with the specified wrapper.
  		 *
  		 * @method wrapInner
  		 * @param {String/Element/Array/maya.dom.DomQuery} content Content to wrap nodes with.
  		 * @return {maya.dom.DomQuery} Set with wrapped nodes.
  		 */
  		wrapInner: function(wrapper) {
  			this.each(function() {
  				DomQuery(this).contents().wrapAll(wrapper);
  			});
  
  			return this;
  		},
  
  		/**
  		 * Unwraps all elements by removing the parent element of each item in set.
  		 *
  		 * @method unwrap
  		 * @return {maya.dom.DomQuery} Set with unwrapped nodes.
  		 */
  		unwrap: function() {
  			return this.parent().each(function() {
  				DomQuery(this).replaceWith(this.childNodes);
  			});
  		},
  
  		/**
  		 * Clones all nodes in set.
  		 *
  		 * @method clone
  		 * @return {maya.dom.DomQuery} Set with cloned nodes.
  		 */
  		clone: function() {
  			var result = [];
  
  			this.each(function() {
  				result.push(this.cloneNode(true));
  			});
  
  			return DomQuery(result);
  		},
  
  		/**
  		 * Adds the specified class name to the current set elements.
  		 *
  		 * @method addClass
  		 * @param {String} className Class name to add.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		addClass: function(className) {
  			return this.toggleClass(className, true);
  		},
  
  		/**
  		 * Removes the specified class name to the current set elements.
  		 *
  		 * @method removeClass
  		 * @param {String} className Class name to remove.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		removeClass: function(className) {
  			return this.toggleClass(className, false);
  		},
  
  		/**
  		 * Toggles the specified class name on the current set elements.
  		 *
  		 * @method toggleClass
  		 * @param {String} className Class name to add/remove.
  		 * @param {Boolean} state Optional state to toggle on/off.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		toggleClass: function(className, state) {
  			var self = this;
  
  			// Functions are not supported
  			if (typeof className != 'string') {
  				return self;
  			}
  
  			if (className.indexOf(' ') !== -1) {
  				each(className.split(' '), function() {
  					self.toggleClass(this, state);
  				});
  			} else {
  				self.each(function(index, node) {
  					var existingClassName, classState;
  
  					classState = hasClass(node, className);
  					if (classState !== state) {
  						existingClassName = node.className;
  
  						if (classState) {
  							node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
  						} else {
  							node.className += existingClassName ? ' ' + className : className;
  						}
  					}
  				});
  			}
  
  			return self;
  		},
  
  		/**
  		 * Returns true/false if the first item in set has the specified class.
  		 *
  		 * @method hasClass
  		 * @param {String} className Class name to check for.
  		 * @return {Boolean} True/false if the set has the specified class.
  		 */
  		hasClass: function(className) {
  			return hasClass(this[0], className);
  		},
  
  		/**
  		 * Executes the callback function for each item DomQuery collection. If you return false in the
  		 * callback it will break the loop.
  		 *
  		 * @method each
  		 * @param {function} callback Callback function to execute for each item.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		each: function(callback) {
  			return each(this, callback);
  		},
  
  		/**
  		 * Binds an event with callback function to the elements in set.
  		 *
  		 * @method on
  		 * @param {String} name Name of the event to bind.
  		 * @param {function} callback Callback function to execute when the event occurs.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		on: function(name, callback) {
  			return this.each(function() {
  				Event.bind(this, name, callback);
  			});
  		},
  
  		/**
  		 * Unbinds an event with callback function to the elements in set.
  		 *
  		 * @method off
  		 * @param {String} name Optional name of the event to bind.
  		 * @param {function} callback Optional callback function to execute when the event occurs.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		off: function(name, callback) {
  			return this.each(function() {
  				Event.unbind(this, name, callback);
  			});
  		},
  
  		/**
  		 * Triggers the specified event by name or event object.
  		 *
  		 * @method trigger
  		 * @param {String/Object} name Name of the event to trigger or event object.
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		trigger: function(name) {
  			return this.each(function() {
  				if (typeof name == 'object') {
  					Event.fire(this, name.type, name);
  				} else {
  					Event.fire(this, name);
  				}
  			});
  		},
  
  		/**
  		 * Shows all elements in set.
  		 *
  		 * @method show
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		show: function() {
  			return this.css('display', '');
  		},
  
  		/**
  		 * Hides all elements in set.
  		 *
  		 * @method hide
  		 * @return {maya.dom.DomQuery} Current set.
  		 */
  		hide: function() {
  			return this.css('display', 'none');
  		},
  
  		/**
  		 * Slices the current set.
  		 *
  		 * @method slice
  		 * @param {Number} start Start index to slice at.
  		 * @param {Number} end Optional ened index to end slice at.
  		 * @return {maya.dom.DomQuery} Sliced set.
  		 */
  		slice: function() {
  			return new DomQuery(slice.apply(this, arguments));
  		},
  
  		/**
  		 * Makes the set equal to the specified index.
  		 *
  		 * @method eq
  		 * @param {Number} index Index to set it equal to.
  		 * @return {maya.dom.DomQuery} Single item set.
  		 */
  		eq: function(index) {
  			return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  		},
  
  		/**
  		 * Makes the set equal to first element in set.
  		 *
  		 * @method first
  		 * @return {maya.dom.DomQuery} Single item set.
  		 */
  		first: function() {
  			return this.eq(0);
  		},
  
  		/**
  		 * Makes the set equal to last element in set.
  		 *
  		 * @method last
  		 * @return {maya.dom.DomQuery} Single item set.
  		 */
  		last: function() {
  			return this.eq(-1);
  		},
  
  		/**
  		 * Finds elements by the specified selector for each element in set.
  		 *
  		 * @method find
  		 * @param {String} selector Selector to find elements by.
  		 * @return {maya.dom.DomQuery} Set with matches elements.
  		 */
  		find: function(selector) {
  			var i, l, ret = [];
  
  			for (i = 0, l = this.length; i < l; i++) {
  				DomQuery.find(selector, this[i], ret);
  			}
  
  			return DomQuery(ret);
  		},
  
  		/**
  		 * Filters the current set with the specified selector.
  		 *
  		 * @method filter
  		 * @param {String/function} selector Selector to filter elements by.
  		 * @return {maya.dom.DomQuery} Set with filtered elements.
  		 */
  		filter: function(selector) {
  			if (typeof selector == 'function') {
  				return DomQuery(grep(this.toArray(), function(item, i) {
  					return selector(i, item);
  				}));
  			}
  
  			return DomQuery(DomQuery.filter(selector, this.toArray()));
  		},
  
  		/**
  		 * Gets the current node or any partent matching the specified selector.
  		 *
  		 * @method closest
  		 * @param {String/Element/maya.dom.DomQuery} selector Selector or element to find.
  		 * @return {maya.dom.DomQuery} Set with closest elements.
  		 */
  		closest: function(selector) {
  			var result = [];
  
  			if (selector instanceof DomQuery) {
  				selector = selector[0];
  			}
  
  			this.each(function(i, node) {
  				while (node) {
  					if (typeof selector == 'string' && DomQuery(node).is(selector)) {
  						result.push(node);
  						break;
  					} else if (node == selector) {
  						result.push(node);
  						break;
  					}
  
  					node = node.parentNode;
  				}
  			});
  
  			return DomQuery(result);
  		},
  
  		/**
  		 * Returns the offset of the first element in set or sets the top/left css properties of all elements in set.
  		 *
  		 * @method offset
  		 * @param {Object} offset Optional offset object to set on each item.
  		 * @return {Object/maya.dom.DomQuery} Returns the first element offset or the current set if you specified an offset.
  		 */
  		offset: function(offset) {
  			var elm, doc, docElm;
  			var x = 0, y = 0, pos;
  
  			if (!offset) {
  				elm = this[0];
  
  				if (elm) {
  					doc = elm.ownerDocument;
  					docElm = doc.documentElement;
  
  					if (elm.getBoundingClientRect) {
  						pos = elm.getBoundingClientRect();
  						x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft;
  						y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop;
  					}
  				}
  
  				return {
  					left: x,
  					top: y
  				};
  			}
  
  			return this.css(offset);
  		},
  
  		push: push,
  		sort: [].sort,
  		splice: [].splice
  	};
  
  	// Static members
  	Tools.extend(DomQuery, {
  		/**
  		 * Extends the specified object with one or more objects.
  		 *
  		 * @static
  		 * @method extend
  		 * @param {Object} target Target object to extend with new items.
  		 * @param {Object..} object Object to extend the target with.
  		 * @return {Object} Extended input object.
  		 */
  		extend: Tools.extend,
  
  		/**
  		 * Creates an array out of an array like object.
  		 *
  		 * @static
  		 * @method makeArray
  		 * @param {Object} object Object to convert to array.
  		 * @return {Arrau} Array produced from object.
  		 */
  		makeArray: function(array) {
  			if (isWindow(array) || array.nodeType) {
  				return [array];
  			}
  
  			return Tools.toArray(array);
  		},
  
  		/**
  		 * Returns the index of the specified item inside the array.
  		 *
  		 * @static
  		 * @method inArray
  		 * @param {Object} item Item to look for.
  		 * @param {Array} array Array to look for item in.
  		 * @return {Number} Index of the item or -1.
  		 */
  		inArray: inArray,
  
  		/**
  		 * Returns true/false if the specified object is an array or not.
  		 *
  		 * @static
  		 * @method isArray
  		 * @param {Object} array Object to check if it's an array or not.
  		 * @return {Boolean} True/false if the object is an array.
  		 */
  		isArray: Tools.isArray,
  
  		/**
  		 * Executes the callback function for each item in array/object. If you return false in the
  		 * callback it will break the loop.
  		 *
  		 * @static
  		 * @method each
  		 * @param {Object} obj Object to iterate.
  		 * @param {function} callback Callback function to execute for each item.
  		 */
  		each: each,
  
  		/**
  		 * Removes whitespace from the beginning and end of a string.
  		 *
  		 * @static
  		 * @method trim
  		 * @param {String} str String to remove whitespace from.
  		 * @return {String} New string with removed whitespace.
  		 */
  		trim: trim,
  
  		/**
  		 * Filters out items from the input array by calling the specified function for each item.
  		 * If the function returns false the item will be excluded if it returns true it will be included.
  		 *
  		 * @static
  		 * @method grep
  		 * @param {Array} array Array of items to loop though.
  		 * @param {function} callback Function to call for each item. Include/exclude depends on it's return value.
  		 * @return {Array} New array with values imported and filtered based in input.
  		 * @example
  		 * // Filter out some items, this will return an array with 4 and 5
  		 * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;});
  		 */
  		grep: grep,
  
  		// Sizzle
  		find: Sizzle,
  		expr: Sizzle.selectors,
  		unique: Sizzle.uniqueSort,
  		text: Sizzle.getText,
  		contains: Sizzle.contains,
  		filter: function(expr, elems, not) {
  			var i = elems.length;
  
  			if (not) {
  				expr = ":not(" + expr + ")";
  			}
  
  			while (i--) {
  				if (elems[i].nodeType != 1) {
  					elems.splice(i, 1);
  				}
  			}
  
  			if (elems.length === 1) {
  				elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
  			} else {
  				elems = DomQuery.find.matches(expr, elems);
  			}
  
  			return elems;
  		}
  	});
  
  	function dir(el, prop, until) {
  		var matched = [], cur = el[prop];
  
  		if (typeof until != 'string' && until instanceof DomQuery) {
  			until = until[0];
  		}
  
  		while (cur && cur.nodeType !== 9) {
  			if (until !== undefined) {
  				if (cur === until) {
  					break;
  				}
  
  				if (typeof until == 'string' && DomQuery(cur).is(until)) {
  					break;
  				}
  			}
  
  			if (cur.nodeType === 1) {
  				matched.push(cur);
  			}
  
  			cur = cur[prop];
  		}
  
  		return matched;
  	}
  
  	function sibling(node, siblingName, nodeType, until) {
  		var result = [];
  
  		if (until instanceof DomQuery) {
  			until = until[0];
  		}
  
  		for (; node; node = node[siblingName]) {
  			if (nodeType && node.nodeType !== nodeType) {
  				continue;
  			}
  
  			if (until !== undefined) {
  				if (node === until) {
  					break;
  				}
  
  				if (typeof until == 'string' && DomQuery(node).is(until)) {
  					break;
  				}
  			}
  
  			result.push(node);
  		}
  
  		return result;
  	}
  
  	function firstSibling(node, siblingName, nodeType) {
  		for (node = node[siblingName]; node; node = node[siblingName]) {
  			if (node.nodeType == nodeType) {
  				return node;
  			}
  		}
  
  		return null;
  	}
  
  	each({
  		/**
  		 * Returns a new collection with the parent of each item in current collection matching the optional selector.
  		 *
  		 * @method parent
  		 * @param {String} selector Selector to match parents agains.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching parents.
  		 */
  		parent: function(node) {
  			var parent = node.parentNode;
  
  			return parent && parent.nodeType !== 11 ? parent : null;
  		},
  
  		/**
  		 * Returns a new collection with the all the parents of each item in current collection matching the optional selector.
  		 *
  		 * @method parents
  		 * @param {String} selector Selector to match parents agains.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching parents.
  		 */
  		parents: function(node) {
  			return dir(node, "parentNode");
  		},
  
  		/**
  		 * Returns a new collection with next sibling of each item in current collection matching the optional selector.
  		 *
  		 * @method next
  		 * @param {String} selector Selector to match the next element against.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching elements.
  		 */
  		next: function(node) {
  			return firstSibling(node, 'nextSibling', 1);
  		},
  
  		/**
  		 * Returns a new collection with previous sibling of each item in current collection matching the optional selector.
  		 *
  		 * @method prev
  		 * @param {String} selector Selector to match the previous element against.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching elements.
  		 */
  		prev: function(node) {
  			return firstSibling(node, 'previousSibling', 1);
  		},
  
  		/**
  		 * Returns all child elements matching the optional selector.
  		 *
  		 * @method children
  		 * @param {String} selector Selector to match the elements against.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching elements.
  		 */
  		children: function(node) {
  			return sibling(node.firstChild, 'nextSibling', 1);
  		},
  
  		/**
  		 * Returns all child nodes matching the optional selector.
  		 *
  		 * @method contents
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching elements.
  		 */
  		contents: function(node) {
  			return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
  		}
  	}, function(name, fn) {
  		DomQuery.fn[name] = function(selector) {
  			var self = this, result = [];
  
  			self.each(function() {
  				var nodes = fn.call(result, this, selector, result);
  
  				if (nodes) {
  					if (DomQuery.isArray(nodes)) {
  						result.push.apply(result, nodes);
  					} else {
  						result.push(nodes);
  					}
  				}
  			});
  
  			// If traversing on multiple elements we might get the same elements twice
  			if (this.length > 1) {
  				result = DomQuery.unique(result);
  
  				if (name.indexOf('parents') === 0) {
  					result = result.reverse();
  				}
  			}
  
  			result = DomQuery(result);
  
  			if (selector) {
  				return result.filter(selector);
  			}
  
  			return result;
  		};
  	});
  
  	each({
  		/**
  		 * Returns a new collection with the all the parents until the matching selector/element
  		 * of each item in current collection matching the optional selector.
  		 *
  		 * @method parentsUntil
  		 * @param {String/Element/maya.dom.DomQuery} until Until the matching selector or element.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching parents.
  		 */
  		parentsUntil: function(node, until) {
  			return dir(node, "parentNode", until);
  		},
  
  		/**
  		 * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
  		 *
  		 * @method nextUntil
  		 * @param {String/Element/maya.dom.DomQuery} until Until the matching selector or element.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching elements.
  		 */
  		nextUntil: function(node, until) {
  			return sibling(node, 'nextSibling', 1, until).slice(1);
  		},
  
  		/**
  		 * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
  		 *
  		 * @method prevUntil
  		 * @param {String/Element/maya.dom.DomQuery} until Until the matching selector or element.
  		 * @return {maya.dom.DomQuery} New DomQuery instance with all matching elements.
  		 */
  		prevUntil: function(node, until) {
  			return sibling(node, 'previousSibling', 1, until).slice(1);
  		}
  	}, function(name, fn) {
  		DomQuery.fn[name] = function(selector, filter) {
  			var self = this, result = [];
  
  			self.each(function() {
  				var nodes = fn.call(result, this, selector, result);
  
  				if (nodes) {
  					if (DomQuery.isArray(nodes)) {
  						result.push.apply(result, nodes);
  					} else {
  						result.push(nodes);
  					}
  				}
  			});
  
  			// If traversing on multiple elements we might get the same elements twice
  			if (this.length > 1) {
  				result = DomQuery.unique(result);
  
  				if (name.indexOf('parents') === 0 || name === 'prevUntil') {
  					result = result.reverse();
  				}
  			}
  
  			result = DomQuery(result);
  
  			if (filter) {
  				return result.filter(filter);
  			}
  
  			return result;
  		};
  	});
  
  	/**
  	 * Returns true/false if the current set items matches the selector.
  	 *
  	 * @method is
  	 * @param {String} selector Selector to match the elements against.
  	 * @return {Boolean} True/false if the current set matches the selector.
  	 */
  	DomQuery.fn.is = function(selector) {
  		return !!selector && this.filter(selector).length > 0;
  	};
  
  	DomQuery.fn.init.prototype = DomQuery.fn;
  
  	DomQuery.overrideDefaults = function(callback) {
  		var defaults;
  
  		function sub(selector, context) {
  			defaults = defaults || callback();
  
  			if (arguments.length === 0) {
  				selector = defaults.element;
  			}
  
  			if (!context) {
  				context = defaults.context;
  			}
  
  			return new sub.fn.init(selector, context);
  		}
  
  		DomQuery.extend(sub, this);
  
  		return sub;
  	};
  
  	function appendHooks(targetHooks, prop, hooks) {
  		each(hooks, function(name, func) {
  			targetHooks[name] = targetHooks[name] || {};
  			targetHooks[name][prop] = func;
  		});
  	}
  
  	if (Env.ie && Env.ie < 8) {
  		appendHooks(attrHooks, 'get', {
  			maxlength: function(elm) {
  				var value = elm.maxLength;
  
  				if (value === 0x7fffffff) {
  					return undef;
  				}
  
  				return value;
  			},
  
  			size: function(elm) {
  				var value = elm.size;
  
  				if (value === 20) {
  					return undef;
  				}
  
  				return value;
  			},
  
  			'class': function(elm) {
  				return elm.className;
  			},
  
  			style: function(elm) {
  				var value = elm.style.cssText;
  
  				if (value.length === 0) {
  					return undef;
  				}
  
  				return value;
  			}
  		});
  
  		appendHooks(attrHooks, 'set', {
  			'class': function(elm, value) {
  				elm.className = value;
  			},
  
  			style: function(elm, value) {
  				elm.style.cssText = value;
  			}
  		});
  	}
  
  	if (Env.ie && Env.ie < 9) {
  		/*jshint sub:true */
  		/*eslint dot-notation: 0*/
  		cssFix['float'] = 'styleFloat';
  
  		appendHooks(cssHooks, 'set', {
  			opacity: function(elm, value) {
  				var style = elm.style;
  
  				if (value === null || value === '') {
  					style.removeAttribute('filter');
  				} else {
  					style.zoom = 1;
  					style.filter = 'alpha(opacity=' + (value * 100) + ')';
  				}
  			}
  		});
  	}
  
  	DomQuery.attrHooks = attrHooks;
  	DomQuery.cssHooks = cssHooks;
  
  	return DomQuery;
  });
  
  // Included from: js/maya/classes/html/Styles.js
  
  /**
   * Styles.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is used to parse CSS styles it also compresses styles to reduce the output size.
   *
   * @example
   * var Styles = new maya.html.Styles({
   *    url_converter: function(url) {
   *       return url;
   *    }
   * });
   *
   * styles = Styles.parse('border: 1px solid red');
   * styles.color = 'red';
   *
   * console.log(new maya.html.StyleSerializer().serialize(styles));
   *
   * @class maya.html.Styles
   * @version 3.4
   */
  define("maya/html/Styles", [], function() {
  	return function(settings, schema) {
  		/*jshint maxlen:255 */
  		/*eslint max-len:0 */
  		var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
  			urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
  			styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
  			trimRightRegExp = /\s+$/,
  			undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
  
  		settings = settings || {};
  
  		if (schema) {
  			validStyles = schema.getValidStyles();
  			invalidStyles = schema.getInvalidStyles();
  		}
  
  		encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
  		for (i = 0; i < encodingItems.length; i++) {
  			encodingLookup[encodingItems[i]] = invisibleChar + i;
  			encodingLookup[invisibleChar + i] = encodingItems[i];
  		}
  
  		function toHex(match, r, g, b) {
  			function hex(val) {
  				val = parseInt(val, 10).toString(16);
  
  				return val.length > 1 ? val : '0' + val; // 0 -> 00
  			}
  
  			return '#' + hex(r) + hex(g) + hex(b);
  		}
  
  		return {
  			/**
  			 * Parses the specified RGB color value and returns a hex version of that color.
  			 *
  			 * @method toHex
  			 * @param {String} color RGB string value like rgb(1,2,3)
  			 * @return {String} Hex version of that RGB value like #FF00FF.
  			 */
  			toHex: function(color) {
  				return color.replace(rgbRegExp, toHex);
  			},
  
  			/**
  			 * Parses the specified style value into an object collection. This parser will also
  			 * merge and remove any redundant items that browsers might have added. It will also convert non hex
  			 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  			 *
  			 * @method parse
  			 * @param {String} css Style value to parse for example: border:1px solid red;.
  			 * @return {Object} Object representation of that style like {border: '1px solid red'}
  			 */
  			parse: function(css) {
  				var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
  				var urlConverterScope = settings.url_converter_scope || this;
  
  				function compress(prefix, suffix, noJoin) {
  					var top, right, bottom, left;
  
  					top = styles[prefix + '-top' + suffix];
  					if (!top) {
  						return;
  					}
  
  					right = styles[prefix + '-right' + suffix];
  					if (!right) {
  						return;
  					}
  
  					bottom = styles[prefix + '-bottom' + suffix];
  					if (!bottom) {
  						return;
  					}
  
  					left = styles[prefix + '-left' + suffix];
  					if (!left) {
  						return;
  					}
  
  					var box = [top, right, bottom, left];
  					i = box.length - 1;
  					while (i--) {
  						if (box[i] !== box[i + 1]) {
  							break;
  						}
  					}
  
  					if (i > -1 && noJoin) {
  						return;
  					}
  
  					styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
  					delete styles[prefix + '-top' + suffix];
  					delete styles[prefix + '-right' + suffix];
  					delete styles[prefix + '-bottom' + suffix];
  					delete styles[prefix + '-left' + suffix];
  				}
  
  				/**
  				 * Checks if the specific style can be compressed in other words if all border-width are equal.
  				 */
  				function canCompress(key) {
  					var value = styles[key], i;
  
  					if (!value) {
  						return;
  					}
  
  					value = value.split(' ');
  					i = value.length;
  					while (i--) {
  						if (value[i] !== value[0]) {
  							return false;
  						}
  					}
  
  					styles[key] = value[0];
  
  					return true;
  				}
  
  				/**
  				 * Compresses multiple styles into one style.
  				 */
  				function compress2(target, a, b, c) {
  					if (!canCompress(a)) {
  						return;
  					}
  
  					if (!canCompress(b)) {
  						return;
  					}
  
  					if (!canCompress(c)) {
  						return;
  					}
  
  					// Compress
  					styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
  					delete styles[a];
  					delete styles[b];
  					delete styles[c];
  				}
  
  				// Encodes the specified string by replacing all \" \' ; : with _<num>
  				function encode(str) {
  					isEncoded = true;
  
  					return encodingLookup[str];
  				}
  
  				// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
  				// It will also decode the \" \' if keep_slashes is set to fale or omitted
  				function decode(str, keep_slashes) {
  					if (isEncoded) {
  						str = str.replace(/\uFEFF[0-9]/g, function(str) {
  							return encodingLookup[str];
  						});
  					}
  
  					if (!keep_slashes) {
  						str = str.replace(/\\([\'\";:])/g, "$1");
  					}
  
  					return str;
  				}
  
  				function processUrl(match, url, url2, url3, str, str2) {
  					str = str || str2;
  
  					if (str) {
  						str = decode(str);
  
  						// Force strings into single quote format
  						return "'" + str.replace(/\'/g, "\\'") + "'";
  					}
  
  					url = decode(url || url2 || url3);
  
  					if (!settings.allow_script_urls) {
  						var scriptUrl = url.replace(/[\s\r\n]+/, '');
  
  						if (/(java|vb)script:/i.test(scriptUrl)) {
  							return "";
  						}
  
  						if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
  							return "";
  						}
  					}
  
  					// Convert the URL to relative/absolute depending on config
  					if (urlConverter) {
  						url = urlConverter.call(urlConverterScope, url, 'style');
  					}
  
  					// Output new URL format
  					return "url('" + url.replace(/\'/g, "\\'") + "')";
  				}
  
  				if (css) {
  					css = css.replace(/[\u0000-\u001F]/g, '');
  
  					// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
  					css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
  						return str.replace(/[;:]/g, encode);
  					});
  
  					// Parse styles
  					while ((matches = styleRegExp.exec(css))) {
  						name = matches[1].replace(trimRightRegExp, '').toLowerCase();
  						value = matches[2].replace(trimRightRegExp, '');
  
  						// Decode escaped sequences like \65 -> e
  						/*jshint loopfunc:true*/
  						/*eslint no-loop-func:0 */
  						value = value.replace(/\\[0-9a-f]+/g, function(e) {
  							return String.fromCharCode(parseInt(e.substr(1), 16));
  						});
  
  						if (name && value.length > 0) {
  							// Don't allow behavior name or expression/comments within the values
  							if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
  								continue;
  							}
  
  							// Opera will produce 700 instead of bold in their style values
  							if (name === 'font-weight' && value === '700') {
  								value = 'bold';
  							} else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
  								value = value.toLowerCase();
  							}
  
  							// Convert RGB colors to HEX
  							value = value.replace(rgbRegExp, toHex);
  
  							// Convert URLs and force them into url('value') format
  							value = value.replace(urlOrStrRegExp, processUrl);
  							styles[name] = isEncoded ? decode(value, true) : value;
  						}
  
  						styleRegExp.lastIndex = matches.index + matches[0].length;
  					}
  					// Compress the styles to reduce it's size for example IE will expand styles
  					compress("border", "", true);
  					compress("border", "-width");
  					compress("border", "-color");
  					compress("border", "-style");
  					compress("padding", "");
  					compress("margin", "");
  					compress2('border', 'border-width', 'border-style', 'border-color');
  
  					// Remove pointless border, IE produces these
  					if (styles.border === 'medium none') {
  						delete styles.border;
  					}
  
  					// IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
  					// So lets asume it shouldn't be there
  					if (styles['border-image'] === 'none') {
  						delete styles['border-image'];
  					}
  				}
  
  				return styles;
  			},
  
  			/**
  			 * Serializes the specified style object into a string.
  			 *
  			 * @method serialize
  			 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
  			 * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
  			 * @return {String} String representation of the style object for example: border: 1px solid red.
  			 */
  			serialize: function(styles, elementName) {
  				var css = '', name, value;
  
  				function serializeStyles(name) {
  					var styleList, i, l, value;
  
  					styleList = validStyles[name];
  					if (styleList) {
  						for (i = 0, l = styleList.length; i < l; i++) {
  							name = styleList[i];
  							value = styles[name];
  
  							if (value !== undef && value.length > 0) {
  								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  							}
  						}
  					}
  				}
  
  				function isValid(name, elementName) {
  					var styleMap;
  
  					styleMap = invalidStyles['*'];
  					if (styleMap && styleMap[name]) {
  						return false;
  					}
  
  					styleMap = invalidStyles[elementName];
  					if (styleMap && styleMap[name]) {
  						return false;
  					}
  
  					return true;
  				}
  
  				// Serialize styles according to schema
  				if (elementName && validStyles) {
  					// Serialize global styles and element specific styles
  					serializeStyles('*');
  					serializeStyles(elementName);
  				} else {
  					// Output the styles in the order they are inside the object
  					for (name in styles) {
  						value = styles[name];
  
  						if (value !== undef && value.length > 0) {
  							if (!invalidStyles || isValid(name, elementName)) {
  								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  							}
  						}
  					}
  				}
  
  				return css;
  			}
  		};
  	};
  });
  
  // Included from: js/maya/classes/dom/TreeWalker.js
  
  /**
   * TreeWalker.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * TreeWalker class enables you to walk the DOM in a linear manner.
   *
   * @class maya.dom.TreeWalker
   * @example
   * var walker = new maya.dom.TreeWalker(startNode);
   *
   * do {
   *     console.log(walker.current());
   * } while (walker.next());
   */
  define("maya/dom/TreeWalker", [], function() {
  	/**
  	 * Constructs a new TreeWalker instance.
  	 *
  	 * @constructor
  	 * @method TreeWalker
  	 * @param {Node} startNode Node to start walking from.
  	 * @param {node} rootNode Optional root node to never walk out of.
  	 */
  	return function(startNode, rootNode) {
  		var node = startNode;
  
  		function findSibling(node, startName, siblingName, shallow) {
  			var sibling, parent;
  
  			if (node) {
  				// Walk into nodes if it has a start
  				if (!shallow && node[startName]) {
  					return node[startName];
  				}
  
  				// Return the sibling if it has one
  				if (node != rootNode) {
  					sibling = node[siblingName];
  					if (sibling) {
  						return sibling;
  					}
  
  					// Walk up the parents to look for siblings
  					for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
  						sibling = parent[siblingName];
  						if (sibling) {
  							return sibling;
  						}
  					}
  				}
  			}
  		}
  
  		/**
  		 * Returns the current node.
  		 *
  		 * @method current
  		 * @return {Node} Current node where the walker is.
  		 */
  		this.current = function() {
  			return node;
  		};
  
  		/**
  		 * Walks to the next node in tree.
  		 *
  		 * @method next
  		 * @return {Node} Current node where the walker is after moving to the next node.
  		 */
  		this.next = function(shallow) {
  			node = findSibling(node, 'firstChild', 'nextSibling', shallow);
  			return node;
  		};
  
  		/**
  		 * Walks to the previous node in tree.
  		 *
  		 * @method prev
  		 * @return {Node} Current node where the walker is after moving to the previous node.
  		 */
  		this.prev = function(shallow) {
  			node = findSibling(node, 'lastChild', 'previousSibling', shallow);
  			return node;
  		};
  	};
  });
  
  // Included from: js/maya/classes/dom/Range.js
  
  /**
   * Range.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  define("maya/dom/Range", [
  	"maya/util/Tools"
  ], function(Tools) {
  	// Range constructor
  	function Range(dom) {
  		var self = this,
  			doc = dom.doc,
  			EXTRACT = 0,
  			CLONE = 1,
  			DELETE = 2,
  			TRUE = true,
  			FALSE = false,
  			START_OFFSET = 'startOffset',
  			START_CONTAINER = 'startContainer',
  			END_CONTAINER = 'endContainer',
  			END_OFFSET = 'endOffset',
  			extend = Tools.extend,
  			nodeIndex = dom.nodeIndex;
  
  		function createDocumentFragment() {
  			return doc.createDocumentFragment();
  		}
  
  		function setStart(n, o) {
  			_setEndPoint(TRUE, n, o);
  		}
  
  		function setEnd(n, o) {
  			_setEndPoint(FALSE, n, o);
  		}
  
  		function setStartBefore(n) {
  			setStart(n.parentNode, nodeIndex(n));
  		}
  
  		function setStartAfter(n) {
  			setStart(n.parentNode, nodeIndex(n) + 1);
  		}
  
  		function setEndBefore(n) {
  			setEnd(n.parentNode, nodeIndex(n));
  		}
  
  		function setEndAfter(n) {
  			setEnd(n.parentNode, nodeIndex(n) + 1);
  		}
  
  		function collapse(ts) {
  			if (ts) {
  				self[END_CONTAINER] = self[START_CONTAINER];
  				self[END_OFFSET] = self[START_OFFSET];
  			} else {
  				self[START_CONTAINER] = self[END_CONTAINER];
  				self[START_OFFSET] = self[END_OFFSET];
  			}
  
  			self.collapsed = TRUE;
  		}
  
  		function selectNode(n) {
  			setStartBefore(n);
  			setEndAfter(n);
  		}
  
  		function selectNodeContents(n) {
  			setStart(n, 0);
  			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
  		}
  
  		function compareBoundaryPoints(h, r) {
  			var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
  			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
  
  			// Check START_TO_START
  			if (h === 0) {
  				return _compareBoundaryPoints(sc, so, rsc, rso);
  			}
  
  			// Check START_TO_END
  			if (h === 1) {
  				return _compareBoundaryPoints(ec, eo, rsc, rso);
  			}
  
  			// Check END_TO_END
  			if (h === 2) {
  				return _compareBoundaryPoints(ec, eo, rec, reo);
  			}
  
  			// Check END_TO_START
  			if (h === 3) {
  				return _compareBoundaryPoints(sc, so, rec, reo);
  			}
  		}
  
  		function deleteContents() {
  			_traverse(DELETE);
  		}
  
  		function extractContents() {
  			return _traverse(EXTRACT);
  		}
  
  		function cloneContents() {
  			return _traverse(CLONE);
  		}
  
  		function insertNode(n) {
  			var startContainer = this[START_CONTAINER],
  				startOffset = this[START_OFFSET], nn, o;
  
  			// Node is TEXT_NODE or CDATA
  			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
  				if (!startOffset) {
  					// At the start of text
  					startContainer.parentNode.insertBefore(n, startContainer);
  				} else if (startOffset >= startContainer.nodeValue.length) {
  					// At the end of text
  					dom.insertAfter(n, startContainer);
  				} else {
  					// Middle, need to split
  					nn = startContainer.splitText(startOffset);
  					startContainer.parentNode.insertBefore(n, nn);
  				}
  			} else {
  				// Insert element node
  				if (startContainer.childNodes.length > 0) {
  					o = startContainer.childNodes[startOffset];
  				}
  
  				if (o) {
  					startContainer.insertBefore(n, o);
  				} else {
  					if (startContainer.nodeType == 3) {
  						dom.insertAfter(n, startContainer);
  					} else {
  						startContainer.appendChild(n);
  					}
  				}
  			}
  		}
  
  		function surroundContents(n) {
  			var f = self.extractContents();
  
  			self.insertNode(n);
  			n.appendChild(f);
  			self.selectNode(n);
  		}
  
  		function cloneRange() {
  			return extend(new Range(dom), {
  				startContainer: self[START_CONTAINER],
  				startOffset: self[START_OFFSET],
  				endContainer: self[END_CONTAINER],
  				endOffset: self[END_OFFSET],
  				collapsed: self.collapsed,
  				commonAncestorContainer: self.commonAncestorContainer
  			});
  		}
  
  		// Private methods
  
  		function _getSelectedNode(container, offset) {
  			var child;
  
  			if (container.nodeType == 3 /* TEXT_NODE */) {
  				return container;
  			}
  
  			if (offset < 0) {
  				return container;
  			}
  
  			child = container.firstChild;
  			while (child && offset > 0) {
  				--offset;
  				child = child.nextSibling;
  			}
  
  			if (child) {
  				return child;
  			}
  
  			return container;
  		}
  
  		function _isCollapsed() {
  			return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
  		}
  
  		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
  			var c, offsetC, n, cmnRoot, childA, childB;
  
  			// In the first case the boundary-points have the same container. A is before B
  			// if its offset is less than the offset of B, A is equal to B if its offset is
  			// equal to the offset of B, and A is after B if its offset is greater than the
  			// offset of B.
  			if (containerA == containerB) {
  				if (offsetA == offsetB) {
  					return 0; // equal
  				}
  
  				if (offsetA < offsetB) {
  					return -1; // before
  				}
  
  				return 1; // after
  			}
  
  			// In the second case a child node C of the container of A is an ancestor
  			// container of B. In this case, A is before B if the offset of A is less than or
  			// equal to the index of the child node C and A is after B otherwise.
  			c = containerB;
  			while (c && c.parentNode != containerA) {
  				c = c.parentNode;
  			}
  
  			if (c) {
  				offsetC = 0;
  				n = containerA.firstChild;
  
  				while (n != c && offsetC < offsetA) {
  					offsetC++;
  					n = n.nextSibling;
  				}
  
  				if (offsetA <= offsetC) {
  					return -1; // before
  				}
  
  				return 1; // after
  			}
  
  			// In the third case a child node C of the container of B is an ancestor container
  			// of A. In this case, A is before B if the index of the child node C is less than
  			// the offset of B and A is after B otherwise.
  			c = containerA;
  			while (c && c.parentNode != containerB) {
  				c = c.parentNode;
  			}
  
  			if (c) {
  				offsetC = 0;
  				n = containerB.firstChild;
  
  				while (n != c && offsetC < offsetB) {
  					offsetC++;
  					n = n.nextSibling;
  				}
  
  				if (offsetC < offsetB) {
  					return -1; // before
  				}
  
  				return 1; // after
  			}
  
  			// In the fourth case, none of three other cases hold: the containers of A and B
  			// are siblings or descendants of sibling nodes. In this case, A is before B if
  			// the container of A is before the container of B in a pre-order traversal of the
  			// Ranges' context tree and A is after B otherwise.
  			cmnRoot = dom.findCommonAncestor(containerA, containerB);
  			childA = containerA;
  
  			while (childA && childA.parentNode != cmnRoot) {
  				childA = childA.parentNode;
  			}
  
  			if (!childA) {
  				childA = cmnRoot;
  			}
  
  			childB = containerB;
  			while (childB && childB.parentNode != cmnRoot) {
  				childB = childB.parentNode;
  			}
  
  			if (!childB) {
  				childB = cmnRoot;
  			}
  
  			if (childA == childB) {
  				return 0; // equal
  			}
  
  			n = cmnRoot.firstChild;
  			while (n) {
  				if (n == childA) {
  					return -1; // before
  				}
  
  				if (n == childB) {
  					return 1; // after
  				}
  
  				n = n.nextSibling;
  			}
  		}
  
  		function _setEndPoint(st, n, o) {
  			var ec, sc;
  
  			if (st) {
  				self[START_CONTAINER] = n;
  				self[START_OFFSET] = o;
  			} else {
  				self[END_CONTAINER] = n;
  				self[END_OFFSET] = o;
  			}
  
  			// If one boundary-point of a Range is set to have a root container
  			// other than the current one for the Range, the Range is collapsed to
  			// the new position. This enforces the restriction that both boundary-
  			// points of a Range must have the same root container.
  			ec = self[END_CONTAINER];
  			while (ec.parentNode) {
  				ec = ec.parentNode;
  			}
  
  			sc = self[START_CONTAINER];
  			while (sc.parentNode) {
  				sc = sc.parentNode;
  			}
  
  			if (sc == ec) {
  				// The start position of a Range is guaranteed to never be after the
  				// end position. To enforce this restriction, if the start is set to
  				// be at a position after the end, the Range is collapsed to that
  				// position.
  				if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
  					self.collapse(st);
  				}
  			} else {
  				self.collapse(st);
  			}
  
  			self.collapsed = _isCollapsed();
  			self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
  		}
  
  		function _traverse(how) {
  			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
  
  			if (self[START_CONTAINER] == self[END_CONTAINER]) {
  				return _traverseSameContainer(how);
  			}
  
  			for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  				if (p == self[START_CONTAINER]) {
  					return _traverseCommonStartContainer(c, how);
  				}
  
  				++endContainerDepth;
  			}
  
  			for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  				if (p == self[END_CONTAINER]) {
  					return _traverseCommonEndContainer(c, how);
  				}
  
  				++startContainerDepth;
  			}
  
  			depthDiff = startContainerDepth - endContainerDepth;
  
  			startNode = self[START_CONTAINER];
  			while (depthDiff > 0) {
  				startNode = startNode.parentNode;
  				depthDiff--;
  			}
  
  			endNode = self[END_CONTAINER];
  			while (depthDiff < 0) {
  				endNode = endNode.parentNode;
  				depthDiff++;
  			}
  
  			// ascend the ancestor hierarchy until we have a common parent.
  			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
  				startNode = sp;
  				endNode = ep;
  			}
  
  			return _traverseCommonAncestors(startNode, endNode, how);
  		}
  
  		function _traverseSameContainer(how) {
  			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
  
  			if (how != DELETE) {
  				frag = createDocumentFragment();
  			}
  
  			// If selection is empty, just return the fragment
  			if (self[START_OFFSET] == self[END_OFFSET]) {
  				return frag;
  			}
  
  			// Text node needs special case handling
  			if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
  				// get the substring
  				s = self[START_CONTAINER].nodeValue;
  				sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
  
  				// set the original text node to its new value
  				if (how != CLONE) {
  					n = self[START_CONTAINER];
  					start = self[START_OFFSET];
  					len = self[END_OFFSET] - self[START_OFFSET];
  
  					if (start === 0 && len >= n.nodeValue.length - 1) {
  						n.parentNode.removeChild(n);
  					} else {
  						n.deleteData(start, len);
  					}
  
  					// Nothing is partially selected, so collapse to start point
  					self.collapse(TRUE);
  				}
  
  				if (how == DELETE) {
  					return;
  				}
  
  				if (sub.length > 0) {
  					frag.appendChild(doc.createTextNode(sub));
  				}
  
  				return frag;
  			}
  
  			// Copy nodes between the start/end offsets.
  			n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
  			cnt = self[END_OFFSET] - self[START_OFFSET];
  
  			while (n && cnt > 0) {
  				sibling = n.nextSibling;
  				xferNode = _traverseFullySelected(n, how);
  
  				if (frag) {
  					frag.appendChild(xferNode);
  				}
  
  				--cnt;
  				n = sibling;
  			}
  
  			// Nothing is partially selected, so collapse to start point
  			if (how != CLONE) {
  				self.collapse(TRUE);
  			}
  
  			return frag;
  		}
  
  		function _traverseCommonStartContainer(endAncestor, how) {
  			var frag, n, endIdx, cnt, sibling, xferNode;
  
  			if (how != DELETE) {
  				frag = createDocumentFragment();
  			}
  
  			n = _traverseRightBoundary(endAncestor, how);
  
  			if (frag) {
  				frag.appendChild(n);
  			}
  
  			endIdx = nodeIndex(endAncestor);
  			cnt = endIdx - self[START_OFFSET];
  
  			if (cnt <= 0) {
  				// Collapse to just before the endAncestor, which
  				// is partially selected.
  				if (how != CLONE) {
  					self.setEndBefore(endAncestor);
  					self.collapse(FALSE);
  				}
  
  				return frag;
  			}
  
  			n = endAncestor.previousSibling;
  			while (cnt > 0) {
  				sibling = n.previousSibling;
  				xferNode = _traverseFullySelected(n, how);
  
  				if (frag) {
  					frag.insertBefore(xferNode, frag.firstChild);
  				}
  
  				--cnt;
  				n = sibling;
  			}
  
  			// Collapse to just before the endAncestor, which
  			// is partially selected.
  			if (how != CLONE) {
  				self.setEndBefore(endAncestor);
  				self.collapse(FALSE);
  			}
  
  			return frag;
  		}
  
  		function _traverseCommonEndContainer(startAncestor, how) {
  			var frag, startIdx, n, cnt, sibling, xferNode;
  
  			if (how != DELETE) {
  				frag = createDocumentFragment();
  			}
  
  			n = _traverseLeftBoundary(startAncestor, how);
  			if (frag) {
  				frag.appendChild(n);
  			}
  
  			startIdx = nodeIndex(startAncestor);
  			++startIdx; // Because we already traversed it
  
  			cnt = self[END_OFFSET] - startIdx;
  			n = startAncestor.nextSibling;
  			while (n && cnt > 0) {
  				sibling = n.nextSibling;
  				xferNode = _traverseFullySelected(n, how);
  
  				if (frag) {
  					frag.appendChild(xferNode);
  				}
  
  				--cnt;
  				n = sibling;
  			}
  
  			if (how != CLONE) {
  				self.setStartAfter(startAncestor);
  				self.collapse(TRUE);
  			}
  
  			return frag;
  		}
  
  		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
  			var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
  
  			if (how != DELETE) {
  				frag = createDocumentFragment();
  			}
  
  			n = _traverseLeftBoundary(startAncestor, how);
  			if (frag) {
  				frag.appendChild(n);
  			}
  
  			startOffset = nodeIndex(startAncestor);
  			endOffset = nodeIndex(endAncestor);
  			++startOffset;
  
  			cnt = endOffset - startOffset;
  			sibling = startAncestor.nextSibling;
  
  			while (cnt > 0) {
  				nextSibling = sibling.nextSibling;
  				n = _traverseFullySelected(sibling, how);
  
  				if (frag) {
  					frag.appendChild(n);
  				}
  
  				sibling = nextSibling;
  				--cnt;
  			}
  
  			n = _traverseRightBoundary(endAncestor, how);
  
  			if (frag) {
  				frag.appendChild(n);
  			}
  
  			if (how != CLONE) {
  				self.setStartAfter(startAncestor);
  				self.collapse(TRUE);
  			}
  
  			return frag;
  		}
  
  		function _traverseRightBoundary(root, how) {
  			var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
  			var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
  
  			if (next == root) {
  				return _traverseNode(next, isFullySelected, FALSE, how);
  			}
  
  			parent = next.parentNode;
  			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
  
  			while (parent) {
  				while (next) {
  					prevSibling = next.previousSibling;
  					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
  
  					if (how != DELETE) {
  						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
  					}
  
  					isFullySelected = TRUE;
  					next = prevSibling;
  				}
  
  				if (parent == root) {
  					return clonedParent;
  				}
  
  				next = parent.previousSibling;
  				parent = parent.parentNode;
  
  				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
  
  				if (how != DELETE) {
  					clonedGrandParent.appendChild(clonedParent);
  				}
  
  				clonedParent = clonedGrandParent;
  			}
  		}
  
  		function _traverseLeftBoundary(root, how) {
  			var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
  			var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
  
  			if (next == root) {
  				return _traverseNode(next, isFullySelected, TRUE, how);
  			}
  
  			parent = next.parentNode;
  			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
  
  			while (parent) {
  				while (next) {
  					nextSibling = next.nextSibling;
  					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
  
  					if (how != DELETE) {
  						clonedParent.appendChild(clonedChild);
  					}
  
  					isFullySelected = TRUE;
  					next = nextSibling;
  				}
  
  				if (parent == root) {
  					return clonedParent;
  				}
  
  				next = parent.nextSibling;
  				parent = parent.parentNode;
  
  				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
  
  				if (how != DELETE) {
  					clonedGrandParent.appendChild(clonedParent);
  				}
  
  				clonedParent = clonedGrandParent;
  			}
  		}
  
  		function _traverseNode(n, isFullySelected, isLeft, how) {
  			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
  
  			if (isFullySelected) {
  				return _traverseFullySelected(n, how);
  			}
  
  			if (n.nodeType == 3 /* TEXT_NODE */) {
  				txtValue = n.nodeValue;
  
  				if (isLeft) {
  					offset = self[START_OFFSET];
  					newNodeValue = txtValue.substring(offset);
  					oldNodeValue = txtValue.substring(0, offset);
  				} else {
  					offset = self[END_OFFSET];
  					newNodeValue = txtValue.substring(0, offset);
  					oldNodeValue = txtValue.substring(offset);
  				}
  
  				if (how != CLONE) {
  					n.nodeValue = oldNodeValue;
  				}
  
  				if (how == DELETE) {
  					return;
  				}
  
  				newNode = dom.clone(n, FALSE);
  				newNode.nodeValue = newNodeValue;
  
  				return newNode;
  			}
  
  			if (how == DELETE) {
  				return;
  			}
  
  			return dom.clone(n, FALSE);
  		}
  
  		function _traverseFullySelected(n, how) {
  			if (how != DELETE) {
  				return how == CLONE ? dom.clone(n, TRUE) : n;
  			}
  
  			n.parentNode.removeChild(n);
  		}
  
  		function toStringIE() {
  			return dom.create('body', null, cloneContents()).outerText;
  		}
  
  		extend(self, {
  			// Inital states
  			startContainer: doc,
  			startOffset: 0,
  			endContainer: doc,
  			endOffset: 0,
  			collapsed: TRUE,
  			commonAncestorContainer: doc,
  
  			// Range constants
  			START_TO_START: 0,
  			START_TO_END: 1,
  			END_TO_END: 2,
  			END_TO_START: 3,
  
  			// Public methods
  			setStart: setStart,
  			setEnd: setEnd,
  			setStartBefore: setStartBefore,
  			setStartAfter: setStartAfter,
  			setEndBefore: setEndBefore,
  			setEndAfter: setEndAfter,
  			collapse: collapse,
  			selectNode: selectNode,
  			selectNodeContents: selectNodeContents,
  			compareBoundaryPoints: compareBoundaryPoints,
  			deleteContents: deleteContents,
  			extractContents: extractContents,
  			cloneContents: cloneContents,
  			insertNode: insertNode,
  			surroundContents: surroundContents,
  			cloneRange: cloneRange,
  			toStringIE: toStringIE
  		});
  
  		return self;
  	}
  
  	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
  	Range.prototype.toString = function() {
  		return this.toStringIE();
  	};
  
  	return Range;
  });
  
  // Included from: js/maya/classes/html/Entities.js
  
  /**
   * Entities.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*jshint bitwise:false */
  /*eslint no-bitwise:0 */
  
  /**
   * Entity encoder class.
   *
   * @class maya.html.Entities
   * @static
   * @version 3.4
   */
  define("maya/html/Entities", [
  	"maya/util/Tools"
  ], function(Tools) {
  	var makeMap = Tools.makeMap;
  
  	var namedEntities, baseEntities, reverseEntities,
  		attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  		rawCharsRegExp = /[<>&\"\']/g,
  		entityRegExp = /&(#x|#)?([\w]+);/g,
  		asciiMap = {
  			128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
  			135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
  			142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
  			150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
  			156: "\u0153", 158: "\u017E", 159: "\u0178"
  		};
  
  	// Raw entities
  	baseEntities = {
  		'\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
  		"'": '&#39;',
  		'<': '&lt;',
  		'>': '&gt;',
  		'&': '&amp;',
  		'\u0060': '&#96;'
  	};
  
  	// Reverse lookup table for raw entities
  	reverseEntities = {
  		'&lt;': '<',
  		'&gt;': '>',
  		'&amp;': '&',
  		'&quot;': '"',
  		'&apos;': "'"
  	};
  
  	// Decodes text by using the browser
  	function nativeDecode(text) {
  		var elm;
  
  		elm = document.createElement("div");
  		elm.innerHTML = text;
  
  		return elm.textContent || elm.innerText || text;
  	}
  
  	// Build a two way lookup table for the entities
  	function buildEntitiesLookup(items, radix) {
  		var i, chr, entity, lookup = {};
  
  		if (items) {
  			items = items.split(',');
  			radix = radix || 10;
  
  			// Build entities lookup table
  			for (i = 0; i < items.length; i += 2) {
  				chr = String.fromCharCode(parseInt(items[i], radix));
  
  				// Only add non base entities
  				if (!baseEntities[chr]) {
  					entity = '&' + items[i + 1] + ';';
  					lookup[chr] = entity;
  					lookup[entity] = chr;
  				}
  			}
  
  			return lookup;
  		}
  	}
  
  	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
  	namedEntities = buildEntitiesLookup(
  		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
  		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
  		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
  		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
  		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
  		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
  		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
  		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
  		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
  		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
  		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
  		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
  		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
  		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
  		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
  		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
  		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
  		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
  		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
  		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
  		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
  		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
  		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
  		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
  		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
  
  	var Entities = {
  		/**
  		 * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded.
  		 *
  		 * @method encodeRaw
  		 * @param {String} text Text to encode.
  		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  		 * @return {String} Entity encoded text.
  		 */
  		encodeRaw: function(text, attr) {
  			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  				return baseEntities[chr] || chr;
  			});
  		},
  
  		/**
  		 * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
  		 * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
  		 * and is exposed as the DOMUtils.encode function.
  		 *
  		 * @method encodeAllRaw
  		 * @param {String} text Text to encode.
  		 * @return {String} Entity encoded text.
  		 */
  		encodeAllRaw: function(text) {
  			return ('' + text).replace(rawCharsRegExp, function(chr) {
  				return baseEntities[chr] || chr;
  			});
  		},
  
  		/**
  		 * Encodes the specified string using numeric entities. The core entities will be
  		 * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
  		 *
  		 * @method encodeNumeric
  		 * @param {String} text Text to encode.
  		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  		 * @return {String} Entity encoded text.
  		 */
  		encodeNumeric: function(text, attr) {
  			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  				// Multi byte sequence convert it to a single entity
  				if (chr.length > 1) {
  					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
  				}
  
  				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
  			});
  		},
  
  		/**
  		 * Encodes the specified string using named entities. The core entities will be encoded
  		 * as named ones but all non lower ascii characters will be encoded into named entities.
  		 *
  		 * @method encodeNamed
  		 * @param {String} text Text to encode.
  		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  		 * @param {Object} entities Optional parameter with entities to use.
  		 * @return {String} Entity encoded text.
  		 */
  		encodeNamed: function(text, attr, entities) {
  			entities = entities || namedEntities;
  
  			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  				return baseEntities[chr] || entities[chr] || chr;
  			});
  		},
  
  		/**
  		 * Returns an encode function based on the name(s) and it's optional entities.
  		 *
  		 * @method getEncodeFunc
  		 * @param {String} name Comma separated list of encoders for example named,numeric.
  		 * @param {String} entities Optional parameter with entities to use instead of the built in set.
  		 * @return {function} Encode function to be used.
  		 */
  		getEncodeFunc: function(name, entities) {
  			entities = buildEntitiesLookup(entities) || namedEntities;
  
  			function encodeNamedAndNumeric(text, attr) {
  				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
  				});
  			}
  
  			function encodeCustomNamed(text, attr) {
  				return Entities.encodeNamed(text, attr, entities);
  			}
  
  			// Replace + with , to be compatible with previous maya versions
  			name = makeMap(name.replace(/\+/g, ','));
  
  			// Named and numeric encoder
  			if (name.named && name.numeric) {
  				return encodeNamedAndNumeric;
  			}
  
  			// Named encoder
  			if (name.named) {
  				// Custom names
  				if (entities) {
  					return encodeCustomNamed;
  				}
  
  				return Entities.encodeNamed;
  			}
  
  			// Numeric
  			if (name.numeric) {
  				return Entities.encodeNumeric;
  			}
  
  			// Raw encoder
  			return Entities.encodeRaw;
  		},
  
  		/**
  		 * Decodes the specified string, this will replace entities with raw UTF characters.
  		 *
  		 * @method decode
  		 * @param {String} text Text to entity decode.
  		 * @return {String} Entity decoded string.
  		 */
  		decode: function(text) {
  			return text.replace(entityRegExp, function(all, numeric, value) {
  				if (numeric) {
  					value = parseInt(value, numeric.length === 2 ? 16 : 10);
  
  					// Support upper UTF
  					if (value > 0xFFFF) {
  						value -= 0x10000;
  
  						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
  					} else {
  						return asciiMap[value] || String.fromCharCode(value);
  					}
  				}
  
  				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
  			});
  		}
  	};
  
  	return Entities;
  });
  
  // Included from: js/maya/classes/dom/StyleSheetLoader.js
  
  /**
   * StyleSheetLoader.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles loading of external stylesheets and fires events when these are loaded.
   *
   * @class maya.dom.StyleSheetLoader
   * @private
   */
  define("maya/dom/StyleSheetLoader", [
  	"maya/util/Tools"
  ], function(Tools) {
  	"use strict";
  
  	return function(document, settings) {
  		var idCount = 0, loadedStates = {}, maxLoadTime;
  
  		settings = settings || {};
  		maxLoadTime = settings.maxLoadTime || 5000;
  
  		function appendToHead(node) {
  			document.getElementsByTagName('head')[0].appendChild(node);
  		}
  
  		/**
  		 * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
  		 *
  		 * @method load
  		 * @param {String} url Url to be loaded.
  		 * @param {Function} loadedCallback Callback to be executed when loaded.
  		 * @param {Function} errorCallback Callback to be executed when failed loading.
  		 */
  		function load(url, loadedCallback, errorCallback) {
  			var link, style, startTime, state;
  
  			function passed() {
  				var callbacks = state.passed, i = callbacks.length;
  
  				while (i--) {
  					callbacks[i]();
  				}
  
  				state.status = 2;
  				state.passed = [];
  				state.failed = [];
  			}
  
  			function failed() {
  				var callbacks = state.failed, i = callbacks.length;
  
  				while (i--) {
  					callbacks[i]();
  				}
  
  				state.status = 3;
  				state.passed = [];
  				state.failed = [];
  			}
  
  			// Sniffs for older WebKit versions that have the link.onload but a broken one
  			function isOldWebKit() {
  				var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
  				return !!(webKitChunks && webKitChunks[1] < 536);
  			}
  
  			// Calls the waitCallback until the test returns true or the timeout occurs
  			function wait(testCallback, waitCallback) {
  				if (!testCallback()) {
  					// Wait for timeout
  					if ((new Date().getTime()) - startTime < maxLoadTime) {
  						window.setTimeout(waitCallback, 0);
  					} else {
  						failed();
  					}
  				}
  			}
  
  			// Workaround for WebKit that doesn't properly support the onload event for link elements
  			// Or WebKit that fires the onload event before the StyleSheet is added to the document
  			function waitForWebKitLinkLoaded() {
  				wait(function() {
  					var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
  
  					while (i--) {
  						styleSheet = styleSheets[i];
  						owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
  						if (owner && owner.id === link.id) {
  							passed();
  							return true;
  						}
  					}
  				}, waitForWebKitLinkLoaded);
  			}
  
  			// Workaround for older Geckos that doesn't have any onload event for StyleSheets
  			function waitForGeckoLinkLoaded() {
  				wait(function() {
  					try {
  						// Accessing the cssRules will throw an exception until the CSS file is loaded
  						var cssRules = style.sheet.cssRules;
  						passed();
  						return !!cssRules;
  					} catch (ex) {
  						// Ignore
  					}
  				}, waitForGeckoLinkLoaded);
  			}
  
  			url = Tools._addCacheSuffix(url);
  
  			if (!loadedStates[url]) {
  				state = {
  					passed: [],
  					failed: []
  				};
  
  				loadedStates[url] = state;
  			} else {
  				state = loadedStates[url];
  			}
  
  			if (loadedCallback) {
  				state.passed.push(loadedCallback);
  			}
  
  			if (errorCallback) {
  				state.failed.push(errorCallback);
  			}
  
  			// Is loading wait for it to pass
  			if (state.status == 1) {
  				return;
  			}
  
  			// Has finished loading and was success
  			if (state.status == 2) {
  				passed();
  				return;
  			}
  
  			// Has finished loading and was a failure
  			if (state.status == 3) {
  				failed();
  				return;
  			}
  
  			// Start loading
  			state.status = 1;
  			link = document.createElement('link');
  			link.rel = 'stylesheet';
  			link.type = 'text/css';
  			link.id = 'u' + (idCount++);
  			link.async = false;
  			link.defer = false;
  			startTime = new Date().getTime();
  
  			// Feature detect onload on link element and sniff older webkits since it has an broken onload event
  			if ("onload" in link && !isOldWebKit()) {
  				link.onload = waitForWebKitLinkLoaded;
  				link.onerror = failed;
  			} else {
  				// Sniff for old Firefox that doesn't support the onload event on link elements
  				// TODO: Remove this in the future when everyone uses modern browsers
  				if (navigator.userAgent.indexOf("Firefox") > 0) {
  					style = document.createElement('style');
  					style.textContent = '@import "' + url + '"';
  					waitForGeckoLinkLoaded();
  					appendToHead(style);
  					return;
  				} else {
  					// Use the id owner on older webkits
  					waitForWebKitLinkLoaded();
  				}
  			}
  
  			appendToHead(link);
  			link.href = url;
  		}
  
  		this.load = load;
  	};
  });
  
  // Included from: js/maya/classes/dom/DOMUtils.js
  
  /**
   * DOMUtils.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Utility class for various DOM manipulation and retrieval functions.
   *
   * @class maya.dom.DOMUtils
   * @example
   * // Add a class to an element by id in the page
   * maya.DOM.addClass('someid', 'someclass');
   *
   * // Add a class to an element by id inside the editor
   * maya.activeEditor.dom.addClass('someid', 'someclass');
   */
  define("maya/dom/DOMUtils", [
  	"maya/dom/Sizzle",
  	"maya/dom/DomQuery",
  	"maya/html/Styles",
  	"maya/dom/EventUtils",
  	"maya/dom/TreeWalker",
  	"maya/dom/Range",
  	"maya/html/Entities",
  	"maya/Env",
  	"maya/util/Tools",
  	"maya/dom/StyleSheetLoader"
  ], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
  	// Shorten names
  	var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim;
  	var isIE = Env.ie;
  	var simpleSelectorRe = /^([a-z0-9],?)+$/i;
  	var whiteSpaceRegExp = /^[ \t\r\n]*$/;
  
  	function setupAttrHooks(domUtils, settings) {
  		var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook;
  
  		keepUrlHook = {
  			set: function($elm, value, name) {
  				if (settings.url_converter) {
  					value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]);
  				}
  
  				$elm.attr('data-mce-' + name, value).attr(name, value);
  			},
  
  			get: function($elm, name) {
  				return $elm.attr('data-mce-' + name) || $elm.attr(name);
  			}
  		};
  
  		attrHooks = {
  			style: {
  				set: function($elm, value) {
  					if (value !== null && typeof value === 'object') {
  						$elm.css(value);
  						return;
  					}
  
  					if (keepValues) {
  						$elm.attr('data-mce-style', value);
  					}
  
  					$elm.attr('style', value);
  				},
  
  				get: function($elm) {
  					var value = $elm.attr('data-mce-style') || $elm.attr('style');
  
  					value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
  
  					return value;
  				}
  			}
  		};
  
  		if (keepValues) {
  			attrHooks.href = attrHooks.src = keepUrlHook;
  		}
  
  		return attrHooks;
  	}
  
  	/**
  	 * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
  	 *
  	 * @constructor
  	 * @method DOMUtils
  	 * @param {Document} d Document reference to bind the utility class to.
  	 * @param {settings} s Optional settings collection.
  	 */
  	function DOMUtils(doc, settings) {
  		var self = this, blockElementsMap;
  
  		self.doc = doc;
  		self.win = window;
  		self.files = {};
  		self.counter = 0;
  		self.stdMode = !isIE || doc.documentMode >= 8;
  		self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
  		self.styleSheetLoader = new StyleSheetLoader(doc);
  		self.boundEvents = [];
  		self.settings = settings = settings || {};
  		self.schema = settings.schema;
  		self.styles = new Styles({
  			url_converter: settings.url_converter,
  			url_converter_scope: settings.url_converter_scope
  		}, settings.schema);
  
  		self.fixDoc(doc);
  		self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
  		self.attrHooks = setupAttrHooks(self, settings);
  		blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
  		self.$ = $.overrideDefaults(function() {
  			return {
  				context: doc,
  				element: self.getRoot()
  			};
  		});
  
  		/**
  		 * Returns true/false if the specified element is a block element or not.
  		 *
  		 * @method isBlock
  		 * @param {Node/String} node Element/Node to check.
  		 * @return {Boolean} True/False state if the node is a block element or not.
  		 */
  		self.isBlock = function(node) {
  			// Fix for #5446
  			if (!node) {
  				return false;
  			}
  
  			// This function is called in module pattern style since it might be executed with the wrong this scope
  			var type = node.nodeType;
  
  			// If it's a node then check the type and use the nodeName
  			if (type) {
  				return !!(type === 1 && blockElementsMap[node.nodeName]);
  			}
  
  			return !!blockElementsMap[node];
  		};
  	}
  
  	DOMUtils.prototype = {
  		$$: function(elm) {
  			if (typeof elm == 'string') {
  				elm = this.get(elm);
  			}
  
  			return this.$(elm);
  		},
  
  		root: null,
  
  		fixDoc: function(doc) {
  			var settings = this.settings, name;
  
  			if (isIE && settings.schema) {
  				// Add missing HTML 4/5 elements to IE
  				('abbr article aside audio canvas ' +
  				'details figcaption figure footer ' +
  				'header hgroup mark menu meter nav ' +
  				'output progress section summary ' +
  				'time video').replace(/\w+/g, function(name) {
  					doc.createElement(name);
  				});
  
  				// Create all custom elements
  				for (name in settings.schema.getCustomElements()) {
  					doc.createElement(name);
  				}
  			}
  		},
  
  		clone: function(node, deep) {
  			var self = this, clone, doc;
  
  			// TODO: Add feature detection here in the future
  			if (!isIE || node.nodeType !== 1 || deep) {
  				return node.cloneNode(deep);
  			}
  
  			doc = self.doc;
  
  			// Make a HTML5 safe shallow copy
  			if (!deep) {
  				clone = doc.createElement(node.nodeName);
  
  				// Copy attribs
  				each(self.getAttribs(node), function(attr) {
  					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
  				});
  
  				return clone;
  			}
  
  			return clone.firstChild;
  		},
  
  		/**
  		 * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
  		 * go above the point of this root node.
  		 *
  		 * @method getRoot
  		 * @return {Element} Root element for the utility class.
  		 */
  		getRoot: function() {
  			var self = this;
  
  			return self.settings.root_element || self.doc.body;
  		},
  
  		/**
  		 * Returns the viewport of the window.
  		 *
  		 * @method getViewPort
  		 * @param {Window} win Optional window to get viewport of.
  		 * @return {Object} Viewport object with fields x, y, w and h.
  		 */
  		getViewPort: function(win) {
  			var doc, rootElm;
  
  			win = !win ? this.win : win;
  			doc = win.document;
  			rootElm = this.boxModel ? doc.documentElement : doc.body;
  
  			// Returns viewport size excluding scrollbars
  			return {
  				x: win.pageXOffset || rootElm.scrollLeft,
  				y: win.pageYOffset || rootElm.scrollTop,
  				w: win.innerWidth || rootElm.clientWidth,
  				h: win.innerHeight || rootElm.clientHeight
  			};
  		},
  
  		/**
  		 * Returns the rectangle for a specific element.
  		 *
  		 * @method getRect
  		 * @param {Element/String} elm Element object or element ID to get rectangle from.
  		 * @return {object} Rectangle for specified element object with x, y, w, h fields.
  		 */
  		getRect: function(elm) {
  			var self = this, pos, size;
  
  			elm = self.get(elm);
  			pos = self.getPos(elm);
  			size = self.getSize(elm);
  
  			return {
  				x: pos.x, y: pos.y,
  				w: size.w, h: size.h
  			};
  		},
  
  		/**
  		 * Returns the size dimensions of the specified element.
  		 *
  		 * @method getSize
  		 * @param {Element/String} elm Element object or element ID to get rectangle from.
  		 * @return {object} Rectangle for specified element object with w, h fields.
  		 */
  		getSize: function(elm) {
  			var self = this, w, h;
  
  			elm = self.get(elm);
  			w = self.getStyle(elm, 'width');
  			h = self.getStyle(elm, 'height');
  
  			// Non pixel value, then force offset/clientWidth
  			if (w.indexOf('px') === -1) {
  				w = 0;
  			}
  
  			// Non pixel value, then force offset/clientWidth
  			if (h.indexOf('px') === -1) {
  				h = 0;
  			}
  
  			return {
  				w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
  				h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
  			};
  		},
  
  		/**
  		 * Returns a node by the specified selector function. This function will
  		 * loop through all parent nodes and call the specified function for each node.
  		 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
  		 * and the node it found will be returned.
  		 *
  		 * @method getParent
  		 * @param {Node/String} node DOM node to search parents on or ID string.
  		 * @param {function} selector Selection function or CSS selector to execute on each node.
  		 * @param {Node} root Optional root element, never go below this point.
  		 * @return {Node} DOM Node or null if it wasn't found.
  		 */
  		getParent: function(node, selector, root) {
  			return this.getParents(node, selector, root, false);
  		},
  
  		/**
  		 * Returns a node list of all parents matching the specified selector function or pattern.
  		 * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
  		 *
  		 * @method getParents
  		 * @param {Node/String} node DOM node to search parents on or ID string.
  		 * @param {function} selector Selection function to execute on each node or CSS pattern.
  		 * @param {Node} root Optional root element, never go below this point.
  		 * @return {Array} Array of nodes or null if it wasn't found.
  		 */
  		getParents: function(node, selector, root, collect) {
  			var self = this, selectorVal, result = [];
  
  			node = self.get(node);
  			collect = collect === undefined;
  
  			// Default root on inline mode
  			root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
  
  			// Wrap node name as func
  			if (is(selector, 'string')) {
  				selectorVal = selector;
  
  				if (selector === '*') {
  					selector = function(node) {
  						return node.nodeType == 1;
  					};
  				} else {
  					selector = function(node) {
  						return self.is(node, selectorVal);
  					};
  				}
  			}
  
  			while (node) {
  				if (node == root || !node.nodeType || node.nodeType === 9) {
  					break;
  				}
  
  				if (!selector || selector(node)) {
  					if (collect) {
  						result.push(node);
  					} else {
  						return node;
  					}
  				}
  
  				node = node.parentNode;
  			}
  
  			return collect ? result : null;
  		},
  
  		/**
  		 * Returns the specified element by ID or the input element if it isn't a string.
  		 *
  		 * @method get
  		 * @param {String/Element} n Element id to look for or element to just pass though.
  		 * @return {Element} Element matching the specified id or null if it wasn't found.
  		 */
  		get: function(elm) {
  			var name;
  
  			if (elm && this.doc && typeof elm == 'string') {
  				name = elm;
  				elm = this.doc.getElementById(elm);
  
  				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
  				if (elm && elm.id !== name) {
  					return this.doc.getElementsByName(name)[1];
  				}
  			}
  
  			return elm;
  		},
  
  		/**
  		 * Returns the next node that matches selector or function
  		 *
  		 * @method getNext
  		 * @param {Node} node Node to find siblings from.
  		 * @param {String/function} selector Selector CSS expression or function.
  		 * @return {Node} Next node item matching the selector or null if it wasn't found.
  		 */
  		getNext: function(node, selector) {
  			return this._findSib(node, selector, 'nextSibling');
  		},
  
  		/**
  		 * Returns the previous node that matches selector or function
  		 *
  		 * @method getPrev
  		 * @param {Node} node Node to find siblings from.
  		 * @param {String/function} selector Selector CSS expression or function.
  		 * @return {Node} Previous node item matching the selector or null if it wasn't found.
  		 */
  		getPrev: function(node, selector) {
  			return this._findSib(node, selector, 'previousSibling');
  		},
  
  		// #ifndef jquery
  
  		/**
  		 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
  		 * This function is optimized for the most common patterns needed in maya but it also performs well enough
  		 * on more complex patterns.
  		 *
  		 * @method select
  		 * @param {String} selector CSS level 3 pattern to select/find elements by.
  		 * @param {Object} scope Optional root element/scope element to search in.
  		 * @return {Array} Array with all matched elements.
  		 * @example
  		 * // Adds a class to all paragraphs in the currently active editor
  		 * maya.activeEditor.dom.addClass(maya.activeEditor.dom.select('p'), 'someclass');
  		 *
  		 * // Adds a class to all spans that have the test class in the currently active editor
  		 * maya.activeEditor.dom.addClass(maya.activeEditor.dom.select('span.test'), 'someclass')
  		 */
  		select: function(selector, scope) {
  			var self = this;
  
  			/*eslint new-cap:0 */
  			return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []);
  		},
  
  		/**
  		 * Returns true/false if the specified element matches the specified css pattern.
  		 *
  		 * @method is
  		 * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
  		 * @param {String} selector CSS pattern to match the element against.
  		 */
  		is: function(elm, selector) {
  			var i;
  
  			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
  			if (elm.length === undefined) {
  				// Simple all selector
  				if (selector === '*') {
  					return elm.nodeType == 1;
  				}
  
  				// Simple selector just elements
  				if (simpleSelectorRe.test(selector)) {
  					selector = selector.toLowerCase().split(/,/);
  					elm = elm.nodeName.toLowerCase();
  
  					for (i = selector.length - 1; i >= 0; i--) {
  						if (selector[i] == elm) {
  							return true;
  						}
  					}
  
  					return false;
  				}
  			}
  
  			// Is non element
  			if (elm.nodeType && elm.nodeType != 1) {
  				return false;
  			}
  
  			var elms = elm.nodeType ? [elm] : elm;
  
  			/*eslint new-cap:0 */
  			return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
  		},
  
  		// #endif
  
  		/**
  		 * Adds the specified element to another element or elements.
  		 *
  		 * @method add
  		 * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
  		 * @param {String/Element} name Name of new element to add or existing element to add.
  		 * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
  		 * @param {String} html Optional inner HTML contents to add for each element.
  		 * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
  		 * were passed in.
  		 * @example
  		 * // Adds a new paragraph to the end of the active editor
  		 * maya.activeEditor.dom.add(maya.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
  		 */
  		add: function(parentElm, name, attrs, html, create) {
  			var self = this;
  
  			return this.run(parentElm, function(parentElm) {
  				var newElm;
  
  				newElm = is(name, 'string') ? self.doc.createElement(name) : name;
  				self.setAttribs(newElm, attrs);
  
  				if (html) {
  					if (html.nodeType) {
  						newElm.appendChild(html);
  					} else {
  						self.setHTML(newElm, html);
  					}
  				}
  
  				return !create ? parentElm.appendChild(newElm) : newElm;
  			});
  		},
  
  		/**
  		 * Creates a new element.
  		 *
  		 * @method create
  		 * @param {String} name Name of new element.
  		 * @param {Object} attrs Optional object name/value collection with element attributes.
  		 * @param {String} html Optional HTML string to set as inner HTML of the element.
  		 * @return {Element} HTML DOM node element that got created.
  		 * @example
  		 * // Adds an element where the caret/selection is in the active editor
  		 * var el = maya.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
  		 * maya.activeEditor.selection.setNode(el);
  		 */
  		create: function(name, attrs, html) {
  			return this.add(this.doc.createElement(name), name, attrs, html, 1);
  		},
  
  		/**
  		 * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
  		 *
  		 * @method createHTML
  		 * @param {String} name Name of new element.
  		 * @param {Object} attrs Optional object name/value collection with element attributes.
  		 * @param {String} html Optional HTML string to set as inner HTML of the element.
  		 * @return {String} String with new HTML element, for example: <a href="#">test</a>.
  		 * @example
  		 * // Creates a html chunk and inserts it at the current selection/caret location
  		 * maya.activeEditor.selection.setContent(maya.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
  		 */
  		createHTML: function(name, attrs, html) {
  			var outHtml = '', key;
  
  			outHtml += '<' + name;
  
  			for (key in attrs) {
  				if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') {
  					outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
  				}
  			}
  
  			// A call to maya.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
  			if (typeof html != "undefined") {
  				return outHtml + '>' + html + '</' + name + '>';
  			}
  
  			return outHtml + ' />';
  		},
  
  		/**
  		 * Creates a document fragment out of the specified HTML string.
  		 *
  		 * @method createFragment
  		 * @param {String} html Html string to create fragment from.
  		 * @return {DocumentFragment} Document fragment node.
  		 */
  		createFragment: function(html) {
  			var frag, node, doc = this.doc, container;
  
  			container = doc.createElement("div");
  			frag = doc.createDocumentFragment();
  
  			if (html) {
  				container.innerHTML = html;
  			}
  
  			while ((node = container.firstChild)) {
  				frag.appendChild(node);
  			}
  
  			return frag;
  		},
  
  		/**
  		 * Removes/deletes the specified element(s) from the DOM.
  		 *
  		 * @method remove
  		 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
  		 * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
  		 * placed at the location of the removed element.
  		 * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
  		 * were passed in.
  		 * @example
  		 * // Removes all paragraphs in the active editor
  		 * maya.activeEditor.dom.remove(maya.activeEditor.dom.select('p'));
  		 *
  		 * // Removes an element by id in the document
  		 * maya.DOM.remove('mydiv');
  		 */
  		remove: function(node, keepChildren) {
  			node = this.$$(node);
  
  			if (keepChildren) {
  				node.each(function() {
  					var child;
  
  					while ((child = this.firstChild)) {
  						if (child.nodeType == 3 && child.data.length === 0) {
  							this.removeChild(child);
  						} else {
  							this.parentNode.insertBefore(child, this);
  						}
  					}
  				}).remove();
  			} else {
  				node.remove();
  			}
  
  			return node.length > 1 ? node.toArray() : node[0];
  		},
  
  		/**
  		 * Sets the CSS style value on a HTML element. The name can be a camelcase string
  		 * or the CSS style name like background-color.
  		 *
  		 * @method setStyle
  		 * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
  		 * @param {String} na Name of the style value to set.
  		 * @param {String} v Value to set on the style.
  		 * @example
  		 * // Sets a style value on all paragraphs in the currently active editor
  		 * maya.activeEditor.dom.setStyle(maya.activeEditor.dom.select('p'), 'background-color', 'red');
  		 *
  		 * // Sets a style value to an element by id in the current document
  		 * maya.DOM.setStyle('mydiv', 'background-color', 'red');
  		 */
  		setStyle: function(elm, name, value) {
  			elm = this.$$(elm).css(name, value);
  
  			if (this.settings.update_styles) {
  				elm.attr('data-mce-style', null);
  			}
  		},
  
  		/**
  		 * Returns the current style or runtime/computed value of an element.
  		 *
  		 * @method getStyle
  		 * @param {String/Element} elm HTML element or element id string to get style from.
  		 * @param {String} name Style name to return.
  		 * @param {Boolean} computed Computed style.
  		 * @return {String} Current style or computed style value of an element.
  		 */
  		getStyle: function(elm, name, computed) {
  			elm = this.$$(elm);
  
  			if (computed) {
  				return elm.css(name);
  			}
  
  			// Camelcase it, if needed
  			name = name.replace(/-(\D)/g, function(a, b) {
  				return b.toUpperCase();
  			});
  
  			if (name == 'float') {
  				name = isIE ? 'styleFloat' : 'cssFloat';
  			}
  
  			return elm[0] && elm[0].style ? elm[0].style[name] : undefined;
  		},
  
  		/**
  		 * Sets multiple styles on the specified element(s).
  		 *
  		 * @method setStyles
  		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
  		 * @param {Object} o Name/Value collection of style items to add to the element(s).
  		 * @example
  		 * // Sets styles on all paragraphs in the currently active editor
  		 * maya.activeEditor.dom.setStyles(maya.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
  		 *
  		 * // Sets styles to an element by id in the current document
  		 * maya.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
  		 */
  		setStyles: function(elm, styles) {
  			elm = this.$$(elm).css(styles);
  
  			if (this.settings.update_styles) {
  				elm.attr('data-mce-style', null);
  			}
  		},
  
  		/**
  		 * Removes all attributes from an element or elements.
  		 *
  		 * @method removeAllAttribs
  		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
  		 */
  		removeAllAttribs: function(e) {
  			return this.run(e, function(e) {
  				var i, attrs = e.attributes;
  				for (i = attrs.length - 1; i >= 0; i--) {
  					e.removeAttributeNode(attrs.item(i));
  				}
  			});
  		},
  
  		/**
  		 * Sets the specified attribute of an element or elements.
  		 *
  		 * @method setAttrib
  		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
  		 * @param {String} n Name of attribute to set.
  		 * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
  		 * @example
  		 * // Sets class attribute on all paragraphs in the active editor
  		 * maya.activeEditor.dom.setAttrib(maya.activeEditor.dom.select('p'), 'class', 'myclass');
  		 *
  		 * // Sets class attribute on a specific element in the current page
  		 * maya.dom.setAttrib('mydiv', 'class', 'myclass');
  		 */
  		setAttrib: function(elm, name, value) {
  			var self = this, originalValue, hook, settings = self.settings;
  
  			if (value === '') {
  				value = null;
  			}
  
  			elm = self.$$(elm);
  			originalValue = elm.attr(name);
  
  			if (!elm.length) {
  				return;
  			}
  
  			hook = self.attrHooks[name];
  			if (hook && hook.set) {
  				hook.set(elm, value, name);
  			} else {
  				elm.attr(name, value);
  			}
  
  			if (originalValue != value && settings.onSetAttrib) {
  				settings.onSetAttrib({
  					attrElm: elm,
  					attrName: name,
  					attrValue: value
  				});
  			}
  		},
  
  		/**
  		 * Sets two or more specified attributes of an element or elements.
  		 *
  		 * @method setAttribs
  		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
  		 * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
  		 * @example
  		 * // Sets class and title attributes on all paragraphs in the active editor
  		 * maya.activeEditor.dom.setAttribs(maya.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
  		 *
  		 * // Sets class and title attributes on a specific element in the current page
  		 * maya.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
  		 */
  		setAttribs: function(elm, attrs) {
  			var self = this;
  
  			self.$$(elm).each(function(i, node) {
  				each(attrs, function(value, name) {
  					self.setAttrib(node, name, value);
  				});
  			});
  		},
  
  		/**
  		 * Returns the specified attribute by name.
  		 *
  		 * @method getAttrib
  		 * @param {String/Element} elm Element string id or DOM element to get attribute from.
  		 * @param {String} name Name of attribute to get.
  		 * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
  		 * @return {String} Attribute value string, default value or null if the attribute wasn't found.
  		 */
  		getAttrib: function(elm, name, defaultVal) {
  			var self = this, hook, value;
  
  			elm = self.$$(elm);
  
  			if (elm.length) {
  				hook = self.attrHooks[name];
  
  				if (hook && hook.get) {
  					value = hook.get(elm, name);
  				} else {
  					value = elm.attr(name);
  				}
  			}
  
  			if (typeof value == 'undefined') {
  				value = defaultVal || '';
  			}
  
  			return value;
  		},
  
  		/**
  		 * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
  		 *
  		 * @method getPos
  		 * @param {Element/String} elm HTML element or element id to get x, y position from.
  		 * @param {Element} rootElm Optional root element to stop calculations at.
  		 * @return {object} Absolute position of the specified element object with x, y fields.
  		 */
  		getPos: function(elm, rootElm) {
  			var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos;
  
  			elm = self.get(elm);
  			rootElm = rootElm || body;
  
  			if (elm) {
  				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
  				// Fallback to offsetParent calculations if the body isn't static better since it stops at the body root
  				if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') {
  					pos = elm.getBoundingClientRect();
  					rootElm = self.boxModel ? doc.documentElement : body;
  
  					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
  					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
  					x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft;
  					y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop;
  
  					return {x: x, y: y};
  				}
  
  				offsetParent = elm;
  				while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  					x += offsetParent.offsetLeft || 0;
  					y += offsetParent.offsetTop || 0;
  					offsetParent = offsetParent.offsetParent;
  				}
  
  				offsetParent = elm.parentNode;
  				while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  					x -= offsetParent.scrollLeft || 0;
  					y -= offsetParent.scrollTop || 0;
  					offsetParent = offsetParent.parentNode;
  				}
  			}
  
  			return {x: x, y: y};
  		},
  
  		/**
  		 * Parses the specified style value into an object collection. This parser will also
  		 * merge and remove any redundant items that browsers might have added. It will also convert non-hex
  		 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  		 *
  		 * @method parseStyle
  		 * @param {String} cssText Style value to parse, for example: border:1px solid red;.
  		 * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
  		 */
  		parseStyle: function(cssText) {
  			return this.styles.parse(cssText);
  		},
  
  		/**
  		 * Serializes the specified style object into a string.
  		 *
  		 * @method serializeStyle
  		 * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
  		 * @param {String} name Optional element name.
  		 * @return {String} String representation of the style object, for example: border: 1px solid red.
  		 */
  		serializeStyle: function(styles, name) {
  			return this.styles.serialize(styles, name);
  		},
  
  		/**
  		 * Adds a style element at the top of the document with the specified cssText content.
  		 *
  		 * @method addStyle
  		 * @param {String} cssText CSS Text style to add to top of head of document.
  		 */
  		addStyle: function(cssText) {
  			var self = this, doc = self.doc, head, styleElm;
  
  			// Prevent inline from loading the same styles twice
  			if (self !== DOMUtils.DOM && doc === document) {
  				var addedStyles = DOMUtils.DOM.addedStyles;
  
  				addedStyles = addedStyles || [];
  				if (addedStyles[cssText]) {
  					return;
  				}
  
  				addedStyles[cssText] = true;
  				DOMUtils.DOM.addedStyles = addedStyles;
  			}
  
  			// Create style element if needed
  			styleElm = doc.getElementById('mceDefaultStyles');
  			if (!styleElm) {
  				styleElm = doc.createElement('style');
  				styleElm.id = 'mceDefaultStyles';
  				styleElm.type = 'text/css';
  
  				head = doc.getElementsByTagName('head')[0];
  				if (head.firstChild) {
  					head.insertBefore(styleElm, head.firstChild);
  				} else {
  					head.appendChild(styleElm);
  				}
  			}
  
  			// Append style data to old or new style element
  			if (styleElm.styleSheet) {
  				styleElm.styleSheet.cssText += cssText;
  			} else {
  				styleElm.appendChild(doc.createTextNode(cssText));
  			}
  		},
  
  		/**
  		 * Imports/loads the specified CSS file into the document bound to the class.
  		 *
  		 * @method loadCSS
  		 * @param {String} u URL to CSS file to load.
  		 * @example
  		 * // Loads a CSS file dynamically into the current document
  		 * maya.DOM.loadCSS('somepath/some.css');
  		 *
  		 * // Loads a CSS file into the currently active editor instance
  		 * maya.activeEditor.dom.loadCSS('somepath/some.css');
  		 *
  		 * // Loads a CSS file into an editor instance by id
  		 * maya.get('someid').dom.loadCSS('somepath/some.css');
  		 *
  		 * // Loads multiple CSS files into the current document
  		 * maya.DOM.loadCSS('somepath/some.css,somepath/someother.css');
  		 */
  		loadCSS: function(url) {
  			var self = this, doc = self.doc, head;
  
  			// Prevent inline from loading the same CSS file twice
  			if (self !== DOMUtils.DOM && doc === document) {
  				DOMUtils.DOM.loadCSS(url);
  				return;
  			}
  
  			if (!url) {
  				url = '';
  			}
  
  			head = doc.getElementsByTagName('head')[0];
  
  			each(url.split(','), function(url) {
  				var link;
  
  				url = Tools._addCacheSuffix(url);
  
  				if (self.files[url]) {
  					return;
  				}
  
  				self.files[url] = true;
  				link = self.create('link', {rel: 'stylesheet', href: url});
  
  				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
  				// This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
  				// It's ugly but it seems to work fine.
  				if (isIE && doc.documentMode && doc.recalc) {
  					link.onload = function() {
  						if (doc.recalc) {
  							doc.recalc();
  						}
  
  						link.onload = null;
  					};
  				}
  
  				head.appendChild(link);
  			});
  		},
  
  		/**
  		 * Adds a class to the specified element or elements.
  		 *
  		 * @method addClass
  		 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  		 * @param {String} cls Class name to add to each element.
  		 * @return {String/Array} String with new class value or array with new class values for all elements.
  		 * @example
  		 * // Adds a class to all paragraphs in the active editor
  		 * maya.activeEditor.dom.addClass(maya.activeEditor.dom.select('p'), 'myclass');
  		 *
  		 * // Adds a class to a specific element in the current page
  		 * maya.DOM.addClass('mydiv', 'myclass');
  		 */
  		addClass: function(elm, cls) {
  			this.$$(elm).addClass(cls);
  		},
  
  		/**
  		 * Removes a class from the specified element or elements.
  		 *
  		 * @method removeClass
  		 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  		 * @param {String} cls Class name to remove from each element.
  		 * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
  		 * were passed in.
  		 * @example
  		 * // Removes a class from all paragraphs in the active editor
  		 * maya.activeEditor.dom.removeClass(maya.activeEditor.dom.select('p'), 'myclass');
  		 *
  		 * // Removes a class from a specific element in the current page
  		 * maya.DOM.removeClass('mydiv', 'myclass');
  		 */
  		removeClass: function(elm, cls) {
  			this.toggleClass(elm, cls, false);
  		},
  
  		/**
  		 * Returns true if the specified element has the specified class.
  		 *
  		 * @method hasClass
  		 * @param {String/Element} n HTML element or element id string to check CSS class on.
  		 * @param {String} c CSS class to check for.
  		 * @return {Boolean} true/false if the specified element has the specified class.
  		 */
  		hasClass: function(elm, cls) {
  			return this.$$(elm).hasClass(cls);
  		},
  
  		/**
  		 * Toggles the specified class on/off.
  		 *
  		 * @method toggleClass
  		 * @param {Element} elm Element to toggle class on.
  		 * @param {[type]} cls Class to toggle on/off.
  		 * @param {[type]} state Optional state to set.
  		 */
  		toggleClass: function(elm, cls, state) {
  			this.$$(elm).toggleClass(cls, state).each(function() {
  				if (this.className === '') {
  					$(this).attr('class', null);
  				}
  			});
  		},
  
  		/**
  		 * Shows the specified element(s) by ID by setting the "display" style.
  		 *
  		 * @method show
  		 * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
  		 */
  		show: function(elm) {
  			this.$$(elm).show();
  		},
  
  		/**
  		 * Hides the specified element(s) by ID by setting the "display" style.
  		 *
  		 * @method hide
  		 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
  		 * @example
  		 * // Hides an element by id in the document
  		 * maya.DOM.hide('myid');
  		 */
  		hide: function(elm) {
  			this.$$(elm).hide();
  		},
  
  		/**
  		 * Returns true/false if the element is hidden or not by checking the "display" style.
  		 *
  		 * @method isHidden
  		 * @param {String/Element} e Id or element to check display state on.
  		 * @return {Boolean} true/false if the element is hidden or not.
  		 */
  		isHidden: function(elm) {
  			return this.$$(elm).css('display') == 'none';
  		},
  
  		/**
  		 * Returns a unique id. This can be useful when generating elements on the fly.
  		 * This method will not check if the element already exists.
  		 *
  		 * @method uniqueId
  		 * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
  		 * @return {String} Unique id.
  		 */
  		uniqueId: function(prefix) {
  			return (!prefix ? 'mce_' : prefix) + (this.counter++);
  		},
  
  		/**
  		 * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
  		 * URLs will get converted, hex color values fixed etc. Check processHTML for details.
  		 *
  		 * @method setHTML
  		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
  		 * @param {String} h HTML content to set as inner HTML of the element.
  		 * @example
  		 * // Sets the inner HTML of all paragraphs in the active editor
  		 * maya.activeEditor.dom.setHTML(maya.activeEditor.dom.select('p'), 'some inner html');
  		 *
  		 * // Sets the inner HTML of an element by id in the document
  		 * maya.DOM.setHTML('mydiv', 'some inner html');
  		 */
  		setHTML: function(elm, html) {
  			elm = this.$$(elm);
  
  			if (isIE) {
  				elm.each(function(i, target) {
  					if (target.canHaveHTML === false) {
  						return;
  					}
  
  					// Remove all child nodes, IE keeps empty text nodes in DOM
  					while (target.firstChild) {
  						target.removeChild(target.firstChild);
  					}
  
  					try {
  						// IE will remove comments from the beginning
  						// unless you padd the contents with something
  						target.innerHTML = '<br>' + html;
  						target.removeChild(target.firstChild);
  					} catch (ex) {
  						// IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
  						$('<div>').html('<br>' + html).contents().slice(1).appendTo(target);
  					}
  
  					return html;
  				});
  			} else {
  				elm.html(html);
  			}
  		},
  
  		/**
  		 * Returns the outer HTML of an element.
  		 *
  		 * @method getOuterHTML
  		 * @param {String/Element} elm Element ID or element object to get outer HTML from.
  		 * @return {String} Outer HTML string.
  		 * @example
  		 * maya.DOM.getOuterHTML(editorElement);
  		 * maya.activeEditor.getOuterHTML(maya.activeEditor.getBody());
  		 */
  		getOuterHTML: function(elm) {
  			elm = this.get(elm);
  
  			// Older FF doesn't have outerHTML 3.6 is still used by some orgaizations
  			return elm.nodeType == 1 && "outerHTML" in elm ? elm.outerHTML : $('<div>').append($(elm).clone()).html();
  		},
  
  		/**
  		 * Sets the specified outer HTML on an element or elements.
  		 *
  		 * @method setOuterHTML
  		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
  		 * @param {Object} html HTML code to set as outer value for the element.
  		 * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
  		 * @example
  		 * // Sets the outer HTML of all paragraphs in the active editor
  		 * maya.activeEditor.dom.setOuterHTML(maya.activeEditor.dom.select('p'), '<div>some html</div>');
  		 *
  		 * // Sets the outer HTML of an element by id in the document
  		 * maya.DOM.setOuterHTML('mydiv', '<div>some html</div>');
  		 */
  		setOuterHTML: function(elm, html) {
  			var self = this;
  
  			self.$$(elm).each(function() {
  				try {
  					// Older FF doesn't have outerHTML 3.6 is still used by some orgaizations
  					if ("outerHTML" in this) {
  						this.outerHTML = html;
  						return;
  					}
  				} catch (ex) {
  					// Ignore
  				}
  
  				// OuterHTML for IE it sometimes produces an "unknown runtime error"
  				self.remove($(this).html(html), true);
  			});
  		},
  
  		/**
  		 * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
  		 *
  		 * @method decode
  		 * @param {String} s String to decode entities on.
  		 * @return {String} Entity decoded string.
  		 */
  		decode: Entities.decode,
  
  		/**
  		 * Entity encodes a string. This method encodes the most common entities, such as <>"&.
  		 *
  		 * @method encode
  		 * @param {String} text String to encode with entities.
  		 * @return {String} Entity encoded string.
  		 */
  		encode: Entities.encodeAllRaw,
  
  		/**
  		 * Inserts an element after the reference element.
  		 *
  		 * @method insertAfter
  		 * @param {Element} node Element to insert after the reference.
  		 * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
  		 * @return {Element/Array} Element that got added or an array with elements.
  		 */
  		insertAfter: function(node, referenceNode) {
  			referenceNode = this.get(referenceNode);
  
  			return this.run(node, function(node) {
  				var parent, nextSibling;
  
  				parent = referenceNode.parentNode;
  				nextSibling = referenceNode.nextSibling;
  
  				if (nextSibling) {
  					parent.insertBefore(node, nextSibling);
  				} else {
  					parent.appendChild(node);
  				}
  
  				return node;
  			});
  		},
  
  		/**
  		 * Replaces the specified element or elements with the new element specified. The new element will
  		 * be cloned if multiple input elements are passed in.
  		 *
  		 * @method replace
  		 * @param {Element} newElm New element to replace old ones with.
  		 * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
  		 * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
  		 */
  		replace: function(newElm, oldElm, keepChildren) {
  			var self = this;
  
  			return self.run(oldElm, function(oldElm) {
  				if (is(oldElm, 'array')) {
  					newElm = newElm.cloneNode(true);
  				}
  
  				if (keepChildren) {
  					each(grep(oldElm.childNodes), function(node) {
  						newElm.appendChild(node);
  					});
  				}
  
  				return oldElm.parentNode.replaceChild(newElm, oldElm);
  			});
  		},
  
  		/**
  		 * Renames the specified element and keeps its attributes and children.
  		 *
  		 * @method rename
  		 * @param {Element} elm Element to rename.
  		 * @param {String} name Name of the new element.
  		 * @return {Element} New element or the old element if it needed renaming.
  		 */
  		rename: function(elm, name) {
  			var self = this, newElm;
  
  			if (elm.nodeName != name.toUpperCase()) {
  				// Rename block element
  				newElm = self.create(name);
  
  				// Copy attribs to new block
  				each(self.getAttribs(elm), function(attrNode) {
  					self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName));
  				});
  
  				// Replace block
  				self.replace(newElm, elm, 1);
  			}
  
  			return newElm || elm;
  		},
  
  		/**
  		 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
  		 *
  		 * @method findCommonAncestor
  		 * @param {Element} a Element to find common ancestor of.
  		 * @param {Element} b Element to find common ancestor of.
  		 * @return {Element} Common ancestor element of the two input elements.
  		 */
  		findCommonAncestor: function(a, b) {
  			var ps = a, pe;
  
  			while (ps) {
  				pe = b;
  
  				while (pe && ps != pe) {
  					pe = pe.parentNode;
  				}
  
  				if (ps == pe) {
  					break;
  				}
  
  				ps = ps.parentNode;
  			}
  
  			if (!ps && a.ownerDocument) {
  				return a.ownerDocument.documentElement;
  			}
  
  			return ps;
  		},
  
  		/**
  		 * Parses the specified RGB color value and returns a hex version of that color.
  		 *
  		 * @method toHex
  		 * @param {String} rgbVal RGB string value like rgb(1,2,3)
  		 * @return {String} Hex version of that RGB value like #FF00FF.
  		 */
  		toHex: function(rgbVal) {
  			return this.styles.toHex(Tools.trim(rgbVal));
  		},
  
  		/**
  		 * Executes the specified function on the element by id or dom element node or array of elements/id.
  		 *
  		 * @method run
  		 * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
  		 * @param {function} f Function to execute for each item.
  		 * @param {Object} s Optional scope to execute the function in.
  		 * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
  		 */
  		run: function(elm, func, scope) {
  			var self = this, result;
  
  			if (typeof elm === 'string') {
  				elm = self.get(elm);
  			}
  
  			if (!elm) {
  				return false;
  			}
  
  			scope = scope || this;
  			if (!elm.nodeType && (elm.length || elm.length === 0)) {
  				result = [];
  
  				each(elm, function(elm, i) {
  					if (elm) {
  						if (typeof elm == 'string') {
  							elm = self.get(elm);
  						}
  
  						result.push(func.call(scope, elm, i));
  					}
  				});
  
  				return result;
  			}
  
  			return func.call(scope, elm);
  		},
  
  		/**
  		 * Returns a NodeList with attributes for the element.
  		 *
  		 * @method getAttribs
  		 * @param {HTMLElement/string} elm Element node or string id to get attributes from.
  		 * @return {NodeList} NodeList with attributes.
  		 */
  		getAttribs: function(elm) {
  			var attrs;
  
  			elm = this.get(elm);
  
  			if (!elm) {
  				return [];
  			}
  
  			if (isIE) {
  				attrs = [];
  
  				// Object will throw exception in IE
  				if (elm.nodeName == 'OBJECT') {
  					return elm.attributes;
  				}
  
  				// IE doesn't keep the selected attribute if you clone option elements
  				if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
  					attrs.push({specified: 1, nodeName: 'selected'});
  				}
  
  				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
  				var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
  				elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
  					attrs.push({specified: 1, nodeName: a});
  				});
  
  				return attrs;
  			}
  
  			return elm.attributes;
  		},
  
  		/**
  		 * Returns true/false if the specified node is to be considered empty or not.
  		 *
  		 * @example
  		 * maya.DOM.isEmpty(node, {img: true});
  		 * @method isEmpty
  		 * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
  		 * @return {Boolean} true/false if the node is empty or not.
  		 */
  		isEmpty: function(node, elements) {
  			var self = this, i, attributes, type, walker, name, brCount = 0;
  
  			node = node.firstChild;
  			if (node) {
  				walker = new TreeWalker(node, node.parentNode);
  				elements = elements || (self.schema ? self.schema.getNonEmptyElements() : null);
  
  				do {
  					type = node.nodeType;
  
  					if (type === 1) {
  						// Ignore bogus elements
  						if (node.getAttribute('data-mce-bogus')) {
  							continue;
  						}
  
  						// Keep empty elements like <img />
  						name = node.nodeName.toLowerCase();
  						if (elements && elements[name]) {
  							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
  							if (name === 'br') {
  								brCount++;
  								continue;
  							}
  
  							return false;
  						}
  
  						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
  						attributes = self.getAttribs(node);
  						i = attributes.length;
  						while (i--) {
  							name = attributes[i].nodeName;
  							if (name === "name" || name === 'data-mce-bookmark') {
  								return false;
  							}
  						}
  					}
  
  					// Keep comment nodes
  					if (type == 8) {
  						return false;
  					}
  
  					// Keep non whitespace text nodes
  					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
  						return false;
  					}
  				} while ((node = walker.next()));
  			}
  
  			return brCount <= 1;
  		},
  
  		/**
  		 * Creates a new DOM Range object. This will use the native DOM Range API if it's
  		 * available. If it's not, it will fall back to the custom maya implementation.
  		 *
  		 * @method createRng
  		 * @return {DOMRange} DOM Range object.
  		 * @example
  		 * var rng = maya.DOM.createRng();
  		 * alert(rng.startContainer + "," + rng.startOffset);
  		 */
  		createRng: function() {
  			var doc = this.doc;
  
  			return doc.createRange ? doc.createRange() : new Range(this);
  		},
  
  		/**
  		 * Returns the index of the specified node within its parent.
  		 *
  		 * @method nodeIndex
  		 * @param {Node} node Node to look for.
  		 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
  		 * @return {Number} Index of the specified node.
  		 */
  		nodeIndex: function(node, normalized) {
  			var idx = 0, lastNodeType, nodeType;
  
  			if (node) {
  				for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
  					nodeType = node.nodeType;
  
  					// Normalize text nodes
  					if (normalized && nodeType == 3) {
  						if (nodeType == lastNodeType || !node.nodeValue.length) {
  							continue;
  						}
  					}
  					idx++;
  					lastNodeType = nodeType;
  				}
  			}
  
  			return idx;
  		},
  
  		/**
  		 * Splits an element into two new elements and places the specified split
  		 * element or elements between the new ones. For example splitting the paragraph at the bold element in
  		 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
  		 *
  		 * @method split
  		 * @param {Element} parentElm Parent element to split.
  		 * @param {Element} splitElm Element to split at.
  		 * @param {Element} replacementElm Optional replacement element to replace the split element with.
  		 * @return {Element} Returns the split element or the replacement element if that is specified.
  		 */
  		split: function(parentElm, splitElm, replacementElm) {
  			var self = this, r = self.createRng(), bef, aft, pa;
  
  			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
  			// but we don't want that in our code since it serves no purpose for the end user
  			// For example splitting this html at the bold element:
  			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
  			// would produce:
  			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
  			// this function will then trim off empty edges and produce:
  			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
  			function trimNode(node) {
  				var i, children = node.childNodes, type = node.nodeType;
  
  				function surroundedBySpans(node) {
  					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
  					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
  					return previousIsSpan && nextIsSpan;
  				}
  
  				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
  					return;
  				}
  
  				for (i = children.length - 1; i >= 0; i--) {
  					trimNode(children[i]);
  				}
  
  				if (type != 9) {
  					// Keep non whitespace text nodes
  					if (type == 3 && node.nodeValue.length > 0) {
  						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
  						// Also keep text nodes with only spaces if surrounded by spans.
  						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
  						var trimmedLength = trim(node.nodeValue).length;
  						if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
  							return;
  						}
  					} else if (type == 1) {
  						// If the only child is a bookmark then move it up
  						children = node.childNodes;
  
  						// TODO fix this complex if
  						if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
  							children[0].getAttribute('data-mce-type') == 'bookmark') {
  							node.parentNode.insertBefore(children[0], node);
  						}
  
  						// Keep non empty elements or img, hr etc
  						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
  							return;
  						}
  					}
  
  					self.remove(node);
  				}
  
  				return node;
  			}
  
  			if (parentElm && splitElm) {
  				// Get before chunk
  				r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
  				r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
  				bef = r.extractContents();
  
  				// Get after chunk
  				r = self.createRng();
  				r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
  				r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
  				aft = r.extractContents();
  
  				// Insert before chunk
  				pa = parentElm.parentNode;
  				pa.insertBefore(trimNode(bef), parentElm);
  
  				// Insert middle chunk
  				if (replacementElm) {
  					pa.replaceChild(replacementElm, splitElm);
  				} else {
  					pa.insertBefore(splitElm, parentElm);
  				}
  
  				// Insert after chunk
  				pa.insertBefore(trimNode(aft), parentElm);
  				self.remove(parentElm);
  
  				return replacementElm || splitElm;
  			}
  		},
  
  		/**
  		 * Adds an event handler to the specified object.
  		 *
  		 * @method bind
  		 * @param {Element/Document/Window/Array} target Target element to bind events to.
  		 * handler to or an array of elements/ids/documents.
  		 * @param {String} name Name of event handler to add, for example: click.
  		 * @param {function} func Function to execute when the event occurs.
  		 * @param {Object} scope Optional scope to execute the function in.
  		 * @return {function} Function callback handler the same as the one passed in.
  		 */
  		bind: function(target, name, func, scope) {
  			var self = this;
  
  			if (Tools.isArray(target)) {
  				var i = target.length;
  
  				while (i--) {
  					target[i] = self.bind(target[i], name, func, scope);
  				}
  
  				return target;
  			}
  
  			// Collect all window/document events bound by editor instance
  			if (self.settings.collect && (target === self.doc || target === self.win)) {
  				self.boundEvents.push([target, name, func, scope]);
  			}
  
  			return self.events.bind(target, name, func, scope || self);
  		},
  
  		/**
  		 * Removes the specified event handler by name and function from an element or collection of elements.
  		 *
  		 * @method unbind
  		 * @param {Element/Document/Window/Array} target Target element to unbind events on.
  		 * @param {String} name Event handler name, for example: "click"
  		 * @param {function} func Function to remove.
  		 * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
  		 * were passed in.
  		 */
  		unbind: function(target, name, func) {
  			var self = this, i;
  
  			if (Tools.isArray(target)) {
  				i = target.length;
  
  				while (i--) {
  					target[i] = self.unbind(target[i], name, func);
  				}
  
  				return target;
  			}
  
  			// Remove any bound events matching the input
  			if (self.boundEvents && (target === self.doc || target === self.win)) {
  				i = self.boundEvents.length;
  
  				while (i--) {
  					var item = self.boundEvents[i];
  
  					if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
  						this.events.unbind(item[0], item[1], item[2]);
  					}
  				}
  			}
  
  			return this.events.unbind(target, name, func);
  		},
  
  		/**
  		 * Fires the specified event name with object on target.
  		 *
  		 * @method fire
  		 * @param {Node/Document/Window} target Target element or object to fire event on.
  		 * @param {String} name Name of the event to fire.
  		 * @param {Object} evt Event object to send.
  		 * @return {Event} Event object.
  		 */
  		fire: function(target, name, evt) {
  			return this.events.fire(target, name, evt);
  		},
  
  		// Returns the content editable state of a node
  		getContentEditable: function(node) {
  			var contentEditable;
  
  			// Check type
  			if (!node || node.nodeType != 1) {
  				return null;
  			}
  
  			// Check for fake content editable
  			contentEditable = node.getAttribute("data-mce-contenteditable");
  			if (contentEditable && contentEditable !== "inherit") {
  				return contentEditable;
  			}
  
  			// Check for real content editable
  			return node.contentEditable !== "inherit" ? node.contentEditable : null;
  		},
  
  		getContentEditableParent: function(node) {
  			var root = this.getRoot(), state = null;
  
  			for (; node && node !== root; node = node.parentNode) {
  				state = this.getContentEditable(node);
  
  				if (state !== null) {
  					break;
  				}
  			}
  
  			return state;
  		},
  
  		/**
  		 * Destroys all internal references to the DOM to solve IE leak issues.
  		 *
  		 * @method destroy
  		 */
  		destroy: function() {
  			var self = this;
  
  			// Unbind all events bound to window/document by editor instance
  			if (self.boundEvents) {
  				var i = self.boundEvents.length;
  
  				while (i--) {
  					var item = self.boundEvents[i];
  					this.events.unbind(item[0], item[1], item[2]);
  				}
  
  				self.boundEvents = null;
  			}
  
  			// Restore sizzle document to window.document
  			// Since the current document might be removed producing "Permission denied" on IE see #6325
  			if (Sizzle.setDocument) {
  				Sizzle.setDocument();
  			}
  
  			self.win = self.doc = self.root = self.events = self.frag = null;
  		},
  
  		isChildOf: function(node, parent) {
  			while (node) {
  				if (parent === node) {
  					return true;
  				}
  
  				node = node.parentNode;
  			}
  
  			return false;
  		},
  
  		// #ifdef debug
  
  		dumpRng: function(r) {
  			return (
  				'startContainer: ' + r.startContainer.nodeName +
  				', startOffset: ' + r.startOffset +
  				', endContainer: ' + r.endContainer.nodeName +
  				', endOffset: ' + r.endOffset
  			);
  		},
  
  		// #endif
  
  		_findSib: function(node, selector, name) {
  			var self = this, func = selector;
  
  			if (node) {
  				// If expression make a function of it using is
  				if (typeof func == 'string') {
  					func = function(node) {
  						return self.is(node, selector);
  					};
  				}
  
  				// Loop all siblings
  				for (node = node[name]; node; node = node[name]) {
  					if (func(node)) {
  						return node;
  					}
  				}
  			}
  
  			return null;
  		}
  	};
  
  	/**
  	 * Instance of DOMUtils for the current document.
  	 *
  	 * @static
  	 * @property DOM
  	 * @type maya.dom.DOMUtils
  	 * @example
  	 * // Example of how to add a class to some element by id
  	 * maya.DOM.addClass('someid', 'someclass');
  	 */
  	DOMUtils.DOM = new DOMUtils(document);
  
  	return DOMUtils;
  });
  
  // Included from: js/maya/classes/dom/ScriptLoader.js
  
  /**
   * ScriptLoader.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*globals console*/
  
  /**
   * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
   * when various items gets loaded. This class is useful to load external JavaScript files.
   *
   * @class maya.dom.ScriptLoader
   * @example
   * // Load a script from a specific URL using the global script loader
   * maya.ScriptLoader.load('somescript.js');
   *
   * // Load a script using a unique instance of the script loader
   * var scriptLoader = new maya.dom.ScriptLoader();
   *
   * scriptLoader.load('somescript.js');
   *
   * // Load multiple scripts
   * var scriptLoader = new maya.dom.ScriptLoader();
   *
   * scriptLoader.add('somescript1.js');
   * scriptLoader.add('somescript2.js');
   * scriptLoader.add('somescript3.js');
   *
   * scriptLoader.loadQueue(function() {
   *    alert('All scripts are now loaded.');
   * });
   */
  define("maya/dom/ScriptLoader", [
  	"maya/dom/DOMUtils",
  	"maya/util/Tools"
  ], function(DOMUtils, Tools) {
  	var DOM = DOMUtils.DOM;
  	var each = Tools.each, grep = Tools.grep;
  
  	function ScriptLoader() {
  		var QUEUED = 0,
  			LOADING = 1,
  			LOADED = 2,
  			states = {},
  			queue = [],
  			scriptLoadedCallbacks = {},
  			queueLoadedCallbacks = [],
  			loading = 0,
  			undef;
  
  		/**
  		 * Loads a specific script directly without adding it to the load queue.
  		 *
  		 * @method load
  		 * @param {String} url Absolute URL to script to add.
  		 * @param {function} callback Optional callback function to execute ones this script gets loaded.
  		 * @param {Object} scope Optional scope to execute callback in.
  		 */
  		function loadScript(url, callback) {
  			var dom = DOM, elm, id;
  
  			// Execute callback when script is loaded
  			function done() {
  				dom.remove(id);
  
  				if (elm) {
  					elm.onreadystatechange = elm.onload = elm = null;
  				}
  
  				callback();
  			}
  
  			function error() {
  				/*eslint no-console:0 */
  
  				// Report the error so it's easier for people to spot loading errors
  				if (typeof console !== "undefined" && console.log) {
  					console.log("Failed to load: " + url);
  				}
  
  				// We can't mark it as done if there is a load error since
  				// A) We don't want to produce 404 errors on the server and
  				// B) the onerror event won't fire on all browsers.
  				// done();
  			}
  
  			id = dom.uniqueId();
  
  			// Create new script element
  			elm = document.createElement('script');
  			elm.id = id;
  			elm.type = 'text/javascript';
  			elm.src = Tools._addCacheSuffix(url);
  
  			// Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
  			if ("onreadystatechange" in elm) {
  				elm.onreadystatechange = function() {
  					if (/loaded|complete/.test(elm.readyState)) {
  						done();
  					}
  				};
  			} else {
  				elm.onload = done;
  			}
  
  			// Add onerror event will get fired on some browsers but not all of them
  			elm.onerror = error;
  
  			// Add script to document
  			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
  		}
  
  		/**
  		 * Returns true/false if a script has been loaded or not.
  		 *
  		 * @method isDone
  		 * @param {String} url URL to check for.
  		 * @return {Boolean} true/false if the URL is loaded.
  		 */
  		this.isDone = function(url) {
  			return states[url] == LOADED;
  		};
  
  		/**
  		 * Marks a specific script to be loaded. This can be useful if a script got loaded outside
  		 * the script loader or to skip it from loading some script.
  		 *
  		 * @method markDone
  		 * @param {string} u Absolute URL to the script to mark as loaded.
  		 */
  		this.markDone = function(url) {
  			states[url] = LOADED;
  		};
  
  		/**
  		 * Adds a specific script to the load queue of the script loader.
  		 *
  		 * @method add
  		 * @param {String} url Absolute URL to script to add.
  		 * @param {function} callback Optional callback function to execute ones this script gets loaded.
  		 * @param {Object} scope Optional scope to execute callback in.
  		 */
  		this.add = this.load = function(url, callback, scope) {
  			var state = states[url];
  
  			// Add url to load queue
  			if (state == undef) {
  				queue.push(url);
  				states[url] = QUEUED;
  			}
  
  			if (callback) {
  				// Store away callback for later execution
  				if (!scriptLoadedCallbacks[url]) {
  					scriptLoadedCallbacks[url] = [];
  				}
  
  				scriptLoadedCallbacks[url].push({
  					func: callback,
  					scope: scope || this
  				});
  			}
  		};
  
  		/**
  		 * Starts the loading of the queue.
  		 *
  		 * @method loadQueue
  		 * @param {function} callback Optional callback to execute when all queued items are loaded.
  		 * @param {Object} scope Optional scope to execute the callback in.
  		 */
  		this.loadQueue = function(callback, scope) {
  			this.loadScripts(queue, callback, scope);
  		};
  
  		/**
  		 * Loads the specified queue of files and executes the callback ones they are loaded.
  		 * This method is generally not used outside this class but it might be useful in some scenarios.
  		 *
  		 * @method loadScripts
  		 * @param {Array} scripts Array of queue items to load.
  		 * @param {function} callback Optional callback to execute ones all items are loaded.
  		 * @param {Object} scope Optional scope to execute callback in.
  		 */
  		this.loadScripts = function(scripts, callback, scope) {
  			var loadScripts;
  
  			function execScriptLoadedCallbacks(url) {
  				// Execute URL callback functions
  				each(scriptLoadedCallbacks[url], function(callback) {
  					callback.func.call(callback.scope);
  				});
  
  				scriptLoadedCallbacks[url] = undef;
  			}
  
  			queueLoadedCallbacks.push({
  				func: callback,
  				scope: scope || this
  			});
  
  			loadScripts = function() {
  				var loadingScripts = grep(scripts);
  
  				// Current scripts has been handled
  				scripts.length = 0;
  
  				// Load scripts that needs to be loaded
  				each(loadingScripts, function(url) {
  					// Script is already loaded then execute script callbacks directly
  					if (states[url] == LOADED) {
  						execScriptLoadedCallbacks(url);
  						return;
  					}
  
  					// Is script not loading then start loading it
  					if (states[url] != LOADING) {
  						states[url] = LOADING;
  						loading++;
  
  						loadScript(url, function() {
  							states[url] = LOADED;
  							loading--;
  
  							execScriptLoadedCallbacks(url);
  
  							// Load more scripts if they where added by the recently loaded script
  							loadScripts();
  						});
  					}
  				});
  
  				// No scripts are currently loading then execute all pending queue loaded callbacks
  				if (!loading) {
  					each(queueLoadedCallbacks, function(callback) {
  						callback.func.call(callback.scope);
  					});
  
  					queueLoadedCallbacks.length = 0;
  				}
  			};
  
  			loadScripts();
  		};
  	}
  
  	ScriptLoader.ScriptLoader = new ScriptLoader();
  
  	return ScriptLoader;
  });
  
  // Included from: js/maya/classes/AddOnManager.js
  
  /**
   * AddOnManager.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles the loading of themes/plugins or other add-ons and their language packs.
   *
   * @class maya.AddOnManager
   */
  define("maya/AddOnManager", [
  	"maya/dom/ScriptLoader",
  	"maya/util/Tools"
  ], function(ScriptLoader, Tools) {
  	var each = Tools.each;
  
  	function AddOnManager() {
  		var self = this;
  
  		self.items = [];
  		self.urls = {};
  		self.lookup = {};
  	}
  
  	AddOnManager.prototype = {
  		/**
  		 * Returns the specified add on by the short name.
  		 *
  		 * @method get
  		 * @param {String} name Add-on to look for.
  		 * @return {maya.Theme/maya.Plugin} Theme or plugin add-on instance or undefined.
  		 */
  		get: function(name) {
  			if (this.lookup[name]) {
  				return this.lookup[name].instance;
  			} else {
  				return undefined;
  			}
  		},
  
  		dependencies: function(name) {
  			var result;
  
  			if (this.lookup[name]) {
  				result = this.lookup[name].dependencies;
  			}
  
  			return result || [];
  		},
  
  		/**
  		 * Loads a language pack for the specified add-on.
  		 *
  		 * @method requireLangPack
  		 * @param {String} name Short name of the add-on.
  		 * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
  		 */
  		requireLangPack: function(name, languages) {
  			var language = AddOnManager.language;
  
  			if (language && AddOnManager.languageLoad !== false) {
  				if (languages) {
  					languages = ',' + languages + ',';
  
  					// Load short form sv.js or long form sv_SE.js
  					if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
  						language = language.substr(0, 2);
  					} else if (languages.indexOf(',' + language + ',') == -1) {
  						return;
  					}
  				}
  
  				ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
  			}
  		},
  
  		/**
  		 * Adds a instance of the add-on by it's short name.
  		 *
  		 * @method add
  		 * @param {String} id Short name/id for the add-on.
  		 * @param {maya.Theme/maya.Plugin} addOn Theme or plugin to add.
  		 * @return {maya.Theme/maya.Plugin} The same theme or plugin instance that got passed in.
  		 * @example
  		 * // Create a simple plugin
  		 * maya.create('maya.plugins.TestPlugin', {
  		 *   TestPlugin: function(ed, url) {
  		 *   ed.on('click', function(e) {
  		 *      ed.windowManager.alert('Hello World!');
  		 *   });
  		 *   }
  		 * });
  		 *
  		 * // Register plugin using the add method
  		 * maya.PluginManager.add('test', maya.plugins.TestPlugin);
  		 *
  		 * // Initialize maya
  		 * maya.init({
  		 *  ...
  		 *  plugins: '-test' // Init the plugin but don't try to load it
  		 * });
  		 */
  		add: function(id, addOn, dependencies) {
  			this.items.push(addOn);
  			this.lookup[id] = {instance: addOn, dependencies: dependencies};
  
  			return addOn;
  		},
  
  		createUrl: function(baseUrl, dep) {
  			if (typeof dep === "object") {
  				return dep;
  			} else {
  				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
  			}
  		},
  
  		/**
  		 * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
  		 * This should be used in development mode.  A new compressor/javascript munger process will ensure that the
  		 * components are put together into the plugin.js file and compressed correctly.
  		 *
  		 * @method addComponents
  		 * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
  		 * @param {Array} scripts Array containing the names of the scripts to load.
  		 */
  		addComponents: function(pluginName, scripts) {
  			var pluginUrl = this.urls[pluginName];
  
  			each(scripts, function(script) {
  				ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
  			});
  		},
  
  		/**
  		 * Loads an add-on from a specific url.
  		 *
  		 * @method load
  		 * @param {String} name Short name of the add-on that gets loaded.
  		 * @param {String} addOnUrl URL to the add-on that will get loaded.
  		 * @param {function} callback Optional callback to execute ones the add-on is loaded.
  		 * @param {Object} scope Optional scope to execute the callback in.
  		 * @example
  		 * // Loads a plugin from an external URL
  		 * maya.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
  		 *
  		 * // Initialize maya
  		 * maya.init({
  		 *  ...
  		 *  plugins: '-myplugin' // Don't try to load it again
  		 * });
  		 */
  		load: function(name, addOnUrl, callback, scope) {
  			var self = this, url = addOnUrl;
  
  			function loadDependencies() {
  				var dependencies = self.dependencies(name);
  
  				each(dependencies, function(dep) {
  					var newUrl = self.createUrl(addOnUrl, dep);
  
  					self.load(newUrl.resource, newUrl, undefined, undefined);
  				});
  
  				if (callback) {
  					if (scope) {
  						callback.call(scope);
  					} else {
  						callback.call(ScriptLoader);
  					}
  				}
  			}
  
  			if (self.urls[name]) {
  				return;
  			}
  
  			if (typeof addOnUrl === "object") {
  				url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
  			}
  
  			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
  				url = AddOnManager.baseURL + '/' + url;
  			}
  
  			self.urls[name] = url.substring(0, url.lastIndexOf('/'));
  
  			if (self.lookup[name]) {
  				loadDependencies();
  			} else {
  				ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
  			}
  		}
  	};
  
  	AddOnManager.PluginManager = new AddOnManager();
  	AddOnManager.ThemeManager = new AddOnManager();
  
  	return AddOnManager;
  });
  
  /**
   * maya theme class.
   *
   * @class maya.Theme
   */
  
  /**
   * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
   *
   * @method renderUI
   * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
   * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
   */
  
  /**
   * Plugin base class, this is a pseudo class that describes how a plugin is to be created for maya. The methods below are all optional.
   *
   * @class maya.Plugin
   * @example
   * maya.PluginManager.add('example', function(editor, url) {
   *     // Add a button that opens a window
   *     editor.addButton('example', {
   *         text: 'My button',
   *         icon: false,
   *         onclick: function() {
   *             // Open window
   *             editor.windowManager.open({
   *                 title: 'Example plugin',
   *                 body: [
   *                     {type: 'textbox', name: 'title', label: 'Title'}
   *                 ],
   *                 onsubmit: function(e) {
   *                     // Insert content when the window form is submitted
   *                     editor.insertContent('Title: ' + e.data.title);
   *                 }
   *             });
   *         }
   *     });
   *
   *     // Adds a menu item to the tools menu
   *     editor.addMenuItem('example', {
   *         text: 'Example plugin',
   *         context: 'tools',
   *         onclick: function() {
   *             // Open window with a specific url
   *             editor.windowManager.open({
   *                 title: 'maya site',
   *                 url: 'http://www.maya.com',
   *                 width: 800,
   *                 height: 600,
   *                 buttons: [{
   *                     text: 'Close',
   *                     onclick: 'close'
   *                 }]
   *             });
   *         }
   *     });
   * });
   */
  
  // Included from: js/maya/classes/dom/RangeUtils.js
  
  /**
   * RangeUtils.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class contains a few utility methods for ranges.
   *
   * @class maya.dom.RangeUtils
   */
  define("maya/dom/RangeUtils", [
  	"maya/util/Tools",
  	"maya/dom/TreeWalker"
  ], function(Tools, TreeWalker) {
  	var each = Tools.each;
  
  	function getEndChild(container, index) {
  		var childNodes = container.childNodes;
  
  		index--;
  
  		if (index > childNodes.length - 1) {
  			index = childNodes.length - 1;
  		} else if (index < 0) {
  			index = 0;
  		}
  
  		return childNodes[index] || container;
  	}
  
  	function RangeUtils(dom) {
  		/**
  		 * Walks the specified range like object and executes the callback for each sibling collection it finds.
  		 *
  		 * @method walk
  		 * @param {Object} rng Range like object.
  		 * @param {function} callback Callback function to execute for each sibling collection.
  		 */
  		this.walk = function(rng, callback) {
  			var startContainer = rng.startContainer,
  				startOffset = rng.startOffset,
  				endContainer = rng.endContainer,
  				endOffset = rng.endOffset,
  				ancestor, startPoint,
  				endPoint, node, parent, siblings, nodes;
  
  			// Handle table cell selection the table plugin enables
  			// you to fake select table cells and perform formatting actions on them
  			nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
  			if (nodes.length > 0) {
  				each(nodes, function(node) {
  					callback([node]);
  				});
  
  				return;
  			}
  
  			/**
  			 * Excludes start/end text node if they are out side the range
  			 *
  			 * @private
  			 * @param {Array} nodes Nodes to exclude items from.
  			 * @return {Array} Array with nodes excluding the start/end container if needed.
  			 */
  			function exclude(nodes) {
  				var node;
  
  				// First node is excluded
  				node = nodes[0];
  				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
  					nodes.splice(0, 1);
  				}
  
  				// Last node is excluded
  				node = nodes[nodes.length - 1];
  				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
  					nodes.splice(nodes.length - 1, 1);
  				}
  
  				return nodes;
  			}
  
  			/**
  			 * Collects siblings
  			 *
  			 * @private
  			 * @param {Node} node Node to collect siblings from.
  			 * @param {String} name Name of the sibling to check for.
  			 * @return {Array} Array of collected siblings.
  			 */
  			function collectSiblings(node, name, end_node) {
  				var siblings = [];
  
  				for (; node && node != end_node; node = node[name]) {
  					siblings.push(node);
  				}
  
  				return siblings;
  			}
  
  			/**
  			 * Find an end point this is the node just before the common ancestor root.
  			 *
  			 * @private
  			 * @param {Node} node Node to start at.
  			 * @param {Node} root Root/ancestor element to stop just before.
  			 * @return {Node} Node just before the root element.
  			 */
  			function findEndPoint(node, root) {
  				do {
  					if (node.parentNode == root) {
  						return node;
  					}
  
  					node = node.parentNode;
  				} while (node);
  			}
  
  			function walkBoundary(start_node, end_node, next) {
  				var siblingName = next ? 'nextSibling' : 'previousSibling';
  
  				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
  					parent = node.parentNode;
  					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
  
  					if (siblings.length) {
  						if (!next) {
  							siblings.reverse();
  						}
  
  						callback(exclude(siblings));
  					}
  				}
  			}
  
  			// If index based start position then resolve it
  			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  				startContainer = startContainer.childNodes[startOffset];
  			}
  
  			// If index based end position then resolve it
  			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  				endContainer = getEndChild(endContainer, endOffset);
  			}
  
  			// Same container
  			if (startContainer == endContainer) {
  				return callback(exclude([startContainer]));
  			}
  
  			// Find common ancestor and end points
  			ancestor = dom.findCommonAncestor(startContainer, endContainer);
  
  			// Process left side
  			for (node = startContainer; node; node = node.parentNode) {
  				if (node === endContainer) {
  					return walkBoundary(startContainer, ancestor, true);
  				}
  
  				if (node === ancestor) {
  					break;
  				}
  			}
  
  			// Process right side
  			for (node = endContainer; node; node = node.parentNode) {
  				if (node === startContainer) {
  					return walkBoundary(endContainer, ancestor);
  				}
  
  				if (node === ancestor) {
  					break;
  				}
  			}
  
  			// Find start/end point
  			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
  			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
  
  			// Walk left leaf
  			walkBoundary(startContainer, startPoint, true);
  
  			// Walk the middle from start to end point
  			siblings = collectSiblings(
  				startPoint == startContainer ? startPoint : startPoint.nextSibling,
  				'nextSibling',
  				endPoint == endContainer ? endPoint.nextSibling : endPoint
  			);
  
  			if (siblings.length) {
  				callback(exclude(siblings));
  			}
  
  			// Walk right leaf
  			walkBoundary(endContainer, endPoint);
  		};
  
  		/**
  		 * Splits the specified range at it's start/end points.
  		 *
  		 * @private
  		 * @param {Range/RangeObject} rng Range to split.
  		 * @return {Object} Range position object.
  		 */
  		this.split = function(rng) {
  			var startContainer = rng.startContainer,
  				startOffset = rng.startOffset,
  				endContainer = rng.endContainer,
  				endOffset = rng.endOffset;
  
  			function splitText(node, offset) {
  				return node.splitText(offset);
  			}
  
  			// Handle single text node
  			if (startContainer == endContainer && startContainer.nodeType == 3) {
  				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  					endContainer = splitText(startContainer, startOffset);
  					startContainer = endContainer.previousSibling;
  
  					if (endOffset > startOffset) {
  						endOffset = endOffset - startOffset;
  						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
  						endOffset = endContainer.nodeValue.length;
  						startOffset = 0;
  					} else {
  						endOffset = 0;
  					}
  				}
  			} else {
  				// Split startContainer text node if needed
  				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  					startContainer = splitText(startContainer, startOffset);
  					startOffset = 0;
  				}
  
  				// Split endContainer text node if needed
  				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
  					endContainer = splitText(endContainer, endOffset).previousSibling;
  					endOffset = endContainer.nodeValue.length;
  				}
  			}
  
  			return {
  				startContainer: startContainer,
  				startOffset: startOffset,
  				endContainer: endContainer,
  				endOffset: endOffset
  			};
  		};
  
  		/**
  		 * Normalizes the specified range by finding the closest best suitable caret location.
  		 *
  		 * @private
  		 * @param {Range} rng Range to normalize.
  		 * @return {Boolean} True/false if the specified range was normalized or not.
  		 */
  		this.normalize = function(rng) {
  			var normalized, collapsed;
  
  			function normalizeEndPoint(start) {
  				var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
  				var directionLeft, isAfterNode;
  
  				function hasBrBeforeAfter(node, left) {
  					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
  
  					while ((node = walker[left ? 'prev' : 'next']())) {
  						if (node.nodeName === "BR") {
  							return true;
  						}
  					}
  				}
  
  				function isPrevNode(node, name) {
  					return node.previousSibling && node.previousSibling.nodeName == name;
  				}
  
  				// Walks the dom left/right to find a suitable text node to move the endpoint into
  				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
  				function findTextNodeRelative(left, startNode) {
  					var walker, lastInlineElement, parentBlockContainer;
  
  					startNode = startNode || container;
  					parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
  
  					// Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
  					// This: <p><br>|</p> becomes <p>|<br></p>
  					if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
  						container = startNode.parentNode;
  						offset = dom.nodeIndex(startNode);
  						normalized = true;
  						return;
  					}
  
  					// Walk left until we hit a text node we can move to or a block/br/img
  					walker = new TreeWalker(startNode, parentBlockContainer);
  					while ((node = walker[left ? 'prev' : 'next']())) {
  						// Break if we hit a non content editable node
  						if (dom.getContentEditableParent(node) === "false") {
  							return;
  						}
  
  						// Found text node that has a length
  						if (node.nodeType === 3 && node.nodeValue.length > 0) {
  							container = node;
  							offset = left ? node.nodeValue.length : 0;
  							normalized = true;
  							return;
  						}
  
  						// Break if we find a block or a BR/IMG/INPUT etc
  						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  							return;
  						}
  
  						lastInlineElement = node;
  					}
  
  					// Only fetch the last inline element when in caret mode for now
  					if (collapsed && lastInlineElement) {
  						container = lastInlineElement;
  						normalized = true;
  						offset = 0;
  					}
  				}
  
  				container = rng[(start ? 'start' : 'end') + 'Container'];
  				offset = rng[(start ? 'start' : 'end') + 'Offset'];
  				isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
  				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
  				directionLeft = start;
  
  				if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
  					directionLeft = false;
  				}
  
  				// If the container is a document move it to the body element
  				if (container.nodeType === 9) {
  					container = dom.getRoot();
  					offset = 0;
  				}
  
  				// If the container is body try move it into the closest text node or position
  				if (container === body) {
  					// If start is before/after a image, table etc
  					if (directionLeft) {
  						node = container.childNodes[offset > 0 ? offset - 1 : 0];
  						if (node) {
  							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
  								return;
  							}
  						}
  					}
  
  					// Resolve the index
  					if (container.hasChildNodes()) {
  						offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
  						container = container.childNodes[offset];
  						offset = 0;
  
  						// Don't walk into elements that doesn't have any child nodes like a IMG
  						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
  							// Walk the DOM to find a text node to place the caret at or a BR
  							node = container;
  							walker = new TreeWalker(container, body);
  
  							do {
  								// Found a text node use that position
  								if (node.nodeType === 3 && node.nodeValue.length > 0) {
  									offset = directionLeft ? 0 : node.nodeValue.length;
  									container = node;
  									normalized = true;
  									break;
  								}
  
  								// Found a BR/IMG element that we can place the caret before
  								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  									offset = dom.nodeIndex(node);
  									container = node.parentNode;
  
  									// Put caret after image when moving the end point
  									if (node.nodeName == "IMG" && !directionLeft) {
  										offset++;
  									}
  
  									normalized = true;
  									break;
  								}
  							} while ((node = (directionLeft ? walker.next() : walker.prev())));
  						}
  					}
  				}
  
  				// Lean the caret to the left if possible
  				if (collapsed) {
  					// So this: <b>x</b><i>|x</i>
  					// Becomes: <b>x|</b><i>x</i>
  					// Seems that only gecko has issues with this
  					if (container.nodeType === 3 && offset === 0) {
  						findTextNodeRelative(true);
  					}
  
  					// Lean left into empty inline elements when the caret is before a BR
  					// So this: <i><b></b><i>|<br></i>
  					// Becomes: <i><b>|</b><i><br></i>
  					// Seems that only gecko has issues with this.
  					// Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
  					if (container.nodeType === 1) {
  						node = container.childNodes[offset];
  
  						// Offset is after the containers last child
  						// then use the previous child for normalization
  						if (!node) {
  							node = container.childNodes[offset - 1];
  						}
  
  						if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
  							!hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
  							findTextNodeRelative(true, node);
  						}
  					}
  				}
  
  				// Lean the start of the selection right if possible
  				// So this: x[<b>x]</b>
  				// Becomes: x<b>[x]</b>
  				if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
  					findTextNodeRelative(false);
  				}
  
  				// Set endpoint if it was normalized
  				if (normalized) {
  					rng['set' + (start ? 'Start' : 'End')](container, offset);
  				}
  			}
  
  			collapsed = rng.collapsed;
  
  			normalizeEndPoint(true);
  
  			if (!collapsed) {
  				normalizeEndPoint();
  			}
  
  			// If it was collapsed then make sure it still is
  			if (normalized && collapsed) {
  				rng.collapse(true);
  			}
  
  			return normalized;
  		};
  	}
  
  	/**
  	 * Compares two ranges and checks if they are equal.
  	 *
  	 * @static
  	 * @method compareRanges
  	 * @param {DOMRange} rng1 First range to compare.
  	 * @param {DOMRange} rng2 First range to compare.
  	 * @return {Boolean} true/false if the ranges are equal.
  	 */
  	RangeUtils.compareRanges = function(rng1, rng2) {
  		if (rng1 && rng2) {
  			// Compare native IE ranges
  			if (rng1.item || rng1.duplicate) {
  				// Both are control ranges and the selected element matches
  				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
  					return true;
  				}
  
  				// Both are text ranges and the range matches
  				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
  					return true;
  				}
  			} else {
  				// Compare w3c ranges
  				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
  			}
  		}
  
  		return false;
  	};
  
  	/**
  	 * Gets the caret range for the given x/y location.
  	 *
  	 * @static
  	 * @method getCaretRangeFromPoint
  	 * @param {Number} x X coordinate for range
  	 * @param {Number} y Y coordinate for range
  	 * @param {Document} doc Document that x/y are relative to
  	 * @returns {Range} caret range
  	 */
  	RangeUtils.getCaretRangeFromPoint = function(x, y, doc) {
  		var rng, point;
  
  		if (doc.caretPositionFromPoint) {
  			point = doc.caretPositionFromPoint(x, y);
  			rng = doc.createRange();
  			rng.setStart(point.offsetNode, point.offset);
  			rng.collapse(true);
  		} else if (doc.caretRangeFromPoint) {
  			rng = doc.caretRangeFromPoint(x, y);
  		} else if (doc.body.createTextRange) {
  			rng = doc.body.createTextRange();
  
  			try {
  				rng.moveToPoint(x, y);
  				rng.collapse(true);
  			} catch (ex) {
  				// Append to top or bottom depending on drop location
  				rng.collapse(y < doc.body.clientHeight);
  			}
  		}
  
  		return rng;
  	};
  
  	RangeUtils.getNode = function(container, offset) {
  		if (container.nodeType == 1 && container.hasChildNodes()) {
  			if (offset >= container.childNodes.length) {
  				offset = container.childNodes.length - 1;
  			}
  
  			container = container.childNodes[offset];
  		}
  
  		return container;
  	};
  
  	return RangeUtils;
  });
  
  // Included from: js/maya/classes/NodeChange.js
  
  /**
   * NodeChange.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles the nodechange event dispatching both manual and though selection change events.
   *
   * @class maya.NodeChange
   * @private
   */
  define("maya/NodeChange", [
  	"maya/dom/RangeUtils",
  	"maya/Env"
  ], function(RangeUtils, Env) {
  	return function(editor) {
  		var lastRng, lastPath = [];
  
  		/**
  		 * Returns true/false if the current element path has been changed or not.
  		 *
  		 * @private
  		 * @return {Boolean} True if the element path is the same false if it's not.
  		 */
  		function isSameElementPath(startElm) {
  			var i, currentPath;
  
  			currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
  			if (currentPath.length === lastPath.length) {
  				for (i = currentPath.length; i >= 0; i--) {
  					if (currentPath[i] !== lastPath[i]) {
  						break;
  					}
  				}
  
  				if (i === -1) {
  					lastPath = currentPath;
  					return true;
  				}
  			}
  
  			lastPath = currentPath;
  
  			return false;
  		}
  
  		// Gecko doesn't support the "selectionchange" event
  		if (!('onselectionchange' in editor.getDoc())) {
  			editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
  				var nativeRng, fakeRng;
  
  				// Since DOM Ranges mutate on modification
  				// of the DOM we need to clone it's contents
  				nativeRng = editor.selection.getRng();
  				fakeRng = {
  					startContainer: nativeRng.startContainer,
  					startOffset: nativeRng.startOffset,
  					endContainer: nativeRng.endContainer,
  					endOffset: nativeRng.endOffset
  				};
  
  				// Always treat nodechange as a selectionchange since applying
  				// formatting to the current range wouldn't update the range but it's parent
  				if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
  					editor.fire('SelectionChange');
  				}
  
  				lastRng = fakeRng;
  			});
  		}
  
  		// IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
  		// When the contextmenu event fires the selection is located at the right location
  		editor.on('contextmenu', function() {
  			editor.fire('SelectionChange');
  		});
  
  		// Selection change is delayed ~200ms on IE when you click inside the current range
  		editor.on('SelectionChange', function() {
  			var startElm = editor.selection.getStart(true);
  
  			// IE 8 will fire a selectionchange event with an incorrect selection
  			// when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event
  			if (!Env.range && editor.selection.isCollapsed()) {
  				return;
  			}
  
  			if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
  				editor.nodeChanged({selectionChange: true});
  			}
  		});
  
  		// Fire an extra nodeChange on mouseup for compatibility reasons
  		editor.on('MouseUp', function(e) {
  			if (!e.isDefaultPrevented()) {
  				// Delay nodeChanged call for WebKit edge case issue where the range
  				// isn't updated until after you click outside a selected image
  				if (editor.selection.getNode().nodeName == 'IMG') {
  					setTimeout(function() {
  						editor.nodeChanged();
  					}, 0);
  				} else {
  					editor.nodeChanged();
  				}
  			}
  		});
  
  		/**
  		 * Distpaches out a onNodeChange event to all observers. This method should be called when you
  		 * need to update the UI states or element path etc.
  		 *
  		 * @method nodeChanged
  		 * @param {Object} args Optional args to pass to NodeChange event handlers.
  		 */
  		this.nodeChanged = function(args) {
  			var selection = editor.selection, node, parents, root;
  
  			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
  			if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.settings.readonly) {
  				// Get start node
  				root = editor.getBody();
  				node = selection.getStart() || root;
  				node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node;
  
  				// Edge case for <p>|<img></p>
  				if (node.nodeName == 'IMG' && selection.isCollapsed()) {
  					node = node.parentNode;
  				}
  
  				// Get parents and add them to object
  				parents = [];
  				editor.dom.getParent(node, function(node) {
  					if (node === root) {
  						return true;
  					}
  
  					parents.push(node);
  				});
  
  				args = args || {};
  				args.element = node;
  				args.parents = parents;
  
  				editor.fire('NodeChange', args);
  			}
  		};
  	};
  });
  
  // Included from: js/maya/classes/html/Node.js
  
  /**
   * Node.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
   *
   * @example
   * var node = new maya.html.Node('strong', 1);
   * someRoot.append(node);
   *
   * @class maya.html.Node
   * @version 3.4
   */
  define("maya/html/Node", [], function() {
  	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
  		'#text': 3,
  		'#comment': 8,
  		'#cdata': 4,
  		'#pi': 7,
  		'#doctype': 10,
  		'#document-fragment': 11
  	};
  
  	// Walks the tree left/right
  	function walk(node, root_node, prev) {
  		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
  
  		// Walk into nodes if it has a start
  		if (node[startName]) {
  			return node[startName];
  		}
  
  		// Return the sibling if it has one
  		if (node !== root_node) {
  			sibling = node[siblingName];
  
  			if (sibling) {
  				return sibling;
  			}
  
  			// Walk up the parents to look for siblings
  			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
  				sibling = parent[siblingName];
  
  				if (sibling) {
  					return sibling;
  				}
  			}
  		}
  	}
  
  	/**
  	 * Constructs a new Node instance.
  	 *
  	 * @constructor
  	 * @method Node
  	 * @param {String} name Name of the node type.
  	 * @param {Number} type Numeric type representing the node.
  	 */
  	function Node(name, type) {
  		this.name = name;
  		this.type = type;
  
  		if (type === 1) {
  			this.attributes = [];
  			this.attributes.map = {};
  		}
  	}
  
  	Node.prototype = {
  		/**
  		 * Replaces the current node with the specified one.
  		 *
  		 * @example
  		 * someNode.replace(someNewNode);
  		 *
  		 * @method replace
  		 * @param {maya.html.Node} node Node to replace the current node with.
  		 * @return {maya.html.Node} The old node that got replaced.
  		 */
  		replace: function(node) {
  			var self = this;
  
  			if (node.parent) {
  				node.remove();
  			}
  
  			self.insert(node, self);
  			self.remove();
  
  			return self;
  		},
  
  		/**
  		 * Gets/sets or removes an attribute by name.
  		 *
  		 * @example
  		 * someNode.attr("name", "value"); // Sets an attribute
  		 * console.log(someNode.attr("name")); // Gets an attribute
  		 * someNode.attr("name", null); // Removes an attribute
  		 *
  		 * @method attr
  		 * @param {String} name Attribute name to set or get.
  		 * @param {String} value Optional value to set.
  		 * @return {String/maya.html.Node} String or undefined on a get operation or the current node on a set operation.
  		 */
  		attr: function(name, value) {
  			var self = this, attrs, i, undef;
  
  			if (typeof name !== "string") {
  				for (i in name) {
  					self.attr(i, name[i]);
  				}
  
  				return self;
  			}
  
  			if ((attrs = self.attributes)) {
  				if (value !== undef) {
  					// Remove attribute
  					if (value === null) {
  						if (name in attrs.map) {
  							delete attrs.map[name];
  
  							i = attrs.length;
  							while (i--) {
  								if (attrs[i].name === name) {
  									attrs = attrs.splice(i, 1);
  									return self;
  								}
  							}
  						}
  
  						return self;
  					}
  
  					// Set attribute
  					if (name in attrs.map) {
  						// Set attribute
  						i = attrs.length;
  						while (i--) {
  							if (attrs[i].name === name) {
  								attrs[i].value = value;
  								break;
  							}
  						}
  					} else {
  						attrs.push({name: name, value: value});
  					}
  
  					attrs.map[name] = value;
  
  					return self;
  				} else {
  					return attrs.map[name];
  				}
  			}
  		},
  
  		/**
  		 * Does a shallow clones the node into a new node. It will also exclude id attributes since
  		 * there should only be one id per document.
  		 *
  		 * @example
  		 * var clonedNode = node.clone();
  		 *
  		 * @method clone
  		 * @return {maya.html.Node} New copy of the original node.
  		 */
  		clone: function() {
  			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
  
  			// Clone element attributes
  			if ((selfAttrs = self.attributes)) {
  				cloneAttrs = [];
  				cloneAttrs.map = {};
  
  				for (i = 0, l = selfAttrs.length; i < l; i++) {
  					selfAttr = selfAttrs[i];
  
  					// Clone everything except id
  					if (selfAttr.name !== 'id') {
  						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
  						cloneAttrs.map[selfAttr.name] = selfAttr.value;
  					}
  				}
  
  				clone.attributes = cloneAttrs;
  			}
  
  			clone.value = self.value;
  			clone.shortEnded = self.shortEnded;
  
  			return clone;
  		},
  
  		/**
  		 * Wraps the node in in another node.
  		 *
  		 * @example
  		 * node.wrap(wrapperNode);
  		 *
  		 * @method wrap
  		 */
  		wrap: function(wrapper) {
  			var self = this;
  
  			self.parent.insert(wrapper, self);
  			wrapper.append(self);
  
  			return self;
  		},
  
  		/**
  		 * Unwraps the node in other words it removes the node but keeps the children.
  		 *
  		 * @example
  		 * node.unwrap();
  		 *
  		 * @method unwrap
  		 */
  		unwrap: function() {
  			var self = this, node, next;
  
  			for (node = self.firstChild; node;) {
  				next = node.next;
  				self.insert(node, self, true);
  				node = next;
  			}
  
  			self.remove();
  		},
  
  		/**
  		 * Removes the node from it's parent.
  		 *
  		 * @example
  		 * node.remove();
  		 *
  		 * @method remove
  		 * @return {maya.html.Node} Current node that got removed.
  		 */
  		remove: function() {
  			var self = this, parent = self.parent, next = self.next, prev = self.prev;
  
  			if (parent) {
  				if (parent.firstChild === self) {
  					parent.firstChild = next;
  
  					if (next) {
  						next.prev = null;
  					}
  				} else {
  					prev.next = next;
  				}
  
  				if (parent.lastChild === self) {
  					parent.lastChild = prev;
  
  					if (prev) {
  						prev.next = null;
  					}
  				} else {
  					next.prev = prev;
  				}
  
  				self.parent = self.next = self.prev = null;
  			}
  
  			return self;
  		},
  
  		/**
  		 * Appends a new node as a child of the current node.
  		 *
  		 * @example
  		 * node.append(someNode);
  		 *
  		 * @method append
  		 * @param {maya.html.Node} node Node to append as a child of the current one.
  		 * @return {maya.html.Node} The node that got appended.
  		 */
  		append: function(node) {
  			var self = this, last;
  
  			if (node.parent) {
  				node.remove();
  			}
  
  			last = self.lastChild;
  			if (last) {
  				last.next = node;
  				node.prev = last;
  				self.lastChild = node;
  			} else {
  				self.lastChild = self.firstChild = node;
  			}
  
  			node.parent = self;
  
  			return node;
  		},
  
  		/**
  		 * Inserts a node at a specific position as a child of the current node.
  		 *
  		 * @example
  		 * parentNode.insert(newChildNode, oldChildNode);
  		 *
  		 * @method insert
  		 * @param {maya.html.Node} node Node to insert as a child of the current node.
  		 * @param {maya.html.Node} ref_node Reference node to set node before/after.
  		 * @param {Boolean} before Optional state to insert the node before the reference node.
  		 * @return {maya.html.Node} The node that got inserted.
  		 */
  		insert: function(node, ref_node, before) {
  			var parent;
  
  			if (node.parent) {
  				node.remove();
  			}
  
  			parent = ref_node.parent || this;
  
  			if (before) {
  				if (ref_node === parent.firstChild) {
  					parent.firstChild = node;
  				} else {
  					ref_node.prev.next = node;
  				}
  
  				node.prev = ref_node.prev;
  				node.next = ref_node;
  				ref_node.prev = node;
  			} else {
  				if (ref_node === parent.lastChild) {
  					parent.lastChild = node;
  				} else {
  					ref_node.next.prev = node;
  				}
  
  				node.next = ref_node.next;
  				node.prev = ref_node;
  				ref_node.next = node;
  			}
  
  			node.parent = parent;
  
  			return node;
  		},
  
  		/**
  		 * Get all children by name.
  		 *
  		 * @method getAll
  		 * @param {String} name Name of the child nodes to collect.
  		 * @return {Array} Array with child nodes matchin the specified name.
  		 */
  		getAll: function(name) {
  			var self = this, node, collection = [];
  
  			for (node = self.firstChild; node; node = walk(node, self)) {
  				if (node.name === name) {
  					collection.push(node);
  				}
  			}
  
  			return collection;
  		},
  
  		/**
  		 * Removes all children of the current node.
  		 *
  		 * @method empty
  		 * @return {maya.html.Node} The current node that got cleared.
  		 */
  		empty: function() {
  			var self = this, nodes, i, node;
  
  			// Remove all children
  			if (self.firstChild) {
  				nodes = [];
  
  				// Collect the children
  				for (node = self.firstChild; node; node = walk(node, self)) {
  					nodes.push(node);
  				}
  
  				// Remove the children
  				i = nodes.length;
  				while (i--) {
  					node = nodes[i];
  					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
  				}
  			}
  
  			self.firstChild = self.lastChild = null;
  
  			return self;
  		},
  
  		/**
  		 * Returns true/false if the node is to be considered empty or not.
  		 *
  		 * @example
  		 * node.isEmpty({img: true});
  		 * @method isEmpty
  		 * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
  		 * @return {Boolean} true/false if the node is empty or not.
  		 */
  		isEmpty: function(elements) {
  			var self = this, node = self.firstChild, i, name;
  
  			if (node) {
  				do {
  					if (node.type === 1) {
  						// Ignore bogus elements
  						if (node.attributes.map['data-mce-bogus']) {
  							continue;
  						}
  
  						// Keep empty elements like <img />
  						if (elements[node.name]) {
  							return false;
  						}
  
  						// Keep bookmark nodes and name attribute like <a name="1"></a>
  						i = node.attributes.length;
  						while (i--) {
  							name = node.attributes[i].name;
  							if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
  								return false;
  							}
  						}
  					}
  
  					// Keep comments
  					if (node.type === 8) {
  						return false;
  					}
  
  					// Keep non whitespace text nodes
  					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
  						return false;
  					}
  				} while ((node = walk(node, self)));
  			}
  
  			return true;
  		},
  
  		/**
  		 * Walks to the next or previous node and returns that node or null if it wasn't found.
  		 *
  		 * @method walk
  		 * @param {Boolean} prev Optional previous node state defaults to false.
  		 * @return {maya.html.Node} Node that is next to or previous of the current node.
  		 */
  		walk: function(prev) {
  			return walk(this, null, prev);
  		}
  	};
  
  	/**
  	 * Creates a node of a specific type.
  	 *
  	 * @static
  	 * @method create
  	 * @param {String} name Name of the node type to create for example "b" or "#text".
  	 * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
  	 */
  	Node.create = function(name, attrs) {
  		var node, attrName;
  
  		// Create node
  		node = new Node(name, typeLookup[name] || 1);
  
  		// Add attributes if needed
  		if (attrs) {
  			for (attrName in attrs) {
  				node.attr(attrName, attrs[attrName]);
  			}
  		}
  
  		return node;
  	};
  
  	return Node;
  });
  
  // Included from: js/maya/classes/html/Schema.js
  
  /**
   * Schema.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Schema validator class.
   *
   * @class maya.html.Schema
   * @example
   *  if (maya.activeEditor.schema.isValidChild('p', 'span'))
   *    alert('span is valid child of p.');
   *
   *  if (maya.activeEditor.schema.getElementRule('p'))
   *    alert('P is a valid element.');
   *
   * @class maya.html.Schema
   * @version 3.4
   */
  define("maya/html/Schema", [
  	"maya/util/Tools"
  ], function(Tools) {
  	var mapCache = {}, dummyObj = {};
  	var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
  
  	function split(items, delim) {
  		return items ? items.split(delim || ' ') : [];
  	}
  
  	/**
  	 * Builds a schema lookup table
  	 *
  	 * @private
  	 * @param {String} type html4, html5 or html5-strict schema type.
  	 * @return {Object} Schema lookup table.
  	 */
  	function compileSchema(type) {
  		var schema = {}, globalAttributes, blockContent;
  		var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
  
  		function add(name, attributes, children) {
  			var ni, i, attributesOrder, args = arguments;
  
  			function arrayToMap(array, obj) {
  				var map = {}, i, l;
  
  				for (i = 0, l = array.length; i < l; i++) {
  					map[array[i]] = obj || {};
  				}
  
  				return map;
  			}
  
  			children = children || [];
  			attributes = attributes || "";
  
  			if (typeof children === "string") {
  				children = split(children);
  			}
  
  			// Split string children
  			for (i = 3; i < args.length; i++) {
  				if (typeof args[i] === "string") {
  					args[i] = split(args[i]);
  				}
  
  				children.push.apply(children, args[i]);
  			}
  
  			name = split(name);
  			ni = name.length;
  			while (ni--) {
  				attributesOrder = [].concat(globalAttributes, split(attributes));
  				schema[name[ni]] = {
  					attributes: arrayToMap(attributesOrder),
  					attributesOrder: attributesOrder,
  					children: arrayToMap(children, dummyObj)
  				};
  			}
  		}
  
  		function addAttrs(name, attributes) {
  			var ni, schemaItem, i, l;
  
  			name = split(name);
  			ni = name.length;
  			attributes = split(attributes);
  			while (ni--) {
  				schemaItem = schema[name[ni]];
  				for (i = 0, l = attributes.length; i < l; i++) {
  					schemaItem.attributes[attributes[i]] = {};
  					schemaItem.attributesOrder.push(attributes[i]);
  				}
  			}
  		}
  
  		// Use cached schema
  		if (mapCache[type]) {
  			return mapCache[type];
  		}
  
  		// Attributes present on all elements
  		globalAttributes = split("id accesskey class dir lang style tabindex title");
  
  		// Event attributes can be opt-in/opt-out
  		/*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
  				"ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
  				"onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
  				"onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
  				"onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
  				"onwaiting"
  		);*/
  
  		// Block content elements
  		blockContent = split(
  			"address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
  		);
  
  		// Phrasing content elements from the HTML5 spec (inline)
  		phrasingContent = split(
  			"a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
  			"label map noscript object q s samp script select small span strong sub sup " +
  			"textarea u var #text #comment"
  		);
  
  		// Add HTML5 items to globalAttributes, blockContent, phrasingContent
  		if (type != "html4") {
  			globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
  				"hidden spellcheck translate"));
  			blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
  			phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
  				"video ruby bdi keygen"));
  		}
  
  		// Add HTML4 elements unless it's html5-strict
  		if (type != "html5-strict") {
  			globalAttributes.push("xml:lang");
  
  			html4PhrasingContent = split("acronym applet basefont big font strike tt");
  			phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
  
  			each(html4PhrasingContent, function(name) {
  				add(name, "", phrasingContent);
  			});
  
  			html4BlockContent = split("center dir isindex noframes");
  			blockContent.push.apply(blockContent, html4BlockContent);
  
  			// Flow content elements from the HTML5 spec (block+inline)
  			flowContent = [].concat(blockContent, phrasingContent);
  
  			each(html4BlockContent, function(name) {
  				add(name, "", flowContent);
  			});
  		}
  
  		// Flow content elements from the HTML5 spec (block+inline)
  		flowContent = flowContent || [].concat(blockContent, phrasingContent);
  
  		// HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
  		// Schema items <element name>, <specific attributes>, <children ..>
  		add("html", "manifest", "head body");
  		add("head", "", "base command link meta noscript script style title");
  		add("title hr noscript br");
  		add("base", "href target");
  		add("link", "href rel media hreflang type sizes hreflang");
  		add("meta", "name http-equiv content charset");
  		add("style", "media type scoped");
  		add("script", "src async defer type charset");
  		add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
  				"onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
  				"onpopstate onresize onscroll onstorage onunload", flowContent);
  		add("address dt dd div caption", "", flowContent);
  		add("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn", "", phrasingContent);
  		add("blockquote", "cite", flowContent);
  		add("ol", "reversed start type", "li");
  		add("ul", "", "li");
  		add("li", "value", flowContent);
  		add("dl", "", "dt dd");
  		add("a", "href target rel media hreflang type", phrasingContent);
  		add("q", "cite", phrasingContent);
  		add("ins del", "cite datetime", flowContent);
  		add("img", "src sizes srcset alt usemap ismap width height");
  		add("iframe", "src name width height", flowContent);
  		add("embed", "src type width height");
  		add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
  		add("param", "name value");
  		add("map", "name", flowContent, "area");
  		add("area", "alt coords shape href target rel media hreflang type");
  		add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
  		add("colgroup", "span", "col");
  		add("col", "span");
  		add("tbody thead tfoot", "", "tr");
  		add("tr", "", "td th");
  		add("td", "colspan rowspan headers", flowContent);
  		add("th", "colspan rowspan headers scope abbr", flowContent);
  		add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
  		add("fieldset", "disabled form name", flowContent, "legend");
  		add("label", "form for", phrasingContent);
  		add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
  				"formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
  		);
  		add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
  			type == "html4" ? flowContent : phrasingContent);
  		add("select", "disabled form multiple name required size", "option optgroup");
  		add("optgroup", "disabled label", "option");
  		add("option", "disabled label selected value");
  		add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
  		add("menu", "type label", flowContent, "li");
  		add("noscript", "", flowContent);
  
  		// Extend with HTML5 elements
  		if (type != "html4") {
  			add("wbr");
  			add("ruby", "", phrasingContent, "rt rp");
  			add("figcaption", "", flowContent);
  			add("mark rt rp summary bdi", "", phrasingContent);
  			add("canvas", "width height", flowContent);
  			add("video", "src crossorigin poster preload autoplay mediagroup loop " +
  				"muted controls width height buffered", flowContent, "track source");
  			add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
  			add("picture", "", "img source");
  			add("source", "src srcset type media sizes");
  			add("track", "kind src srclang label default");
  			add("datalist", "", phrasingContent, "option");
  			add("article section nav aside header footer", "", flowContent);
  			add("hgroup", "", "h1 h2 h3 h4 h5 h6");
  			add("figure", "", flowContent, "figcaption");
  			add("time", "datetime", phrasingContent);
  			add("dialog", "open", flowContent);
  			add("command", "type label icon disabled checked radiogroup command");
  			add("output", "for form name", phrasingContent);
  			add("progress", "value max", phrasingContent);
  			add("meter", "value min max low high optimum", phrasingContent);
  			add("details", "open", flowContent, "summary");
  			add("keygen", "autofocus challenge disabled form keytype name");
  		}
  
  		// Extend with HTML4 attributes unless it's html5-strict
  		if (type != "html5-strict") {
  			addAttrs("script", "language xml:space");
  			addAttrs("style", "xml:space");
  			addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
  			addAttrs("embed", "align name hspace vspace");
  			addAttrs("param", "valuetype type");
  			addAttrs("a", "charset name rev shape coords");
  			addAttrs("br", "clear");
  			addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
  			addAttrs("img", "name longdesc align border hspace vspace");
  			addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
  			addAttrs("font basefont", "size color face");
  			addAttrs("input", "usemap align");
  			addAttrs("select", "onchange");
  			addAttrs("textarea");
  			addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
  			addAttrs("ul", "type compact");
  			addAttrs("li", "type");
  			addAttrs("ol dl menu dir", "compact");
  			addAttrs("pre", "width xml:space");
  			addAttrs("hr", "align noshade size width");
  			addAttrs("isindex", "prompt");
  			addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
  			addAttrs("col", "width align char charoff valign");
  			addAttrs("colgroup", "width align char charoff valign");
  			addAttrs("thead", "align char charoff valign");
  			addAttrs("tr", "align char charoff valign bgcolor");
  			addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
  			addAttrs("form", "accept");
  			addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
  			addAttrs("tfoot", "align char charoff valign");
  			addAttrs("tbody", "align char charoff valign");
  			addAttrs("area", "nohref");
  			addAttrs("body", "background bgcolor text link vlink alink");
  		}
  
  		// Extend with HTML5 attributes unless it's html4
  		if (type != "html4") {
  			addAttrs("input button select textarea", "autofocus");
  			addAttrs("input textarea", "placeholder");
  			addAttrs("a", "download");
  			addAttrs("link script img", "crossorigin");
  			addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
  		}
  
  		// Special: iframe, ruby, video, audio, label
  
  		// Delete children of the same name from it's parent
  		// For example: form can't have a child of the name form
  		each(split('a form meter progress dfn'), function(name) {
  			if (schema[name]) {
  				delete schema[name].children[name];
  			}
  		});
  
  		// Delete header, footer, sectioning and heading content descendants
  		/*each('dt th address', function(name) {
  			delete schema[name].children[name];
  		});*/
  
  		// Caption can't have tables
  		delete schema.caption.children.table;
  
  		// TODO: LI:s can only have value if parent is OL
  
  		// TODO: Handle transparent elements
  		// a ins del canvas map
  
  		mapCache[type] = schema;
  
  		return schema;
  	}
  
  	function compileElementMap(value, mode) {
  		var styles;
  
  		if (value) {
  			styles = {};
  
  			if (typeof value == 'string') {
  				value = {
  					'*': value
  				};
  			}
  
  			// Convert styles into a rule list
  			each(value, function(value, key) {
  				styles[key] = styles[key.toUpperCase()] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
  			});
  		}
  
  		return styles;
  	}
  
  	/**
  	 * Constructs a new Schema instance.
  	 *
  	 * @constructor
  	 * @method Schema
  	 * @param {Object} settings Name/value settings object.
  	 */
  	return function(settings) {
  		var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
  		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
  		var blockElementsMap, nonEmptyElementsMap, moveCaretBeforeOnEnterElementsMap, textBlockElementsMap, textInlineElementsMap;
  		var customElementsMap = {}, specialElements = {};
  
  		// Creates an lookup table map object for the specified option or the default value
  		function createLookupTable(option, default_value, extendWith) {
  			var value = settings[option];
  
  			if (!value) {
  				// Get cached default map or make it if needed
  				value = mapCache[option];
  
  				if (!value) {
  					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
  					value = extend(value, extendWith);
  
  					mapCache[option] = value;
  				}
  			} else {
  				// Create custom map
  				value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
  			}
  
  			return value;
  		}
  
  		settings = settings || {};
  		schemaItems = compileSchema(settings.schema);
  
  		// Allow all elements and attributes if verify_html is set to false
  		if (settings.verify_html === false) {
  			settings.valid_elements = '*[*]';
  		}
  
  		validStyles = compileElementMap(settings.valid_styles);
  		invalidStyles = compileElementMap(settings.invalid_styles, 'map');
  		validClasses = compileElementMap(settings.valid_classes, 'map');
  
  		// Setup map objects
  		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
  		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
  		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
  			'meta param embed source wbr track');
  		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
  			'noshade nowrap readonly selected autoplay loop controls');
  		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
  		moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', 'table', nonEmptyElementsMap);
  		textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
  						'blockquote center dir fieldset header footer article section hgroup aside nav figure');
  		blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
  						'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
  						'datalist select optgroup', textBlockElementsMap);
  		textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
  										'dfn code mark q sup sub samp');
  
  		each((settings.special || 'script noscript style textarea').split(' '), function(name) {
  			specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
  		});
  
  		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
  		function patternToRegExp(str) {
  			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
  		}
  
  		// Parses the specified valid_elements string and adds to the current rules
  		// This function is a bit hard to read since it's heavily optimized for speed
  		function addValidElements(validElements) {
  			var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
  				prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
  				elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
  				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
  				hasPatternsRegExp = /[*?+]/;
  
  			if (validElements) {
  				// Split valid elements into an array with rules
  				validElements = split(validElements, ',');
  
  				if (elements['@']) {
  					globalAttributes = elements['@'].attributes;
  					globalAttributesOrder = elements['@'].attributesOrder;
  				}
  
  				// Loop all rules
  				for (ei = 0, el = validElements.length; ei < el; ei++) {
  					// Parse element rule
  					matches = elementRuleRegExp.exec(validElements[ei]);
  					if (matches) {
  						// Setup local names for matches
  						prefix = matches[1];
  						elementName = matches[2];
  						outputName = matches[3];
  						attrData = matches[5];
  
  						// Create new attributes and attributesOrder
  						attributes = {};
  						attributesOrder = [];
  
  						// Create the new element
  						element = {
  							attributes: attributes,
  							attributesOrder: attributesOrder
  						};
  
  						// Padd empty elements prefix
  						if (prefix === '#') {
  							element.paddEmpty = true;
  						}
  
  						// Remove empty elements prefix
  						if (prefix === '-') {
  							element.removeEmpty = true;
  						}
  
  						if (matches[4] === '!') {
  							element.removeEmptyAttrs = true;
  						}
  
  						// Copy attributes from global rule into current rule
  						if (globalAttributes) {
  							for (key in globalAttributes) {
  								attributes[key] = globalAttributes[key];
  							}
  
  							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
  						}
  
  						// Attributes defined
  						if (attrData) {
  							attrData = split(attrData, '|');
  							for (ai = 0, al = attrData.length; ai < al; ai++) {
  								matches = attrRuleRegExp.exec(attrData[ai]);
  								if (matches) {
  									attr = {};
  									attrType = matches[1];
  									attrName = matches[2].replace(/::/g, ':');
  									prefix = matches[3];
  									value = matches[4];
  
  									// Required
  									if (attrType === '!') {
  										element.attributesRequired = element.attributesRequired || [];
  										element.attributesRequired.push(attrName);
  										attr.required = true;
  									}
  
  									// Denied from global
  									if (attrType === '-') {
  										delete attributes[attrName];
  										attributesOrder.splice(inArray(attributesOrder, attrName), 1);
  										continue;
  									}
  
  									// Default value
  									if (prefix) {
  										// Default value
  										if (prefix === '=') {
  											element.attributesDefault = element.attributesDefault || [];
  											element.attributesDefault.push({name: attrName, value: value});
  											attr.defaultValue = value;
  										}
  
  										// Forced value
  										if (prefix === ':') {
  											element.attributesForced = element.attributesForced || [];
  											element.attributesForced.push({name: attrName, value: value});
  											attr.forcedValue = value;
  										}
  
  										// Required values
  										if (prefix === '<') {
  											attr.validValues = makeMap(value, '?');
  										}
  									}
  
  									// Check for attribute patterns
  									if (hasPatternsRegExp.test(attrName)) {
  										element.attributePatterns = element.attributePatterns || [];
  										attr.pattern = patternToRegExp(attrName);
  										element.attributePatterns.push(attr);
  									} else {
  										// Add attribute to order list if it doesn't already exist
  										if (!attributes[attrName]) {
  											attributesOrder.push(attrName);
  										}
  
  										attributes[attrName] = attr;
  									}
  								}
  							}
  						}
  
  						// Global rule, store away these for later usage
  						if (!globalAttributes && elementName == '@') {
  							globalAttributes = attributes;
  							globalAttributesOrder = attributesOrder;
  						}
  
  						// Handle substitute elements such as b/strong
  						if (outputName) {
  							element.outputName = elementName;
  							elements[outputName] = element;
  						}
  
  						// Add pattern or exact element
  						if (hasPatternsRegExp.test(elementName)) {
  							element.pattern = patternToRegExp(elementName);
  							patternElements.push(element);
  						} else {
  							elements[elementName] = element;
  						}
  					}
  				}
  			}
  		}
  
  		function setValidElements(validElements) {
  			elements = {};
  			patternElements = [];
  
  			addValidElements(validElements);
  
  			each(schemaItems, function(element, name) {
  				children[name] = element.children;
  			});
  		}
  
  		// Adds custom non HTML elements to the schema
  		function addCustomElements(customElements) {
  			var customElementRegExp = /^(~)?(.+)$/;
  
  			if (customElements) {
  				// Flush cached items since we are altering the default maps
  				mapCache.text_block_elements = mapCache.block_elements = null;
  
  				each(split(customElements, ','), function(rule) {
  					var matches = customElementRegExp.exec(rule),
  						inline = matches[1] === '~',
  						cloneName = inline ? 'span' : 'div',
  						name = matches[2];
  
  					children[name] = children[cloneName];
  					customElementsMap[name] = cloneName;
  
  					// If it's not marked as inline then add it to valid block elements
  					if (!inline) {
  						blockElementsMap[name.toUpperCase()] = {};
  						blockElementsMap[name] = {};
  					}
  
  					// Add elements clone if needed
  					if (!elements[name]) {
  						var customRule = elements[cloneName];
  
  						customRule = extend({}, customRule);
  						delete customRule.removeEmptyAttrs;
  						delete customRule.removeEmpty;
  
  						elements[name] = customRule;
  					}
  
  					// Add custom elements at span/div positions
  					each(children, function(element, elmName) {
  						if (element[cloneName]) {
  							children[elmName] = element = extend({}, children[elmName]);
  							element[name] = element[cloneName];
  						}
  					});
  				});
  			}
  		}
  
  		// Adds valid children to the schema object
  		function addValidChildren(validChildren) {
  			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
  
  			if (validChildren) {
  				each(split(validChildren, ','), function(rule) {
  					var matches = childRuleRegExp.exec(rule), parent, prefix;
  
  					if (matches) {
  						prefix = matches[1];
  
  						// Add/remove items from default
  						if (prefix) {
  							parent = children[matches[2]];
  						} else {
  							parent = children[matches[2]] = {'#comment': {}};
  						}
  
  						parent = children[matches[2]];
  
  						each(split(matches[3], '|'), function(child) {
  							if (prefix === '-') {
  								// Clone the element before we delete
  								// things in it to not mess up default schemas
  								children[matches[2]] = parent = extend({}, children[matches[2]]);
  
  								delete parent[child];
  							} else {
  								parent[child] = {};
  							}
  						});
  					}
  				});
  			}
  		}
  
  		function getElementRule(name) {
  			var element = elements[name], i;
  
  			// Exact match found
  			if (element) {
  				return element;
  			}
  
  			// No exact match then try the patterns
  			i = patternElements.length;
  			while (i--) {
  				element = patternElements[i];
  
  				if (element.pattern.test(name)) {
  					return element;
  				}
  			}
  		}
  
  		if (!settings.valid_elements) {
  			// No valid elements defined then clone the elements from the schema spec
  			each(schemaItems, function(element, name) {
  				elements[name] = {
  					attributes: element.attributes,
  					attributesOrder: element.attributesOrder
  				};
  
  				children[name] = element.children;
  			});
  
  			// Switch these on HTML4
  			if (settings.schema != "html5") {
  				each(split('strong/b em/i'), function(item) {
  					item = split(item, '/');
  					elements[item[1]].outputName = item[0];
  				});
  			}
  
  			// Add default alt attribute for images
  			elements.img.attributesDefault = [{name: 'alt', value: ''}];
  
  			// Remove these if they are empty by default
  			each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
  				if (elements[name]) {
  					elements[name].removeEmpty = true;
  				}
  			});
  
  			// Padd these by default
  			each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
  				elements[name].paddEmpty = true;
  			});
  
  			// Remove these if they have no attributes
  			each(split('span'), function(name) {
  				elements[name].removeEmptyAttrs = true;
  			});
  
  			// Remove these by default
  			// TODO: Reenable in 4.1
  			/*each(split('script style'), function(name) {
  				delete elements[name];
  			});*/
  		} else {
  			setValidElements(settings.valid_elements);
  		}
  
  		addCustomElements(settings.custom_elements);
  		addValidChildren(settings.valid_children);
  		addValidElements(settings.extended_valid_elements);
  
  		// Todo: Remove this when we fix list handling to be valid
  		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
  
  		// Delete invalid elements
  		if (settings.invalid_elements) {
  			each(explode(settings.invalid_elements), function(item) {
  				if (elements[item]) {
  					delete elements[item];
  				}
  			});
  		}
  
  		// If the user didn't allow span only allow internal spans
  		if (!getElementRule('span')) {
  			addValidElements('span[!data-mce-type|*]');
  		}
  
  		/**
  		 * Name/value map object with valid parents and children to those parents.
  		 *
  		 * @example
  		 * children = {
  		 *    div:{p:{}, h1:{}}
  		 * };
  		 * @field children
  		 * @type Object
  		 */
  		self.children = children;
  
  		/**
  		 * Name/value map object with valid styles for each element.
  		 *
  		 * @method getValidStyles
  		 * @type Object
  		 */
  		self.getValidStyles = function() {
  			return validStyles;
  		};
  
  		/**
  		 * Name/value map object with valid styles for each element.
  		 *
  		 * @method getInvalidStyles
  		 * @type Object
  		 */
  		self.getInvalidStyles = function() {
  			return invalidStyles;
  		};
  
  		/**
  		 * Name/value map object with valid classes for each element.
  		 *
  		 * @method getValidClasses
  		 * @type Object
  		 */
  		self.getValidClasses = function() {
  			return validClasses;
  		};
  
  		/**
  		 * Returns a map with boolean attributes.
  		 *
  		 * @method getBoolAttrs
  		 * @return {Object} Name/value lookup map for boolean attributes.
  		 */
  		self.getBoolAttrs = function() {
  			return boolAttrMap;
  		};
  
  		/**
  		 * Returns a map with block elements.
  		 *
  		 * @method getBlockElements
  		 * @return {Object} Name/value lookup map for block elements.
  		 */
  		self.getBlockElements = function() {
  			return blockElementsMap;
  		};
  
  		/**
  		 * Returns a map with text block elements. Such as: p,h1-h6,div,address
  		 *
  		 * @method getTextBlockElements
  		 * @return {Object} Name/value lookup map for block elements.
  		 */
  		self.getTextBlockElements = function() {
  			return textBlockElementsMap;
  		};
  
  		/**
  		 * Returns a map of inline text format nodes for example strong/span or ins.
  		 *
  		 * @method getTextInlineElements
  		 * @return {Object} Name/value lookup map for text format elements.
  		 */
  		self.getTextInlineElements = function() {
  			return textInlineElementsMap;
  		};
  
  		/**
  		 * Returns a map with short ended elements such as BR or IMG.
  		 *
  		 * @method getShortEndedElements
  		 * @return {Object} Name/value lookup map for short ended elements.
  		 */
  		self.getShortEndedElements = function() {
  			return shortEndedElementsMap;
  		};
  
  		/**
  		 * Returns a map with self closing tags such as <li>.
  		 *
  		 * @method getSelfClosingElements
  		 * @return {Object} Name/value lookup map for self closing tags elements.
  		 */
  		self.getSelfClosingElements = function() {
  			return selfClosingElementsMap;
  		};
  
  		/**
  		 * Returns a map with elements that should be treated as contents regardless if it has text
  		 * content in them or not such as TD, VIDEO or IMG.
  		 *
  		 * @method getNonEmptyElements
  		 * @return {Object} Name/value lookup map for non empty elements.
  		 */
  		self.getNonEmptyElements = function() {
  			return nonEmptyElementsMap;
  		};
  
  		/**
  		 * Returns a map with elements that the caret should be moved in front of after enter is
  		 * pressed
  		 *
  		 * @method getMoveCaretBeforeOnEnterElements
  		 * @return {Object} Name/value lookup map for elements to place the caret in front of.
  		 */
  		self.getMoveCaretBeforeOnEnterElements = function() {
  			return moveCaretBeforeOnEnterElementsMap;
  		};
  
  		/**
  		 * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
  		 *
  		 * @method getWhiteSpaceElements
  		 * @return {Object} Name/value lookup map for white space elements.
  		 */
  		self.getWhiteSpaceElements = function() {
  			return whiteSpaceElementsMap;
  		};
  
  		/**
  		 * Returns a map with special elements. These are elements that needs to be parsed
  		 * in a special way such as script, style, textarea etc. The map object values
  		 * are regexps used to find the end of the element.
  		 *
  		 * @method getSpecialElements
  		 * @return {Object} Name/value lookup map for special elements.
  		 */
  		self.getSpecialElements = function() {
  			return specialElements;
  		};
  
  		/**
  		 * Returns true/false if the specified element and it's child is valid or not
  		 * according to the schema.
  		 *
  		 * @method isValidChild
  		 * @param {String} name Element name to check for.
  		 * @param {String} child Element child to verify.
  		 * @return {Boolean} True/false if the element is a valid child of the specified parent.
  		 */
  		self.isValidChild = function(name, child) {
  			var parent = children[name];
  
  			return !!(parent && parent[child]);
  		};
  
  		/**
  		 * Returns true/false if the specified element name and optional attribute is
  		 * valid according to the schema.
  		 *
  		 * @method isValid
  		 * @param {String} name Name of element to check.
  		 * @param {String} attr Optional attribute name to check for.
  		 * @return {Boolean} True/false if the element and attribute is valid.
  		 */
  		self.isValid = function(name, attr) {
  			var attrPatterns, i, rule = getElementRule(name);
  
  			// Check if it's a valid element
  			if (rule) {
  				if (attr) {
  					// Check if attribute name exists
  					if (rule.attributes[attr]) {
  						return true;
  					}
  
  					// Check if attribute matches a regexp pattern
  					attrPatterns = rule.attributePatterns;
  					if (attrPatterns) {
  						i = attrPatterns.length;
  						while (i--) {
  							if (attrPatterns[i].pattern.test(name)) {
  								return true;
  							}
  						}
  					}
  				} else {
  					return true;
  				}
  			}
  
  			// No match
  			return false;
  		};
  
  		/**
  		 * Returns true/false if the specified element is valid or not
  		 * according to the schema.
  		 *
  		 * @method getElementRule
  		 * @param {String} name Element name to check for.
  		 * @return {Object} Element object or undefined if the element isn't valid.
  		 */
  		self.getElementRule = getElementRule;
  
  		/**
  		 * Returns an map object of all custom elements.
  		 *
  		 * @method getCustomElements
  		 * @return {Object} Name/value map object of all custom elements.
  		 */
  		self.getCustomElements = function() {
  			return customElementsMap;
  		};
  
  		/**
  		 * Parses a valid elements string and adds it to the schema. The valid elements
  		 * format is for example "element[attr=default|otherattr]".
  		 * Existing rules will be replaced with the ones specified, so this extends the schema.
  		 *
  		 * @method addValidElements
  		 * @param {String} valid_elements String in the valid elements format to be parsed.
  		 */
  		self.addValidElements = addValidElements;
  
  		/**
  		 * Parses a valid elements string and sets it to the schema. The valid elements
  		 * format is for example "element[attr=default|otherattr]".
  		 * Existing rules will be replaced with the ones specified, so this extends the schema.
  		 *
  		 * @method setValidElements
  		 * @param {String} valid_elements String in the valid elements format to be parsed.
  		 */
  		self.setValidElements = setValidElements;
  
  		/**
  		 * Adds custom non HTML elements to the schema.
  		 *
  		 * @method addCustomElements
  		 * @param {String} custom_elements Comma separated list of custom elements to add.
  		 */
  		self.addCustomElements = addCustomElements;
  
  		/**
  		 * Parses a valid children string and adds them to the schema structure. The valid children
  		 * format is for example: "element[child1|child2]".
  		 *
  		 * @method addValidChildren
  		 * @param {String} valid_children Valid children elements string to parse
  		 */
  		self.addValidChildren = addValidChildren;
  
  		self.elements = elements;
  	};
  });
  
  // Included from: js/maya/classes/html/SaxParser.js
  
  /**
   * SaxParser.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*eslint max-depth:[2, 9] */
  
  /**
   * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
   * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
   * and attributes that doesn't fit the schema if the validate setting is enabled.
   *
   * @example
   * var parser = new maya.html.SaxParser({
   *     validate: true,
   *
   *     comment: function(text) {
   *         console.log('Comment:', text);
   *     },
   *
   *     cdata: function(text) {
   *         console.log('CDATA:', text);
   *     },
   *
   *     text: function(text, raw) {
   *         console.log('Text:', text, 'Raw:', raw);
   *     },
   *
   *     start: function(name, attrs, empty) {
   *         console.log('Start:', name, attrs, empty);
   *     },
   *
   *     end: function(name) {
   *         console.log('End:', name);
   *     },
   *
   *     pi: function(name, text) {
   *         console.log('PI:', name, text);
   *     },
   *
   *     doctype: function(text) {
   *         console.log('DocType:', text);
   *     }
   * }, schema);
   * @class maya.html.SaxParser
   * @version 3.4
   */
  define("maya/html/SaxParser", [
  	"maya/html/Schema",
  	"maya/html/Entities",
  	"maya/util/Tools"
  ], function(Schema, Entities, Tools) {
  	var each = Tools.each;
  
  	/**
  	 * Returns the index of the end tag for a specific start tag. This can be
  	 * used to skip all children of a parent element from being processed.
  	 *
  	 * @private
  	 * @method findEndTag
  	 * @param {maya.html.Schema} schema Schema instance to use to match short ended elements.
  	 * @param {String} html HTML string to find the end tag in.
  	 * @param {Number} startIndex Indext to start searching at should be after the start tag.
  	 * @return {Number} Index of the end tag.
  	 */
  	function findEndTag(schema, html, startIndex) {
  		var count = 1, index, matches, tokenRegExp, shortEndedElements;
  
  		shortEndedElements = schema.getShortEndedElements();
  		tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
  		tokenRegExp.lastIndex = index = startIndex;
  
  		while ((matches = tokenRegExp.exec(html))) {
  			index = tokenRegExp.lastIndex;
  
  			if (matches[1] === '/') { // End element
  				count--;
  			} else if (!matches[1]) { // Start element
  				if (matches[2] in shortEndedElements) {
  					continue;
  				}
  
  				count++;
  			}
  
  			if (count === 0) {
  				break;
  			}
  		}
  
  		return index;
  	}
  
  	/**
  	 * Constructs a new SaxParser instance.
  	 *
  	 * @constructor
  	 * @method SaxParser
  	 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  	 * @param {maya.html.Schema} schema HTML Schema class to use when parsing.
  	 */
  	function SaxParser(settings, schema) {
  		var self = this;
  
  		function noop() {}
  
  		settings = settings || {};
  		self.schema = schema = schema || new Schema();
  
  		if (settings.fix_self_closing !== false) {
  			settings.fix_self_closing = true;
  		}
  
  		// Add handler functions from settings and setup default handlers
  		each('comment cdata text start end pi doctype'.split(' '), function(name) {
  			if (name) {
  				self[name] = settings[name] || noop;
  			}
  		});
  
  		/**
  		 * Parses the specified HTML string and executes the callbacks for each item it finds.
  		 *
  		 * @example
  		 * new SaxParser({...}).parse('<b>text</b>');
  		 * @method parse
  		 * @param {String} html Html string to sax parse.
  		 */
  		self.parse = function(html) {
  			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
  			var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
  			var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
  			var attributesRequired, attributesDefault, attributesForced;
  			var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
  			var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
  			var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
  
  			function processEndTag(name) {
  				var pos, i;
  
  				// Find position of parent of the same type
  				pos = stack.length;
  				while (pos--) {
  					if (stack[pos].name === name) {
  						break;
  					}
  				}
  
  				// Found parent
  				if (pos >= 0) {
  					// Close all the open elements
  					for (i = stack.length - 1; i >= pos; i--) {
  						name = stack[i];
  
  						if (name.valid) {
  							self.end(name.name);
  						}
  					}
  
  					// Remove the open elements from the stack
  					stack.length = pos;
  				}
  			}
  
  			function parseAttribute(match, name, value, val2, val3) {
  				var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
  
  				name = name.toLowerCase();
  				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
  
  				// Validate name and value pass through all data- attributes
  				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
  					attrRule = validAttributesMap[name];
  
  					// Find rule by pattern matching
  					if (!attrRule && validAttributePatterns) {
  						i = validAttributePatterns.length;
  						while (i--) {
  							attrRule = validAttributePatterns[i];
  							if (attrRule.pattern.test(name)) {
  								break;
  							}
  						}
  
  						// No rule matched
  						if (i === -1) {
  							attrRule = null;
  						}
  					}
  
  					// No attribute rule found
  					if (!attrRule) {
  						return;
  					}
  
  					// Validate value
  					if (attrRule.validValues && !(value in attrRule.validValues)) {
  						return;
  					}
  				}
  
  				// Block any javascript: urls or non image data uris
  				if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
  					var uri = value.replace(trimRegExp, '');
  
  					try {
  						// Might throw malformed URI sequence
  						uri = decodeURIComponent(uri);
  					} catch (ex) {
  						// Fallback to non UTF-8 decoder
  						uri = unescape(uri);
  					}
  
  					if (scriptUriRegExp.test(uri)) {
  						return;
  					}
  
  					if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
  						return;
  					}
  				}
  
  				// Add attribute to list and map
  				attrList.map[name] = value;
  				attrList.push({
  					name: name,
  					value: value
  				});
  			}
  
  			// Precompile RegExps and map objects
  			tokenRegExp = new RegExp('<(?:' +
  				'(?:!--([\\w\\W]*?)-->)|' + // Comment
  				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
  				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
  				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
  				'(?:\\/([^>]+)>)|' + // End element
  				'(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
  			')', 'g');
  
  			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
  
  			// Setup lookup tables for empty elements and boolean attributes
  			shortEndedElements = schema.getShortEndedElements();
  			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
  			fillAttrsMap = schema.getBoolAttrs();
  			validate = settings.validate;
  			removeInternalElements = settings.remove_internals;
  			fixSelfClosing = settings.fix_self_closing;
  			specialElements = schema.getSpecialElements();
  
  			while ((matches = tokenRegExp.exec(html))) {
  				// Text
  				if (index < matches.index) {
  					self.text(decode(html.substr(index, matches.index - index)));
  				}
  
  				if ((value = matches[6])) { // End element
  					value = value.toLowerCase();
  
  					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  					if (value.charAt(0) === ':') {
  						value = value.substr(1);
  					}
  
  					processEndTag(value);
  				} else if ((value = matches[7])) { // Start element
  					value = value.toLowerCase();
  
  					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  					if (value.charAt(0) === ':') {
  						value = value.substr(1);
  					}
  
  					isShortEnded = value in shortEndedElements;
  
  					// Is self closing tag for example an <li> after an open <li>
  					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
  						processEndTag(value);
  					}
  
  					// Validate element
  					if (!validate || (elementRule = schema.getElementRule(value))) {
  						isValidElement = true;
  
  						// Grab attributes map and patters when validation is enabled
  						if (validate) {
  							validAttributesMap = elementRule.attributes;
  							validAttributePatterns = elementRule.attributePatterns;
  						}
  
  						// Parse attributes
  						if ((attribsValue = matches[8])) {
  							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
  
  							// If the element has internal attributes then remove it if we are told to do so
  							if (isInternalElement && removeInternalElements) {
  								isValidElement = false;
  							}
  
  							attrList = [];
  							attrList.map = {};
  
  							attribsValue.replace(attrRegExp, parseAttribute);
  						} else {
  							attrList = [];
  							attrList.map = {};
  						}
  
  						// Process attributes if validation is enabled
  						if (validate && !isInternalElement) {
  							attributesRequired = elementRule.attributesRequired;
  							attributesDefault = elementRule.attributesDefault;
  							attributesForced = elementRule.attributesForced;
  							anyAttributesRequired = elementRule.removeEmptyAttrs;
  
  							// Check if any attribute exists
  							if (anyAttributesRequired && !attrList.length) {
  								isValidElement = false;
  							}
  
  							// Handle forced attributes
  							if (attributesForced) {
  								i = attributesForced.length;
  								while (i--) {
  									attr = attributesForced[i];
  									name = attr.name;
  									attrValue = attr.value;
  
  									if (attrValue === '{$uid}') {
  										attrValue = 'mce_' + idCount++;
  									}
  
  									attrList.map[name] = attrValue;
  									attrList.push({name: name, value: attrValue});
  								}
  							}
  
  							// Handle default attributes
  							if (attributesDefault) {
  								i = attributesDefault.length;
  								while (i--) {
  									attr = attributesDefault[i];
  									name = attr.name;
  
  									if (!(name in attrList.map)) {
  										attrValue = attr.value;
  
  										if (attrValue === '{$uid}') {
  											attrValue = 'mce_' + idCount++;
  										}
  
  										attrList.map[name] = attrValue;
  										attrList.push({name: name, value: attrValue});
  									}
  								}
  							}
  
  							// Handle required attributes
  							if (attributesRequired) {
  								i = attributesRequired.length;
  								while (i--) {
  									if (attributesRequired[i] in attrList.map) {
  										break;
  									}
  								}
  
  								// None of the required attributes where found
  								if (i === -1) {
  									isValidElement = false;
  								}
  							}
  
  							// Invalidate element if it's marked as bogus
  							if ((attr = attrList.map['data-mce-bogus'])) {
  								if (attr === 'all') {
  									index = findEndTag(schema, html, tokenRegExp.lastIndex);
  									tokenRegExp.lastIndex = index;
  									continue;
  								}
  
  								isValidElement = false;
  							}
  						}
  
  						if (isValidElement) {
  							self.start(value, attrList, isShortEnded);
  						}
  					} else {
  						isValidElement = false;
  					}
  
  					// Treat script, noscript and style a bit different since they may include code that looks like elements
  					if ((endRegExp = specialElements[value])) {
  						endRegExp.lastIndex = index = matches.index + matches[0].length;
  
  						if ((matches = endRegExp.exec(html))) {
  							if (isValidElement) {
  								text = html.substr(index, matches.index - index);
  							}
  
  							index = matches.index + matches[0].length;
  						} else {
  							text = html.substr(index);
  							index = html.length;
  						}
  
  						if (isValidElement) {
  							if (text.length > 0) {
  								self.text(text, true);
  							}
  
  							self.end(value);
  						}
  
  						tokenRegExp.lastIndex = index;
  						continue;
  					}
  
  					// Push value on to stack
  					if (!isShortEnded) {
  						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
  							stack.push({name: value, valid: isValidElement});
  						} else if (isValidElement) {
  							self.end(value);
  						}
  					}
  				} else if ((value = matches[1])) { // Comment
  					// Padd comment value to avoid browsers from parsing invalid comments as HTML
  					if (value.charAt(0) === '>') {
  						value = ' ' + value;
  					}
  
  					if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
  						value = ' ' + value;
  					}
  
  					self.comment(value);
  				} else if ((value = matches[2])) { // CDATA
  					self.cdata(value);
  				} else if ((value = matches[3])) { // DOCTYPE
  					self.doctype(value);
  				} else if ((value = matches[4])) { // PI
  					self.pi(value, matches[5]);
  				}
  
  				index = matches.index + matches[0].length;
  			}
  
  			// Text
  			if (index < html.length) {
  				self.text(decode(html.substr(index)));
  			}
  
  			// Close any open elements
  			for (i = stack.length - 1; i >= 0; i--) {
  				value = stack[i];
  
  				if (value.valid) {
  					self.end(value.name);
  				}
  			}
  		};
  	}
  
  	SaxParser.findEndTag = findEndTag;
  
  	return SaxParser;
  });
  
  // Included from: js/maya/classes/html/DomParser.js
  
  /**
   * DomParser.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
   * sure that the node tree is valid according to the specified schema.
   * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
   *
   * @example
   * var parser = new maya.html.DomParser({validate: true}, schema);
   * var rootNode = parser.parse('<h1>content</h1>');
   *
   * @class maya.html.DomParser
   * @version 3.4
   */
  define("maya/html/DomParser", [
  	"maya/html/Node",
  	"maya/html/Schema",
  	"maya/html/SaxParser",
  	"maya/util/Tools"
  ], function(Node, Schema, SaxParser, Tools) {
  	var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
  
  	/**
  	 * Constructs a new DomParser instance.
  	 *
  	 * @constructor
  	 * @method DomParser
  	 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  	 * @param {maya.html.Schema} schema HTML Schema class to use when parsing.
  	 */
  	return function(settings, schema) {
  		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
  
  		settings = settings || {};
  		settings.validate = "validate" in settings ? settings.validate : true;
  		settings.root_name = settings.root_name || 'body';
  		self.schema = schema = schema || new Schema();
  
  		function fixInvalidChildren(nodes) {
  			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
  			var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
  
  			nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
  			nonEmptyElements = schema.getNonEmptyElements();
  			textBlockElements = schema.getTextBlockElements();
  
  			for (ni = 0; ni < nodes.length; ni++) {
  				node = nodes[ni];
  
  				// Already removed or fixed
  				if (!node.parent || node.fixed) {
  					continue;
  				}
  
  				// If the invalid element is a text block and the text block is within a parent LI element
  				// Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
  				if (textBlockElements[node.name] && node.parent.name == 'li') {
  					// Move sibling text blocks after LI element
  					sibling = node.next;
  					while (sibling) {
  						if (textBlockElements[sibling.name]) {
  							sibling.name = 'li';
  							sibling.fixed = true;
  							node.parent.insert(sibling, node.parent);
  						} else {
  							break;
  						}
  
  						sibling = sibling.next;
  					}
  
  					// Unwrap current text block
  					node.unwrap(node);
  					continue;
  				}
  
  				// Get list of all parent nodes until we find a valid parent to stick the child into
  				parents = [node];
  				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
  					!nonSplitableElements[parent.name]; parent = parent.parent) {
  					parents.push(parent);
  				}
  
  				// Found a suitable parent
  				if (parent && parents.length > 1) {
  					// Reverse the array since it makes looping easier
  					parents.reverse();
  
  					// Clone the related parent and insert that after the moved node
  					newParent = currentNode = self.filterNode(parents[0].clone());
  
  					// Start cloning and moving children on the left side of the target node
  					for (i = 0; i < parents.length - 1; i++) {
  						if (schema.isValidChild(currentNode.name, parents[i].name)) {
  							tempNode = self.filterNode(parents[i].clone());
  							currentNode.append(tempNode);
  						} else {
  							tempNode = currentNode;
  						}
  
  						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) {
  							nextNode = childNode.next;
  							tempNode.append(childNode);
  							childNode = nextNode;
  						}
  
  						currentNode = tempNode;
  					}
  
  					if (!newParent.isEmpty(nonEmptyElements)) {
  						parent.insert(newParent, parents[0], true);
  						parent.insert(node, newParent);
  					} else {
  						parent.insert(node, parents[0], true);
  					}
  
  					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
  					parent = parents[0];
  					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
  						parent.empty().remove();
  					}
  				} else if (node.parent) {
  					// If it's an LI try to find a UL/OL for it or wrap it
  					if (node.name === 'li') {
  						sibling = node.prev;
  						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  							sibling.append(node);
  							continue;
  						}
  
  						sibling = node.next;
  						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  							sibling.insert(node, sibling.firstChild, true);
  							continue;
  						}
  
  						node.wrap(self.filterNode(new Node('ul', 1)));
  						continue;
  					}
  
  					// Try wrapping the element in a DIV
  					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
  						node.wrap(self.filterNode(new Node('div', 1)));
  					} else {
  						// We failed wrapping it, then remove or unwrap it
  						if (node.name === 'style' || node.name === 'script') {
  							node.empty().remove();
  						} else {
  							node.unwrap();
  						}
  					}
  				}
  			}
  		}
  
  		/**
  		 * Runs the specified node though the element and attributes filters.
  		 *
  		 * @method filterNode
  		 * @param {maya.html.Node} Node the node to run filters on.
  		 * @return {maya.html.Node} The passed in node.
  		 */
  		self.filterNode = function(node) {
  			var i, name, list;
  
  			// Run element filters
  			if (name in nodeFilters) {
  				list = matchedNodes[name];
  
  				if (list) {
  					list.push(node);
  				} else {
  					matchedNodes[name] = [node];
  				}
  			}
  
  			// Run attribute filters
  			i = attributeFilters.length;
  			while (i--) {
  				name = attributeFilters[i].name;
  
  				if (name in node.attributes.map) {
  					list = matchedAttributes[name];
  
  					if (list) {
  						list.push(node);
  					} else {
  						matchedAttributes[name] = [node];
  					}
  				}
  			}
  
  			return node;
  		};
  
  		/**
  		 * Adds a node filter function to the parser, the parser will collect the specified nodes by name
  		 * and then execute the callback ones it has finished parsing the document.
  		 *
  		 * @example
  		 * parser.addNodeFilter('p,h1', function(nodes, name) {
  		 *		for (var i = 0; i < nodes.length; i++) {
  		 *			console.log(nodes[i].name);
  		 *		}
  		 * });
  		 * @method addNodeFilter
  		 * @method {String} name Comma separated list of nodes to collect.
  		 * @param {function} callback Callback function to execute once it has collected nodes.
  		 */
  		self.addNodeFilter = function(name, callback) {
  			each(explode(name), function(name) {
  				var list = nodeFilters[name];
  
  				if (!list) {
  					nodeFilters[name] = list = [];
  				}
  
  				list.push(callback);
  			});
  		};
  
  		/**
  		 * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
  		 * and then execute the callback ones it has finished parsing the document.
  		 *
  		 * @example
  		 * parser.addAttributeFilter('src,href', function(nodes, name) {
  		 *		for (var i = 0; i < nodes.length; i++) {
  		 *			console.log(nodes[i].name);
  		 *		}
  		 * });
  		 * @method addAttributeFilter
  		 * @method {String} name Comma separated list of nodes to collect.
  		 * @param {function} callback Callback function to execute once it has collected nodes.
  		 */
  		self.addAttributeFilter = function(name, callback) {
  			each(explode(name), function(name) {
  				var i;
  
  				for (i = 0; i < attributeFilters.length; i++) {
  					if (attributeFilters[i].name === name) {
  						attributeFilters[i].callbacks.push(callback);
  						return;
  					}
  				}
  
  				attributeFilters.push({name: name, callbacks: [callback]});
  			});
  		};
  
  		/**
  		 * Parses the specified HTML string into a DOM like node tree and returns the result.
  		 *
  		 * @example
  		 * var rootNode = new DomParser({...}).parse('<b>text</b>');
  		 * @method parse
  		 * @param {String} html Html string to sax parse.
  		 * @param {Object} args Optional args object that gets passed to all filter functions.
  		 * @return {maya.html.Node} Root node containing the tree.
  		 */
  		self.parse = function(html, args) {
  			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
  			var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
  			var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
  			var children, nonEmptyElements, rootBlockName;
  
  			args = args || {};
  			matchedNodes = {};
  			matchedAttributes = {};
  			blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
  			nonEmptyElements = schema.getNonEmptyElements();
  			children = schema.children;
  			validate = settings.validate;
  			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
  
  			whiteSpaceElements = schema.getWhiteSpaceElements();
  			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
  			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
  			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
  			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
  
  			function addRootBlocks() {
  				var node = rootNode.firstChild, next, rootBlockNode;
  
  				// Removes whitespace at beginning and end of block so:
  				// <p> x </p> -> <p>x</p>
  				function trim(rootBlockNode) {
  					if (rootBlockNode) {
  						node = rootBlockNode.firstChild;
  						if (node && node.type == 3) {
  							node.value = node.value.replace(startWhiteSpaceRegExp, '');
  						}
  
  						node = rootBlockNode.lastChild;
  						if (node && node.type == 3) {
  							node.value = node.value.replace(endWhiteSpaceRegExp, '');
  						}
  					}
  				}
  
  				// Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
  				if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
  					return;
  				}
  
  				while (node) {
  					next = node.next;
  
  					if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
  						!blockElements[node.name] && !node.attr('data-mce-type'))) {
  						if (!rootBlockNode) {
  							// Create a new root block element
  							rootBlockNode = createNode(rootBlockName, 1);
  							rootBlockNode.attr(settings.forced_root_block_attrs);
  							rootNode.insert(rootBlockNode, node);
  							rootBlockNode.append(node);
  						} else {
  							rootBlockNode.append(node);
  						}
  					} else {
  						trim(rootBlockNode);
  						rootBlockNode = null;
  					}
  
  					node = next;
  				}
  
  				trim(rootBlockNode);
  			}
  
  			function createNode(name, type) {
  				var node = new Node(name, type), list;
  
  				if (name in nodeFilters) {
  					list = matchedNodes[name];
  
  					if (list) {
  						list.push(node);
  					} else {
  						matchedNodes[name] = [node];
  					}
  				}
  
  				return node;
  			}
  
  			function removeWhitespaceBefore(node) {
  				var textNode, textVal, sibling;
  
  				for (textNode = node.prev; textNode && textNode.type === 3;) {
  					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
  
  					if (textVal.length > 0) {
  						textNode.value = textVal;
  						textNode = textNode.prev;
  					} else {
  						sibling = textNode.prev;
  						textNode.remove();
  						textNode = sibling;
  					}
  				}
  			}
  
  			function cloneAndExcludeBlocks(input) {
  				var name, output = {};
  
  				for (name in input) {
  					if (name !== 'li' && name != 'p') {
  						output[name] = input[name];
  					}
  				}
  
  				return output;
  			}
  
  			parser = new SaxParser({
  				validate: validate,
  				allow_script_urls: settings.allow_script_urls,
  				allow_conditional_comments: settings.allow_conditional_comments,
  
  				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
  				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
  
  				cdata: function(text) {
  					node.append(createNode('#cdata', 4)).value = text;
  				},
  
  				text: function(text, raw) {
  					var textNode;
  
  					// Trim all redundant whitespace on non white space elements
  					if (!isInWhiteSpacePreservedElement) {
  						text = text.replace(allWhiteSpaceRegExp, ' ');
  
  						if (node.lastChild && blockElements[node.lastChild.name]) {
  							text = text.replace(startWhiteSpaceRegExp, '');
  						}
  					}
  
  					// Do we need to create the node
  					if (text.length !== 0) {
  						textNode = createNode('#text', 3);
  						textNode.raw = !!raw;
  						node.append(textNode).value = text;
  					}
  				},
  
  				comment: function(text) {
  					node.append(createNode('#comment', 8)).value = text;
  				},
  
  				pi: function(name, text) {
  					node.append(createNode(name, 7)).value = text;
  					removeWhitespaceBefore(node);
  				},
  
  				doctype: function(text) {
  					var newNode;
  
  					newNode = node.append(createNode('#doctype', 10));
  					newNode.value = text;
  					removeWhitespaceBefore(node);
  				},
  
  				start: function(name, attrs, empty) {
  					var newNode, attrFiltersLen, elementRule, attrName, parent;
  
  					elementRule = validate ? schema.getElementRule(name) : {};
  					if (elementRule) {
  						newNode = createNode(elementRule.outputName || name, 1);
  						newNode.attributes = attrs;
  						newNode.shortEnded = empty;
  
  						node.append(newNode);
  
  						// Check if node is valid child of the parent node is the child is
  						// unknown we don't collect it since it's probably a custom element
  						parent = children[node.name];
  						if (parent && children[newNode.name] && !parent[newNode.name]) {
  							invalidChildren.push(newNode);
  						}
  
  						attrFiltersLen = attributeFilters.length;
  						while (attrFiltersLen--) {
  							attrName = attributeFilters[attrFiltersLen].name;
  
  							if (attrName in attrs.map) {
  								list = matchedAttributes[attrName];
  
  								if (list) {
  									list.push(newNode);
  								} else {
  									matchedAttributes[attrName] = [newNode];
  								}
  							}
  						}
  
  						// Trim whitespace before block
  						if (blockElements[name]) {
  							removeWhitespaceBefore(newNode);
  						}
  
  						// Change current node if the element wasn't empty i.e not <br /> or <img />
  						if (!empty) {
  							node = newNode;
  						}
  
  						// Check if we are inside a whitespace preserved element
  						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  							isInWhiteSpacePreservedElement = true;
  						}
  					}
  				},
  
  				end: function(name) {
  					var textNode, elementRule, text, sibling, tempNode;
  
  					elementRule = validate ? schema.getElementRule(name) : {};
  					if (elementRule) {
  						if (blockElements[name]) {
  							if (!isInWhiteSpacePreservedElement) {
  								// Trim whitespace of the first node in a block
  								textNode = node.firstChild;
  								if (textNode && textNode.type === 3) {
  									text = textNode.value.replace(startWhiteSpaceRegExp, '');
  
  									// Any characters left after trim or should we remove it
  									if (text.length > 0) {
  										textNode.value = text;
  										textNode = textNode.next;
  									} else {
  										sibling = textNode.next;
  										textNode.remove();
  										textNode = sibling;
  
  										// Remove any pure whitespace siblings
  										while (textNode && textNode.type === 3) {
  											text = textNode.value;
  											sibling = textNode.next;
  
  											if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  												textNode.remove();
  												textNode = sibling;
  											}
  
  											textNode = sibling;
  										}
  									}
  								}
  
  								// Trim whitespace of the last node in a block
  								textNode = node.lastChild;
  								if (textNode && textNode.type === 3) {
  									text = textNode.value.replace(endWhiteSpaceRegExp, '');
  
  									// Any characters left after trim or should we remove it
  									if (text.length > 0) {
  										textNode.value = text;
  										textNode = textNode.prev;
  									} else {
  										sibling = textNode.prev;
  										textNode.remove();
  										textNode = sibling;
  
  										// Remove any pure whitespace siblings
  										while (textNode && textNode.type === 3) {
  											text = textNode.value;
  											sibling = textNode.prev;
  
  											if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  												textNode.remove();
  												textNode = sibling;
  											}
  
  											textNode = sibling;
  										}
  									}
  								}
  							}
  
  							// Trim start white space
  							// Removed due to: #5424
  							/*textNode = node.prev;
  							if (textNode && textNode.type === 3) {
  								text = textNode.value.replace(startWhiteSpaceRegExp, '');
  
  								if (text.length > 0)
  									textNode.value = text;
  								else
  									textNode.remove();
  							}*/
  						}
  
  						// Check if we exited a whitespace preserved element
  						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  							isInWhiteSpacePreservedElement = false;
  						}
  
  						// Handle empty nodes
  						if (elementRule.removeEmpty || elementRule.paddEmpty) {
  							if (node.isEmpty(nonEmptyElements)) {
  								if (elementRule.paddEmpty) {
  									node.empty().append(new Node('#text', '3')).value = '\u00a0';
  								} else {
  									// Leave nodes that have a name like <a name="name">
  									if (!node.attributes.map.name && !node.attributes.map.id) {
  										tempNode = node.parent;
  
  										if (blockElements[node.name]) {
  											node.empty().remove();
  										} else {
  											node.unwrap();
  										}
  
  										node = tempNode;
  										return;
  									}
  								}
  							}
  						}
  
  						node = node.parent;
  					}
  				}
  			}, schema);
  
  			rootNode = node = new Node(args.context || settings.root_name, 11);
  
  			parser.parse(html);
  
  			// Fix invalid children or report invalid children in a contextual parsing
  			if (validate && invalidChildren.length) {
  				if (!args.context) {
  					fixInvalidChildren(invalidChildren);
  				} else {
  					args.invalid = true;
  				}
  			}
  
  			// Wrap nodes in the root into block elements if the root is body
  			if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
  				addRootBlocks();
  			}
  
  			// Run filters only when the contents is valid
  			if (!args.invalid) {
  				// Run node filters
  				for (name in matchedNodes) {
  					list = nodeFilters[name];
  					nodes = matchedNodes[name];
  
  					// Remove already removed children
  					fi = nodes.length;
  					while (fi--) {
  						if (!nodes[fi].parent) {
  							nodes.splice(fi, 1);
  						}
  					}
  
  					for (i = 0, l = list.length; i < l; i++) {
  						list[i](nodes, name, args);
  					}
  				}
  
  				// Run attribute filters
  				for (i = 0, l = attributeFilters.length; i < l; i++) {
  					list = attributeFilters[i];
  
  					if (list.name in matchedAttributes) {
  						nodes = matchedAttributes[list.name];
  
  						// Remove already removed children
  						fi = nodes.length;
  						while (fi--) {
  							if (!nodes[fi].parent) {
  								nodes.splice(fi, 1);
  							}
  						}
  
  						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
  							list.callbacks[fi](nodes, list.name, args);
  						}
  					}
  				}
  			}
  
  			return rootNode;
  		};
  
  		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
  		// make it possible to place the caret inside empty blocks. This logic tries to remove
  		// these elements and keep br elements that where intended to be there intact
  		if (settings.remove_trailing_brs) {
  			self.addNodeFilter('br', function(nodes) {
  				var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
  				var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
  				var elementRule, textNode;
  
  				// Remove brs from body element as well
  				blockElements.body = 1;
  
  				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
  				for (i = 0; i < l; i++) {
  					node = nodes[i];
  					parent = node.parent;
  
  					if (blockElements[node.parent.name] && node === parent.lastChild) {
  						// Loop all nodes to the left of the current node and check for other BR elements
  						// excluding bookmarks since they are invisible
  						prev = node.prev;
  						while (prev) {
  							prevName = prev.name;
  
  							// Ignore bookmarks
  							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
  								// Found a non BR element
  								if (prevName !== "br") {
  									break;
  								}
  
  								// Found another br it's a <br><br> structure then don't remove anything
  								if (prevName === 'br') {
  									node = null;
  									break;
  								}
  							}
  
  							prev = prev.prev;
  						}
  
  						if (node) {
  							node.remove();
  
  							// Is the parent to be considered empty after we removed the BR
  							if (parent.isEmpty(nonEmptyElements)) {
  								elementRule = schema.getElementRule(parent.name);
  
  								// Remove or padd the element depending on schema rule
  								if (elementRule) {
  									if (elementRule.removeEmpty) {
  										parent.remove();
  									} else if (elementRule.paddEmpty) {
  										parent.empty().append(new Node('#text', 3)).value = '\u00a0';
  									}
  								}
  							}
  						}
  					} else {
  						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
  						// so they become <p><b><i>&nbsp;</i></b></p>
  						lastParent = node;
  						while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
  							lastParent = parent;
  
  							if (blockElements[parent.name]) {
  								break;
  							}
  
  							parent = parent.parent;
  						}
  
  						if (lastParent === parent) {
  							textNode = new Node('#text', 3);
  							textNode.value = '\u00a0';
  							node.replace(textNode);
  						}
  					}
  				}
  			});
  		}
  
  		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
  		if (!settings.allow_html_in_named_anchor) {
  			self.addAttributeFilter('id,name', function(nodes) {
  				var i = nodes.length, sibling, prevSibling, parent, node;
  
  				while (i--) {
  					node = nodes[i];
  					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
  						parent = node.parent;
  
  						// Move children after current node
  						sibling = node.lastChild;
  						do {
  							prevSibling = sibling.prev;
  							parent.insert(sibling, node);
  							sibling = prevSibling;
  						} while (sibling);
  					}
  				}
  			});
  		}
  
  		if (settings.validate && schema.getValidClasses()) {
  			self.addAttributeFilter('class', function(nodes) {
  				var i = nodes.length, node, classList, ci, className, classValue;
  				var validClasses = schema.getValidClasses(), validClassesMap, valid;
  
  				while (i--) {
  					node = nodes[i];
  					classList = node.attr('class').split(' ');
  					classValue = '';
  
  					for (ci = 0; ci < classList.length; ci++) {
  						className = classList[ci];
  						valid = false;
  
  						validClassesMap = validClasses['*'];
  						if (validClassesMap && validClassesMap[className]) {
  							valid = true;
  						}
  
  						validClassesMap = validClasses[node.name];
  						if (!valid && validClassesMap && validClassesMap[className]) {
  							valid = true;
  						}
  
  						if (valid) {
  							if (classValue) {
  								classValue += ' ';
  							}
  
  							classValue += className;
  						}
  					}
  
  					if (!classValue.length) {
  						classValue = null;
  					}
  
  					node.attr('class', classValue);
  				}
  			});
  		}
  	};
  });
  
  // Included from: js/maya/classes/html/Writer.js
  
  /**
   * Writer.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
   *
   * @class maya.html.Writer
   * @example
   * var writer = new maya.html.Writer({indent: true});
   * var parser = new maya.html.SaxParser(writer).parse('<p><br></p>');
   * console.log(writer.getContent());
   *
   * @class maya.html.Writer
   * @version 3.4
   */
  define("maya/html/Writer", [
  	"maya/html/Entities",
  	"maya/util/Tools"
  ], function(Entities, Tools) {
  	var makeMap = Tools.makeMap;
  
  	/**
  	 * Constructs a new Writer instance.
  	 *
  	 * @constructor
  	 * @method Writer
  	 * @param {Object} settings Name/value settings object.
  	 */
  	return function(settings) {
  		var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
  
  		settings = settings || {};
  		indent = settings.indent;
  		indentBefore = makeMap(settings.indent_before || '');
  		indentAfter = makeMap(settings.indent_after || '');
  		encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
  		htmlOutput = settings.element_format == "html";
  
  		return {
  			/**
  			 * Writes the a start element such as <p id="a">.
  			 *
  			 * @method start
  			 * @param {String} name Name of the element.
  			 * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
  			 * @param {Boolean} empty Optional empty state if the tag should end like <br />.
  			 */
  			start: function(name, attrs, empty) {
  				var i, l, attr, value;
  
  				if (indent && indentBefore[name] && html.length > 0) {
  					value = html[html.length - 1];
  
  					if (value.length > 0 && value !== '\n') {
  						html.push('\n');
  					}
  				}
  
  				html.push('<', name);
  
  				if (attrs) {
  					for (i = 0, l = attrs.length; i < l; i++) {
  						attr = attrs[i];
  						html.push(' ', attr.name, '="', encode(attr.value, true), '"');
  					}
  				}
  
  				if (!empty || htmlOutput) {
  					html[html.length] = '>';
  				} else {
  					html[html.length] = ' />';
  				}
  
  				if (empty && indent && indentAfter[name] && html.length > 0) {
  					value = html[html.length - 1];
  
  					if (value.length > 0 && value !== '\n') {
  						html.push('\n');
  					}
  				}
  			},
  
  			/**
  			 * Writes the a end element such as </p>.
  			 *
  			 * @method end
  			 * @param {String} name Name of the element.
  			 */
  			end: function(name) {
  				var value;
  
  				/*if (indent && indentBefore[name] && html.length > 0) {
  					value = html[html.length - 1];
  
  					if (value.length > 0 && value !== '\n')
  						html.push('\n');
  				}*/
  
  				html.push('</', name, '>');
  
  				if (indent && indentAfter[name] && html.length > 0) {
  					value = html[html.length - 1];
  
  					if (value.length > 0 && value !== '\n') {
  						html.push('\n');
  					}
  				}
  			},
  
  			/**
  			 * Writes a text node.
  			 *
  			 * @method text
  			 * @param {String} text String to write out.
  			 * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
  			 */
  			text: function(text, raw) {
  				if (text.length > 0) {
  					html[html.length] = raw ? text : encode(text);
  				}
  			},
  
  			/**
  			 * Writes a cdata node such as <![CDATA[data]]>.
  			 *
  			 * @method cdata
  			 * @param {String} text String to write out inside the cdata.
  			 */
  			cdata: function(text) {
  				html.push('<![CDATA[', text, ']]>');
  			},
  
  			/**
  			 * Writes a comment node such as <!-- Comment -->.
  			 *
  			 * @method cdata
  			 * @param {String} text String to write out inside the comment.
  			 */
  			comment: function(text) {
  				html.push('<!--', text, '-->');
  			},
  
  			/**
  			 * Writes a PI node such as <?xml attr="value" ?>.
  			 *
  			 * @method pi
  			 * @param {String} name Name of the pi.
  			 * @param {String} text String to write out inside the pi.
  			 */
  			pi: function(name, text) {
  				if (text) {
  					html.push('<?', name, ' ', text, '?>');
  				} else {
  					html.push('<?', name, '?>');
  				}
  
  				if (indent) {
  					html.push('\n');
  				}
  			},
  
  			/**
  			 * Writes a doctype node such as <!DOCTYPE data>.
  			 *
  			 * @method doctype
  			 * @param {String} text String to write out inside the doctype.
  			 */
  			doctype: function(text) {
  				html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
  			},
  
  			/**
  			 * Resets the internal buffer if one wants to reuse the writer.
  			 *
  			 * @method reset
  			 */
  			reset: function() {
  				html.length = 0;
  			},
  
  			/**
  			 * Returns the contents that got serialized.
  			 *
  			 * @method getContent
  			 * @return {String} HTML contents that got written down.
  			 */
  			getContent: function() {
  				return html.join('').replace(/\n$/, '');
  			}
  		};
  	};
  });
  
  // Included from: js/maya/classes/html/Serializer.js
  
  /**
   * Serializer.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is used to serialize down the DOM tree into a string using a Writer instance.
   *
   *
   * @example
   * new maya.html.Serializer().serialize(new maya.html.DomParser().parse('<p>text</p>'));
   * @class maya.html.Serializer
   * @version 3.4
   */
  define("maya/html/Serializer", [
  	"maya/html/Writer",
  	"maya/html/Schema"
  ], function(Writer, Schema) {
  	/**
  	 * Constructs a new Serializer instance.
  	 *
  	 * @constructor
  	 * @method Serializer
  	 * @param {Object} settings Name/value settings object.
  	 * @param {maya.html.Schema} schema Schema instance to use.
  	 */
  	return function(settings, schema) {
  		var self = this, writer = new Writer(settings);
  
  		settings = settings || {};
  		settings.validate = "validate" in settings ? settings.validate : true;
  
  		self.schema = schema = schema || new Schema();
  		self.writer = writer;
  
  		/**
  		 * Serializes the specified node into a string.
  		 *
  		 * @example
  		 * new maya.html.Serializer().serialize(new maya.html.DomParser().parse('<p>text</p>'));
  		 * @method serialize
  		 * @param {maya.html.Node} node Node instance to serialize.
  		 * @return {String} String with HTML based on DOM tree.
  		 */
  		self.serialize = function(node) {
  			var handlers, validate;
  
  			validate = settings.validate;
  
  			handlers = {
  				// #text
  				3: function(node) {
  					writer.text(node.value, node.raw);
  				},
  
  				// #comment
  				8: function(node) {
  					writer.comment(node.value);
  				},
  
  				// Processing instruction
  				7: function(node) {
  					writer.pi(node.name, node.value);
  				},
  
  				// Doctype
  				10: function(node) {
  					writer.doctype(node.value);
  				},
  
  				// CDATA
  				4: function(node) {
  					writer.cdata(node.value);
  				},
  
  				// Document fragment
  				11: function(node) {
  					if ((node = node.firstChild)) {
  						do {
  							walk(node);
  						} while ((node = node.next));
  					}
  				}
  			};
  
  			writer.reset();
  
  			function walk(node) {
  				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
  
  				if (!handler) {
  					name = node.name;
  					isEmpty = node.shortEnded;
  					attrs = node.attributes;
  
  					// Sort attributes
  					if (validate && attrs && attrs.length > 1) {
  						sortedAttrs = [];
  						sortedAttrs.map = {};
  
  						elementRule = schema.getElementRule(node.name);
  						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
  							attrName = elementRule.attributesOrder[i];
  
  							if (attrName in attrs.map) {
  								attrValue = attrs.map[attrName];
  								sortedAttrs.map[attrName] = attrValue;
  								sortedAttrs.push({name: attrName, value: attrValue});
  							}
  						}
  
  						for (i = 0, l = attrs.length; i < l; i++) {
  							attrName = attrs[i].name;
  
  							if (!(attrName in sortedAttrs.map)) {
  								attrValue = attrs.map[attrName];
  								sortedAttrs.map[attrName] = attrValue;
  								sortedAttrs.push({name: attrName, value: attrValue});
  							}
  						}
  
  						attrs = sortedAttrs;
  					}
  
  					writer.start(node.name, attrs, isEmpty);
  
  					if (!isEmpty) {
  						if ((node = node.firstChild)) {
  							do {
  								walk(node);
  							} while ((node = node.next));
  						}
  
  						writer.end(name);
  					}
  				} else {
  					handler(node);
  				}
  			}
  
  			// Serialize element and treat all non elements as fragments
  			if (node.type == 1 && !settings.inner) {
  				walk(node);
  			} else {
  				handlers[11](node);
  			}
  
  			return writer.getContent();
  		};
  	};
  });
  
  // Included from: js/maya/classes/dom/Serializer.js
  
  /**
   * Serializer.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is used to serialize DOM trees into a string. Consult the maya Wiki API for
   * more details and examples on how to use this class.
   *
   * @class maya.dom.Serializer
   */
  define("maya/dom/Serializer", [
  	"maya/dom/DOMUtils",
  	"maya/html/DomParser",
  	"maya/html/Entities",
  	"maya/html/Serializer",
  	"maya/html/Node",
  	"maya/html/Schema",
  	"maya/Env",
  	"maya/util/Tools"
  ], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) {
  	var each = Tools.each, trim = Tools.trim;
  	var DOM = DOMUtils.DOM;
  
  	/**
  	 * Constructs a new DOM serializer class.
  	 *
  	 * @constructor
  	 * @method Serializer
  	 * @param {Object} settings Serializer settings object.
  	 * @param {maya.Editor} editor Optional editor to bind events to and get schema/dom from.
  	 */
  	return function(settings, editor) {
  		var dom, schema, htmlParser;
  
  		if (editor) {
  			dom = editor.dom;
  			schema = editor.schema;
  		}
  
  		// Default DOM and Schema if they are undefined
  		dom = dom || DOM;
  		schema = schema || new Schema(settings);
  		settings.entity_encoding = settings.entity_encoding || 'named';
  		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
  
  		htmlParser = new DomParser(settings, schema);
  
  		// Convert tabindex back to elements when serializing contents
  		htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
  			var i = nodes.length, node;
  
  			while (i--) {
  				node = nodes[i];
  				node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
  				node.attr(name, null);
  			}
  		});
  
  		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
  		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
  			var i = nodes.length, node, value, internalName = 'data-mce-' + name;
  			var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
  
  			while (i--) {
  				node = nodes[i];
  
  				value = node.attributes.map[internalName];
  				if (value !== undef) {
  					// Set external name to internal value and remove internal
  					node.attr(name, value.length > 0 ? value : null);
  					node.attr(internalName, null);
  				} else {
  					// No internal attribute found then convert the value we have in the DOM
  					value = node.attributes.map[name];
  
  					if (name === "style") {
  						value = dom.serializeStyle(dom.parseStyle(value), node.name);
  					} else if (urlConverter) {
  						value = urlConverter.call(urlConverterScope, value, name, node.name);
  					}
  
  					node.attr(name, value.length > 0 ? value : null);
  				}
  			}
  		});
  
  		// Remove internal classes mceItem<..> or mceSelected
  		htmlParser.addAttributeFilter('class', function(nodes) {
  			var i = nodes.length, node, value;
  
  			while (i--) {
  				node = nodes[i];
  				value = node.attr('class');
  
  				if (value) {
  					value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
  					node.attr('class', value.length > 0 ? value : null);
  				}
  			}
  		});
  
  		// Remove bookmark elements
  		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
  			var i = nodes.length, node;
  
  			while (i--) {
  				node = nodes[i];
  
  				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
  					node.remove();
  				}
  			}
  		});
  
  		htmlParser.addNodeFilter('noscript', function(nodes) {
  			var i = nodes.length, node;
  
  			while (i--) {
  				node = nodes[i].firstChild;
  
  				if (node) {
  					node.value = Entities.decode(node.value);
  				}
  			}
  		});
  
  		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
  		htmlParser.addNodeFilter('script,style', function(nodes, name) {
  			var i = nodes.length, node, value, type;
  
  			function trim(value) {
  				/*jshint maxlen:255 */
  				/*eslint max-len:0 */
  				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
  						.replace(/^[\r\n]*|[\r\n]*$/g, '')
  						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
  						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
  			}
  
  			while (i--) {
  				node = nodes[i];
  				value = node.firstChild ? node.firstChild.value : '';
  
  				if (name === "script") {
  					// Remove mce- prefix from script elements and remove default type since the user specified
  					// a script element without type attribute
  					type = node.attr('type');
  					if (type) {
  						node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
  					}
  
  					if (value.length > 0) {
  						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
  					}
  				} else {
  					if (value.length > 0) {
  						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
  					}
  				}
  			}
  		});
  
  		// Convert comments to cdata and handle protected comments
  		htmlParser.addNodeFilter('#comment', function(nodes) {
  			var i = nodes.length, node;
  
  			while (i--) {
  				node = nodes[i];
  
  				if (node.value.indexOf('[CDATA[') === 0) {
  					node.name = '#cdata';
  					node.type = 4;
  					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
  				} else if (node.value.indexOf('mce:protected ') === 0) {
  					node.name = "#text";
  					node.type = 3;
  					node.raw = true;
  					node.value = unescape(node.value).substr(14);
  				}
  			}
  		});
  
  		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
  			var i = nodes.length, node;
  
  			while (i--) {
  				node = nodes[i];
  				if (node.type === 7) {
  					node.remove();
  				} else if (node.type === 1) {
  					if (name === "input" && !("type" in node.attributes.map)) {
  						node.attr('type', 'text');
  					}
  				}
  			}
  		});
  
  		// Fix list elements, TODO: Replace this later
  		if (settings.fix_list_elements) {
  			htmlParser.addNodeFilter('ul,ol', function(nodes) {
  				var i = nodes.length, node, parentNode;
  
  				while (i--) {
  					node = nodes[i];
  					parentNode = node.parent;
  
  					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
  						if (node.prev && node.prev.name === 'li') {
  							node.prev.append(node);
  						}
  					}
  				}
  			});
  		}
  
  		// Remove internal data attributes
  		htmlParser.addAttributeFilter(
  			'data-mce-src,data-mce-href,data-mce-style,' +
  			'data-mce-selected,data-mce-expando,' +
  			'data-mce-type,data-mce-resize',
  
  			function(nodes, name) {
  				var i = nodes.length;
  
  				while (i--) {
  					nodes[i].attr(name, null);
  				}
  			}
  		);
  
  		// Return public methods
  		return {
  			/**
  			 * Schema instance that was used to when the Serializer was constructed.
  			 *
  			 * @field {maya.html.Schema} schema
  			 */
  			schema: schema,
  
  			/**
  			 * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
  			 * and then execute the callback ones it has finished parsing the document.
  			 *
  			 * @example
  			 * parser.addNodeFilter('p,h1', function(nodes, name) {
  			 *		for (var i = 0; i < nodes.length; i++) {
  			 *			console.log(nodes[i].name);
  			 *		}
  			 * });
  			 * @method addNodeFilter
  			 * @method {String} name Comma separated list of nodes to collect.
  			 * @param {function} callback Callback function to execute once it has collected nodes.
  			 */
  			addNodeFilter: htmlParser.addNodeFilter,
  
  			/**
  			 * Adds a attribute filter function to the parser used by the serializer, the parser will
  			 * collect nodes that has the specified attributes
  			 * and then execute the callback ones it has finished parsing the document.
  			 *
  			 * @example
  			 * parser.addAttributeFilter('src,href', function(nodes, name) {
  			 *		for (var i = 0; i < nodes.length; i++) {
  			 *			console.log(nodes[i].name);
  			 *		}
  			 * });
  			 * @method addAttributeFilter
  			 * @method {String} name Comma separated list of nodes to collect.
  			 * @param {function} callback Callback function to execute once it has collected nodes.
  			 */
  			addAttributeFilter: htmlParser.addAttributeFilter,
  
  			/**
  			 * Serializes the specified browser DOM node into a HTML string.
  			 *
  			 * @method serialize
  			 * @param {DOMNode} node DOM node to serialize.
  			 * @param {Object} args Arguments option that gets passed to event handlers.
  			 */
  			serialize: function(node, args) {
  				var self = this, impl, doc, oldDoc, htmlSerializer, content;
  
  				// Explorer won't clone contents of script and style and the
  				// selected index of select elements are cleared on a clone operation.
  				if (Env.ie && dom.select('script,style,select,map').length > 0) {
  					content = node.innerHTML;
  					node = node.cloneNode(false);
  					dom.setHTML(node, content);
  				} else {
  					node = node.cloneNode(true);
  				}
  
  				// Nodes needs to be attached to something in WebKit/Opera
  				// This fix will make DOM ranges and make Sizzle happy!
  				impl = node.ownerDocument.implementation;
  				if (impl.createHTMLDocument) {
  					// Create an empty HTML document
  					doc = impl.createHTMLDocument("");
  
  					// Add the element or it's children if it's a body element to the new document
  					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
  						doc.body.appendChild(doc.importNode(node, true));
  					});
  
  					// Grab first child or body element for serialization
  					if (node.nodeName != 'BODY') {
  						node = doc.body.firstChild;
  					} else {
  						node = doc.body;
  					}
  
  					// set the new document in DOMUtils so createElement etc works
  					oldDoc = dom.doc;
  					dom.doc = doc;
  				}
  
  				args = args || {};
  				args.format = args.format || 'html';
  
  				// Don't wrap content if we want selected html
  				if (args.selection) {
  					args.forced_root_block = '';
  				}
  
  				// Pre process
  				if (!args.no_events) {
  					args.node = node;
  					self.onPreProcess(args);
  				}
  
  				// Setup serializer
  				htmlSerializer = new Serializer(settings, schema);
  
  				// Parse and serialize HTML
  				args.content = htmlSerializer.serialize(
  					htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
  				);
  
  				// Replace all BOM characters for now until we can find a better solution
  				if (!args.cleanup) {
  					args.content = args.content.replace(/\uFEFF/g, '');
  				}
  
  				// Post process
  				if (!args.no_events) {
  					self.onPostProcess(args);
  				}
  
  				// Restore the old document if it was changed
  				if (oldDoc) {
  					dom.doc = oldDoc;
  				}
  
  				args.node = null;
  
  				return args.content;
  			},
  
  			/**
  			 * Adds valid elements rules to the serializers schema instance this enables you to specify things
  			 * like what elements should be outputted and what attributes specific elements might have.
  			 * Consult the Wiki for more details on this format.
  			 *
  			 * @method addRules
  			 * @param {String} rules Valid elements rules string to add to schema.
  			 */
  			addRules: function(rules) {
  				schema.addValidElements(rules);
  			},
  
  			/**
  			 * Sets the valid elements rules to the serializers schema instance this enables you to specify things
  			 * like what elements should be outputted and what attributes specific elements might have.
  			 * Consult the Wiki for more details on this format.
  			 *
  			 * @method setRules
  			 * @param {String} rules Valid elements rules string.
  			 */
  			setRules: function(rules) {
  				schema.setValidElements(rules);
  			},
  
  			onPreProcess: function(args) {
  				if (editor) {
  					editor.fire('PreProcess', args);
  				}
  			},
  
  			onPostProcess: function(args) {
  				if (editor) {
  					editor.fire('PostProcess', args);
  				}
  			}
  		};
  	};
  });
  
  // Included from: js/maya/classes/dom/TridentSelection.js
  
  /**
   * TridentSelection.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Selection class for old explorer versions. This one fakes the
   * native selection object available on modern browsers.
   *
   * @class maya.dom.TridentSelection
   */
  define("maya/dom/TridentSelection", [], function() {
  	function Selection(selection) {
  		var self = this, dom = selection.dom, FALSE = false;
  
  		function getPosition(rng, start) {
  			var checkRng, startIndex = 0, endIndex, inside,
  				children, child, offset, index, position = -1, parent;
  
  			// Setup test range, collapse it and get the parent
  			checkRng = rng.duplicate();
  			checkRng.collapse(start);
  			parent = checkRng.parentElement();
  
  			// Check if the selection is within the right document
  			if (parent.ownerDocument !== selection.dom.doc) {
  				return;
  			}
  
  			// IE will report non editable elements as it's parent so look for an editable one
  			while (parent.contentEditable === "false") {
  				parent = parent.parentNode;
  			}
  
  			// If parent doesn't have any children then return that we are inside the element
  			if (!parent.hasChildNodes()) {
  				return {node: parent, inside: 1};
  			}
  
  			// Setup node list and endIndex
  			children = parent.children;
  			endIndex = children.length - 1;
  
  			// Perform a binary search for the position
  			while (startIndex <= endIndex) {
  				index = Math.floor((startIndex + endIndex) / 2);
  
  				// Move selection to node and compare the ranges
  				child = children[index];
  				checkRng.moveToElementText(child);
  				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
  
  				// Before/after or an exact match
  				if (position > 0) {
  					endIndex = index - 1;
  				} else if (position < 0) {
  					startIndex = index + 1;
  				} else {
  					return {node: child};
  				}
  			}
  
  			// Check if child position is before or we didn't find a position
  			if (position < 0) {
  				// No element child was found use the parent element and the offset inside that
  				if (!child) {
  					checkRng.moveToElementText(parent);
  					checkRng.collapse(true);
  					child = parent;
  					inside = true;
  				} else {
  					checkRng.collapse(false);
  				}
  
  				// Walk character by character in text node until we hit the selected range endpoint,
  				// hit the end of document or parent isn't the right one
  				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
  				offset = 0;
  				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
  						break;
  					}
  
  					offset++;
  				}
  			} else {
  				// Child position is after the selection endpoint
  				checkRng.collapse(true);
  
  				// Walk character by character in text node until we hit the selected range endpoint, hit
  				// the end of document or parent isn't the right one
  				offset = 0;
  				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
  						break;
  					}
  
  					offset++;
  				}
  			}
  
  			return {node: child, position: position, offset: offset, inside: inside};
  		}
  
  		// Returns a W3C DOM compatible range object by using the IE Range API
  		function getRange() {
  			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
  
  			// If selection is outside the current document just return an empty range
  			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
  			if (element.ownerDocument != dom.doc) {
  				return domRange;
  			}
  
  			collapsed = selection.isCollapsed();
  
  			// Handle control selection
  			if (ieRange.item) {
  				domRange.setStart(element.parentNode, dom.nodeIndex(element));
  				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
  
  				return domRange;
  			}
  
  			function findEndPoint(start) {
  				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
  
  				container = endPoint.node;
  				offset = endPoint.offset;
  
  				if (endPoint.inside && !container.hasChildNodes()) {
  					domRange[start ? 'setStart' : 'setEnd'](container, 0);
  					return;
  				}
  
  				if (offset === undef) {
  					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
  					return;
  				}
  
  				if (endPoint.position < 0) {
  					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
  
  					if (!sibling) {
  						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
  						return;
  					}
  
  					if (!offset) {
  						if (sibling.nodeType == 3) {
  							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
  						} else {
  							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
  						}
  
  						return;
  					}
  
  					// Find the text node and offset
  					while (sibling) {
  						if (sibling.nodeType == 3) {
  							nodeValue = sibling.nodeValue;
  							textNodeOffset += nodeValue.length;
  
  							// We are at or passed the position we where looking for
  							if (textNodeOffset >= offset) {
  								container = sibling;
  								textNodeOffset -= offset;
  								textNodeOffset = nodeValue.length - textNodeOffset;
  								break;
  							}
  						}
  
  						sibling = sibling.nextSibling;
  					}
  				} else {
  					// Find the text node and offset
  					sibling = container.previousSibling;
  
  					if (!sibling) {
  						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
  					}
  
  					// If there isn't any text to loop then use the first position
  					if (!offset) {
  						if (container.nodeType == 3) {
  							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
  						} else {
  							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
  						}
  
  						return;
  					}
  
  					while (sibling) {
  						if (sibling.nodeType == 3) {
  							textNodeOffset += sibling.nodeValue.length;
  
  							// We are at or passed the position we where looking for
  							if (textNodeOffset >= offset) {
  								container = sibling;
  								textNodeOffset -= offset;
  								break;
  							}
  						}
  
  						sibling = sibling.previousSibling;
  					}
  				}
  
  				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
  			}
  
  			try {
  				// Find start point
  				findEndPoint(true);
  
  				// Find end point if needed
  				if (!collapsed) {
  					findEndPoint();
  				}
  			} catch (ex) {
  				// IE has a nasty bug where text nodes might throw "invalid argument" when you
  				// access the nodeValue or other properties of text nodes. This seems to happend when
  				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
  				if (ex.number == -2147024809) {
  					// Get the current selection
  					bookmark = self.getBookmark(2);
  
  					// Get start element
  					tmpRange = ieRange.duplicate();
  					tmpRange.collapse(true);
  					element = tmpRange.parentElement();
  
  					// Get end element
  					if (!collapsed) {
  						tmpRange = ieRange.duplicate();
  						tmpRange.collapse(false);
  						element2 = tmpRange.parentElement();
  						element2.innerHTML = element2.innerHTML;
  					}
  
  					// Remove the broken elements
  					element.innerHTML = element.innerHTML;
  
  					// Restore the selection
  					self.moveToBookmark(bookmark);
  
  					// Since the range has moved we need to re-get it
  					ieRange = selection.getRng();
  
  					// Find start point
  					findEndPoint(true);
  
  					// Find end point if needed
  					if (!collapsed) {
  						findEndPoint();
  					}
  				} else {
  					throw ex; // Throw other errors
  				}
  			}
  
  			return domRange;
  		}
  
  		this.getBookmark = function(type) {
  			var rng = selection.getRng(), bookmark = {};
  
  			function getIndexes(node) {
  				var parent, root, children, i, indexes = [];
  
  				parent = node.parentNode;
  				root = dom.getRoot().parentNode;
  
  				while (parent != root && parent.nodeType !== 9) {
  					children = parent.children;
  
  					i = children.length;
  					while (i--) {
  						if (node === children[i]) {
  							indexes.push(i);
  							break;
  						}
  					}
  
  					node = parent;
  					parent = parent.parentNode;
  				}
  
  				return indexes;
  			}
  
  			function getBookmarkEndPoint(start) {
  				var position;
  
  				position = getPosition(rng, start);
  				if (position) {
  					return {
  						position: position.position,
  						offset: position.offset,
  						indexes: getIndexes(position.node),
  						inside: position.inside
  					};
  				}
  			}
  
  			// Non ubstructive bookmark
  			if (type === 2) {
  				// Handle text selection
  				if (!rng.item) {
  					bookmark.start = getBookmarkEndPoint(true);
  
  					if (!selection.isCollapsed()) {
  						bookmark.end = getBookmarkEndPoint();
  					}
  				} else {
  					bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
  				}
  			}
  
  			return bookmark;
  		};
  
  		this.moveToBookmark = function(bookmark) {
  			var rng, body = dom.doc.body;
  
  			function resolveIndexes(indexes) {
  				var node, i, idx, children;
  
  				node = dom.getRoot();
  				for (i = indexes.length - 1; i >= 0; i--) {
  					children = node.children;
  					idx = indexes[i];
  
  					if (idx <= children.length - 1) {
  						node = children[idx];
  					}
  				}
  
  				return node;
  			}
  
  			function setBookmarkEndPoint(start) {
  				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
  
  				if (endPoint) {
  					moveLeft = endPoint.position > 0;
  
  					moveRng = body.createTextRange();
  					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
  
  					offset = endPoint.offset;
  					if (offset !== undef) {
  						moveRng.collapse(endPoint.inside || moveLeft);
  						moveRng.moveStart('character', moveLeft ? -offset : offset);
  					} else {
  						moveRng.collapse(start);
  					}
  
  					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
  
  					if (start) {
  						rng.collapse(true);
  					}
  				}
  			}
  
  			if (bookmark.start) {
  				if (bookmark.start.ctrl) {
  					rng = body.createControlRange();
  					rng.addElement(resolveIndexes(bookmark.start.indexes));
  					rng.select();
  				} else {
  					rng = body.createTextRange();
  					setBookmarkEndPoint(true);
  					setBookmarkEndPoint();
  					rng.select();
  				}
  			}
  		};
  
  		this.addRange = function(rng) {
  			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
  				doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
  
  			function setEndPoint(start) {
  				var container, offset, marker, tmpRng, nodes;
  
  				marker = dom.create('a');
  				container = start ? startContainer : endContainer;
  				offset = start ? startOffset : endOffset;
  				tmpRng = ieRng.duplicate();
  
  				if (container == doc || container == doc.documentElement) {
  					container = body;
  					offset = 0;
  				}
  
  				if (container.nodeType == 3) {
  					container.parentNode.insertBefore(marker, container);
  					tmpRng.moveToElementText(marker);
  					tmpRng.moveStart('character', offset);
  					dom.remove(marker);
  					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  				} else {
  					nodes = container.childNodes;
  
  					if (nodes.length) {
  						if (offset >= nodes.length) {
  							dom.insertAfter(marker, nodes[nodes.length - 1]);
  						} else {
  							container.insertBefore(marker, nodes[offset]);
  						}
  
  						tmpRng.moveToElementText(marker);
  					} else if (container.canHaveHTML) {
  						// Empty node selection for example <div>|</div>
  						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
  						container.innerHTML = '<span>&#xFEFF;</span>';
  						marker = container.firstChild;
  						tmpRng.moveToElementText(marker);
  						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
  					}
  
  					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  					dom.remove(marker);
  				}
  			}
  
  			// Setup some shorter versions
  			startContainer = rng.startContainer;
  			startOffset = rng.startOffset;
  			endContainer = rng.endContainer;
  			endOffset = rng.endOffset;
  			ieRng = body.createTextRange();
  
  			// If single element selection then try making a control selection out of it
  			if (startContainer == endContainer && startContainer.nodeType == 1) {
  				// Trick to place the caret inside an empty block element like <p></p>
  				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
  					if (startContainer.canHaveHTML) {
  						// Check if previous sibling is an empty block if it is then we need to render it
  						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
  						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
  						sibling = startContainer.previousSibling;
  						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
  							sibling.innerHTML = '&#xFEFF;';
  						} else {
  							sibling = null;
  						}
  
  						startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
  						ieRng.moveToElementText(startContainer.lastChild);
  						ieRng.select();
  						dom.doc.selection.clear();
  						startContainer.innerHTML = '';
  
  						if (sibling) {
  							sibling.innerHTML = '';
  						}
  						return;
  					} else {
  						startOffset = dom.nodeIndex(startContainer);
  						startContainer = startContainer.parentNode;
  					}
  				}
  
  				if (startOffset == endOffset - 1) {
  					try {
  						ctrlElm = startContainer.childNodes[startOffset];
  						ctrlRng = body.createControlRange();
  						ctrlRng.addElement(ctrlElm);
  						ctrlRng.select();
  
  						// Check if the range produced is on the correct element and is a control range
  						// On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
  						nativeRng = selection.getRng();
  						if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
  							return;
  						}
  					} catch (ex) {
  						// Ignore
  					}
  				}
  			}
  
  			// Set start/end point of selection
  			setEndPoint(true);
  			setEndPoint();
  
  			// Select the new range and scroll it into view
  			ieRng.select();
  		};
  
  		// Expose range method
  		this.getRangeAt = getRange;
  	}
  
  	return Selection;
  });
  
  // Included from: js/maya/classes/util/VK.js
  
  /**
   * VK.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This file exposes a set of the common KeyCodes for use.  Please grow it as needed.
   */
  define("maya/util/VK", [
  	"maya/Env"
  ], function(Env) {
  	return {
  		BACKSPACE: 8,
  		DELETE: 46,
  		DOWN: 40,
  		ENTER: 13,
  		LEFT: 37,
  		RIGHT: 39,
  		SPACEBAR: 32,
  		TAB: 9,
  		UP: 38,
  
  		modifierPressed: function(e) {
  			return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e);
  		},
  
  		metaKeyPressed: function(e) {
  			// Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
  			return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
  		}
  	};
  });
  
  // Included from: js/maya/classes/dom/ControlSelection.js
  
  /**
   * ControlSelection.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles control selection of elements. Controls are elements
   * that can be resized and needs to be selected as a whole. It adds custom resize handles
   * to all browser engines that support properly disabling the built in resize logic.
   *
   * @class maya.dom.ControlSelection
   */
  define("maya/dom/ControlSelection", [
  	"maya/util/VK",
  	"maya/util/Tools",
  	"maya/Env"
  ], function(VK, Tools, Env) {
  	return function(selection, editor) {
  		var dom = editor.dom, each = Tools.each;
  		var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
  		var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
  		var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
  		var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;
  
  		// Details about each resize handle how to scale etc
  		resizeHandles = {
  			// Name: x multiplier, y multiplier, delta size x, delta size y
  			n: [0.5, 0, 0, -1],
  			e: [1, 0.5, 1, 0],
  			s: [0.5, 1, 0, 1],
  			w: [0, 0.5, -1, 0],
  			nw: [0, 0, -1, -1],
  			ne: [1, 0, 1, -1],
  			se: [1, 1, 1, 1],
  			sw: [0, 1, -1, 1]
  		};
  
  		// Add CSS for resize handles, cloned element and selected
  		var rootClass = '.mce-content-body';
  		editor.contentStyles.push(
  			rootClass + ' div.mce-resizehandle {' +
  				'position: absolute;' +
  				'border: 1px solid black;' +
  				'background: #FFF;' +
  				'width: 5px;' +
  				'height: 5px;' +
  				'z-index: 10000' +
  			'}' +
  			rootClass + ' .mce-resizehandle:hover {' +
  				'background: #000' +
  			'}' +
  			rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
  				'outline: 1px solid black;' +
  				'resize: none' + // Have been talks about implementing this in browsers
  			'}' +
  			rootClass + ' .mce-clonedresizable {' +
  				'position: absolute;' +
  				(Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
  				'opacity: .5;' +
  				'filter: alpha(opacity=50);' +
  				'z-index: 10000' +
  			'}' +
  			rootClass + ' .mce-resize-helper {' +
  				'background: #555;' +
  				'background: rgba(0,0,0,0.75);' +
  				'border-radius: 3px;' +
  				'border: 1px;' +
  				'color: white;' +
  				'display: none;' +
  				'font-family: sans-serif;' +
  				'font-size: 12px;' +
  				'white-space: nowrap;' +
  				'line-height: 14px;' +
  				'margin: 5px 10px;' +
  				'padding: 5px;' +
  				'position: absolute;' +
  				'z-index: 10001' +
  			'}'
  		);
  
  		function isResizable(elm) {
  			var selector = editor.settings.object_resizing;
  
  			if (selector === false || Env.iOS) {
  				return false;
  			}
  
  			if (typeof selector != 'string') {
  				selector = 'table,img,div';
  			}
  
  			if (elm.getAttribute('data-mce-resize') === 'false') {
  				return false;
  			}
  
  			return editor.dom.is(elm, selector);
  		}
  
  		function resizeGhostElement(e) {
  			var deltaX, deltaY, proportional;
  			var resizeHelperX, resizeHelperY;
  
  			// Calc new width/height
  			deltaX = e.screenX - startX;
  			deltaY = e.screenY - startY;
  
  			// Calc new size
  			width = deltaX * selectedHandle[2] + startW;
  			height = deltaY * selectedHandle[3] + startH;
  
  			// Never scale down lower than 5 pixels
  			width = width < 5 ? 5 : width;
  			height = height < 5 ? 5 : height;
  
  			if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
  				proportional = !VK.modifierPressed(e);
  			} else {
  				proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
  			}
  
  			// Constrain proportions
  			if (proportional) {
  				if (abs(deltaX) > abs(deltaY)) {
  					height = round(width * ratio);
  					width = round(height / ratio);
  				} else {
  					width = round(height / ratio);
  					height = round(width * ratio);
  				}
  			}
  
  			// Update ghost size
  			dom.setStyles(selectedElmGhost, {
  				width: width,
  				height: height
  			});
  
  			// Update resize helper position
  			resizeHelperX = selectedHandle.startPos.x + deltaX;
  			resizeHelperY = selectedHandle.startPos.y + deltaY;
  			resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
  			resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
  
  			dom.setStyles(resizeHelper, {
  				left: resizeHelperX,
  				top: resizeHelperY,
  				display: 'block'
  			});
  
  			resizeHelper.innerHTML = width + ' &times; ' + height;
  
  			// Update ghost X position if needed
  			if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
  				dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
  			}
  
  			// Update ghost Y position if needed
  			if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
  				dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
  			}
  
  			// Calculate how must overflow we got
  			deltaX = rootElement.scrollWidth - startScrollWidth;
  			deltaY = rootElement.scrollHeight - startScrollHeight;
  
  			// Re-position the resize helper based on the overflow
  			if (deltaX + deltaY !== 0) {
  				dom.setStyles(resizeHelper, {
  					left: resizeHelperX - deltaX,
  					top: resizeHelperY - deltaY
  				});
  			}
  
  			if (!resizeStarted) {
  				editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
  				resizeStarted = true;
  			}
  		}
  
  		function endGhostResize() {
  			resizeStarted = false;
  
  			function setSizeProp(name, value) {
  				if (value) {
  					// Resize by using style or attribute
  					if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
  						dom.setStyle(selectedElm, name, value);
  					} else {
  						dom.setAttrib(selectedElm, name, value);
  					}
  				}
  			}
  
  			// Set width/height properties
  			setSizeProp('width', width);
  			setSizeProp('height', height);
  
  			dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
  			dom.unbind(editableDoc, 'mouseup', endGhostResize);
  
  			if (rootDocument != editableDoc) {
  				dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
  				dom.unbind(rootDocument, 'mouseup', endGhostResize);
  			}
  
  			// Remove ghost/helper and update resize handle positions
  			dom.remove(selectedElmGhost);
  			dom.remove(resizeHelper);
  
  			if (!isIE || selectedElm.nodeName == "TABLE") {
  				showResizeRect(selectedElm);
  			}
  
  			editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
  			dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
  			editor.nodeChanged();
  		}
  
  		function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
  			var position, targetWidth, targetHeight, e, rect;
  
  			unbindResizeHandleEvents();
  
  			// Get position and size of target
  			position = dom.getPos(targetElm, rootElement);
  			selectedElmX = position.x;
  			selectedElmY = position.y;
  			rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
  			targetWidth = rect.width || (rect.right - rect.left);
  			targetHeight = rect.height || (rect.bottom - rect.top);
  
  			// Reset width/height if user selects a new image/table
  			if (selectedElm != targetElm) {
  				detachResizeStartListener();
  				selectedElm = targetElm;
  				width = height = 0;
  			}
  
  			// Makes it possible to disable resizing
  			e = editor.fire('ObjectSelected', {target: targetElm});
  
  			if (isResizable(targetElm) && !e.isDefaultPrevented()) {
  				each(resizeHandles, function(handle, name) {
  					var handleElm, handlerContainerElm;
  
  					function startDrag(e) {
  						startX = e.screenX;
  						startY = e.screenY;
  						startW = selectedElm.clientWidth;
  						startH = selectedElm.clientHeight;
  						ratio = startH / startW;
  						selectedHandle = handle;
  
  						handle.startPos = {
  							x: targetWidth * handle[0] + selectedElmX,
  							y: targetHeight * handle[1] + selectedElmY
  						};
  
  						startScrollWidth = rootElement.scrollWidth;
  						startScrollHeight = rootElement.scrollHeight;
  
  						selectedElmGhost = selectedElm.cloneNode(true);
  						dom.addClass(selectedElmGhost, 'mce-clonedresizable');
  						dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
  						selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
  						selectedElmGhost.unSelectabe = true;
  						dom.setStyles(selectedElmGhost, {
  							left: selectedElmX,
  							top: selectedElmY,
  							margin: 0
  						});
  
  						selectedElmGhost.removeAttribute('data-mce-selected');
  						rootElement.appendChild(selectedElmGhost);
  
  						dom.bind(editableDoc, 'mousemove', resizeGhostElement);
  						dom.bind(editableDoc, 'mouseup', endGhostResize);
  
  						if (rootDocument != editableDoc) {
  							dom.bind(rootDocument, 'mousemove', resizeGhostElement);
  							dom.bind(rootDocument, 'mouseup', endGhostResize);
  						}
  
  						resizeHelper = dom.add(rootElement, 'div', {
  							'class': 'mce-resize-helper',
  							'data-mce-bogus': 'all'
  						}, startW + ' &times; ' + startH);
  					}
  
  					if (mouseDownHandleName) {
  						// Drag started by IE native resizestart
  						if (name == mouseDownHandleName) {
  							startDrag(mouseDownEvent);
  						}
  
  						return;
  					}
  
  					// Get existing or render resize handle
  					handleElm = dom.get('mceResizeHandle' + name);
  					if (!handleElm) {
  						handlerContainerElm = rootElement;
  
  						handleElm = dom.add(handlerContainerElm, 'div', {
  							id: 'mceResizeHandle' + name,
  							'data-mce-bogus': 'all',
  							'class': 'mce-resizehandle',
  							unselectable: true,
  							style: 'cursor:' + name + '-resize; margin:0; padding:0'
  						});
  
  						// Hides IE move layer cursor
  						// If we set it on Chrome we get this wounderful bug: #6725
  						if (Env.ie) {
  							handleElm.contentEditable = false;
  						}
  					} else {
  						dom.show(handleElm);
  					}
  
  					if (!handle.elm) {
  						dom.bind(handleElm, 'mousedown', function(e) {
  							e.stopImmediatePropagation();
  							e.preventDefault();
  							startDrag(e);
  						});
  
  						handle.elm = handleElm;
  					}
  
  					// Position element
  					dom.setStyles(handleElm, {
  						left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
  						top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
  					});
  				});
  			} else {
  				hideResizeRect();
  			}
  
  			selectedElm.setAttribute('data-mce-selected', '1');
  		}
  
  		function hideResizeRect() {
  			var name, handleElm;
  
  			unbindResizeHandleEvents();
  
  			if (selectedElm) {
  				selectedElm.removeAttribute('data-mce-selected');
  			}
  
  			for (name in resizeHandles) {
  				handleElm = dom.get('mceResizeHandle' + name);
  				if (handleElm) {
  					dom.unbind(handleElm);
  					dom.remove(handleElm);
  				}
  			}
  		}
  
  		function updateResizeRect(e) {
  			var startElm, controlElm;
  
  			function isChildOrEqual(node, parent) {
  				if (node) {
  					do {
  						if (node === parent) {
  							return true;
  						}
  					} while ((node = node.parentNode));
  				}
  			}
  
  			// Ignore all events while resizing
  			if (resizeStarted) {
  				return;
  			}
  
  			// Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
  			each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
  				img.removeAttribute('data-mce-selected');
  			});
  
  			controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
  			controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
  
  			if (isChildOrEqual(controlElm, rootElement)) {
  				disableGeckoResize();
  				startElm = selection.getStart(true);
  
  				if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
  					if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) {
  						showResizeRect(controlElm);
  						return;
  					}
  				}
  			}
  
  			hideResizeRect();
  		}
  
  		function attachEvent(elm, name, func) {
  			if (elm && elm.attachEvent) {
  				elm.attachEvent('on' + name, func);
  			}
  		}
  
  		function detachEvent(elm, name, func) {
  			if (elm && elm.detachEvent) {
  				elm.detachEvent('on' + name, func);
  			}
  		}
  
  		function resizeNativeStart(e) {
  			var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
  
  			pos = target.getBoundingClientRect();
  			relativeX = lastMouseDownEvent.clientX - pos.left;
  			relativeY = lastMouseDownEvent.clientY - pos.top;
  
  			// Figure out what corner we are draging on
  			for (name in resizeHandles) {
  				corner = resizeHandles[name];
  
  				cornerX = target.offsetWidth * corner[0];
  				cornerY = target.offsetHeight * corner[1];
  
  				if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
  					selectedHandle = corner;
  					break;
  				}
  			}
  
  			// Remove native selection and let the magic begin
  			resizeStarted = true;
  			editor.fire('ObjectResizeStart', {
  				target: selectedElm,
  				width: selectedElm.clientWidth,
  				height: selectedElm.clientHeight
  			});
  			editor.getDoc().selection.empty();
  			showResizeRect(target, name, lastMouseDownEvent);
  		}
  
  		function nativeControlSelect(e) {
  			var target = e.srcElement;
  
  			if (target != selectedElm) {
  				editor.fire('ObjectSelected', {target: target});
  				detachResizeStartListener();
  
  				if (target.id.indexOf('mceResizeHandle') === 0) {
  					e.returnValue = false;
  					return;
  				}
  
  				if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
  					hideResizeRect();
  					selectedElm = target;
  					attachEvent(target, 'resizestart', resizeNativeStart);
  				}
  			}
  		}
  
  		function detachResizeStartListener() {
  			detachEvent(selectedElm, 'resizestart', resizeNativeStart);
  		}
  
  		function unbindResizeHandleEvents() {
  			for (var name in resizeHandles) {
  				var handle = resizeHandles[name];
  
  				if (handle.elm) {
  					dom.unbind(handle.elm);
  					delete handle.elm;
  				}
  			}
  		}
  
  		function disableGeckoResize() {
  			try {
  				// Disable object resizing on Gecko
  				editor.getDoc().execCommand('enableObjectResizing', false, false);
  			} catch (ex) {
  				// Ignore
  			}
  		}
  
  		function controlSelect(elm) {
  			var ctrlRng;
  
  			if (!isIE) {
  				return;
  			}
  
  			ctrlRng = editableDoc.body.createControlRange();
  
  			try {
  				ctrlRng.addElement(elm);
  				ctrlRng.select();
  				return true;
  			} catch (ex) {
  				// Ignore since the element can't be control selected for example a P tag
  			}
  		}
  
  		editor.on('init', function() {
  			if (isIE) {
  				// Hide the resize rect on resize and reselect the image
  				editor.on('ObjectResized', function(e) {
  					if (e.target.nodeName != 'TABLE') {
  						hideResizeRect();
  						controlSelect(e.target);
  					}
  				});
  
  				attachEvent(rootElement, 'controlselect', nativeControlSelect);
  
  				editor.on('mousedown', function(e) {
  					lastMouseDownEvent = e;
  				});
  			} else {
  				disableGeckoResize();
  
  				if (Env.ie >= 11) {
  					// TODO: Drag/drop doesn't work
  					editor.on('mouseup', function(e) {
  						var nodeName = e.target.nodeName;
  
  						if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) {
  							editor.selection.select(e.target, nodeName == 'TABLE');
  							editor.nodeChanged();
  						}
  					});
  
  					editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
  						if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
  							e.preventDefault();
  
  							// This moves the selection from being a control selection to a text like selection like in WebKit #6753
  							// TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
  							if (e.target.tagName == 'IMG') {
  								window.setTimeout(function() {
  									editor.selection.select(e.target);
  								}, 0);
  							}
  						}
  					});
  				}
  			}
  
  			editor.on('nodechange ResizeEditor', updateResizeRect);
  
  			// Update resize rect while typing in a table
  			editor.on('keydown keyup', function(e) {
  				if (selectedElm && selectedElm.nodeName == "TABLE") {
  					updateResizeRect(e);
  				}
  			});
  
  			editor.on('hide', hideResizeRect);
  
  			// Hide rect on focusout since it would float on top of windows otherwise
  			//editor.on('focusout', hideResizeRect);
  		});
  
  		editor.on('remove', unbindResizeHandleEvents);
  
  		function destroy() {
  			selectedElm = selectedElmGhost = null;
  
  			if (isIE) {
  				detachResizeStartListener();
  				detachEvent(rootElement, 'controlselect', nativeControlSelect);
  			}
  		}
  
  		return {
  			isResizable: isResizable,
  			showResizeRect: showResizeRect,
  			hideResizeRect: hideResizeRect,
  			updateResizeRect: updateResizeRect,
  			controlSelect: controlSelect,
  			destroy: destroy
  		};
  	};
  });
  
  // Included from: js/maya/classes/dom/BookmarkManager.js
  
  /**
   * BookmarkManager.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles selection bookmarks.
   *
   * @class maya.dom.BookmarkManager
   */
  define("maya/dom/BookmarkManager", [
  	"maya/Env",
  	"maya/util/Tools"
  ], function(Env, Tools) {
  	/**
  	 * Constructs a new BookmarkManager instance for a specific selection instance.
  	 *
  	 * @constructor
  	 * @method BookmarkManager
  	 * @param {maya.dom.Selection} selection Selection instance to handle bookmarks for.
  	 */
  	function BookmarkManager(selection) {
  		var dom = selection.dom;
  
  		/**
  		 * Returns a bookmark location for the current selection. This bookmark object
  		 * can then be used to restore the selection after some content modification to the document.
  		 *
  		 * @method getBookmark
  		 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
  		 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
  		 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
  		 * @example
  		 * // Stores a bookmark of the current selection
  		 * var bm = maya.activeEditor.selection.getBookmark();
  		 *
  		 * maya.activeEditor.setContent(maya.activeEditor.getContent() + 'Some new content');
  		 *
  		 * // Restore the selection bookmark
  		 * maya.activeEditor.selection.moveToBookmark(bm);
  		 */
  		this.getBookmark = function(type, normalized) {
  			var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
  
  			function findIndex(name, element) {
  				var index = 0;
  
  				Tools.each(dom.select(name), function(node, i) {
  					if (node == element) {
  						index = i;
  					}
  				});
  
  				return index;
  			}
  
  			function normalizeTableCellSelection(rng) {
  				function moveEndPoint(start) {
  					var container, offset, childNodes, prefix = start ? 'start' : 'end';
  
  					container = rng[prefix + 'Container'];
  					offset = rng[prefix + 'Offset'];
  
  					if (container.nodeType == 1 && container.nodeName == "TR") {
  						childNodes = container.childNodes;
  						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
  						if (container) {
  							offset = start ? 0 : container.childNodes.length;
  							rng['set' + (start ? 'Start' : 'End')](container, offset);
  						}
  					}
  				}
  
  				moveEndPoint(true);
  				moveEndPoint();
  
  				return rng;
  			}
  
  			function getLocation() {
  				var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
  
  				function getPoint(rng, start) {
  					var container = rng[start ? 'startContainer' : 'endContainer'],
  						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
  
  					if (container.nodeType == 3) {
  						if (normalized) {
  							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
  								offset += node.nodeValue.length;
  							}
  						}
  
  						point.push(offset);
  					} else {
  						childNodes = container.childNodes;
  
  						if (offset >= childNodes.length && childNodes.length) {
  							after = 1;
  							offset = Math.max(0, childNodes.length - 1);
  						}
  
  						point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
  					}
  
  					for (; container && container != root; container = container.parentNode) {
  						point.push(dom.nodeIndex(container, normalized));
  					}
  
  					return point;
  				}
  
  				bookmark.start = getPoint(rng, true);
  
  				if (!selection.isCollapsed()) {
  					bookmark.end = getPoint(rng);
  				}
  
  				return bookmark;
  			}
  
  			if (type == 2) {
  				element = selection.getNode();
  				name = element ? element.nodeName : null;
  
  				if (name == 'IMG') {
  					return {name: name, index: findIndex(name, element)};
  				}
  
  				if (selection.tridentSel) {
  					return selection.tridentSel.getBookmark(type);
  				}
  
  				return getLocation();
  			}
  
  			// Handle simple range
  			if (type) {
  				return {rng: selection.getRng()};
  			}
  
  			rng = selection.getRng();
  			id = dom.uniqueId();
  			collapsed = selection.isCollapsed();
  			styles = 'overflow:hidden;line-height:0px';
  
  			// Explorer method
  			if (rng.duplicate || rng.item) {
  				// Text selection
  				if (!rng.item) {
  					rng2 = rng.duplicate();
  
  					try {
  						// Insert start marker
  						rng.collapse();
  						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
  
  						// Insert end marker
  						if (!collapsed) {
  							rng2.collapse(false);
  
  							// Detect the empty space after block elements in IE and move the
  							// end back one character <p></p>] becomes <p>]</p>
  							rng.moveToElementText(rng2.parentElement());
  							if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
  								rng2.move('character', -1);
  							}
  
  							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
  						}
  					} catch (ex) {
  						// IE might throw unspecified error so lets ignore it
  						return null;
  					}
  				} else {
  					// Control selection
  					element = rng.item(0);
  					name = element.nodeName;
  
  					return {name: name, index: findIndex(name, element)};
  				}
  			} else {
  				element = selection.getNode();
  				name = element.nodeName;
  				if (name == 'IMG') {
  					return {name: name, index: findIndex(name, element)};
  				}
  
  				// W3C method
  				rng2 = normalizeTableCellSelection(rng.cloneRange());
  
  				// Insert end marker
  				if (!collapsed) {
  					rng2.collapse(false);
  					rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
  				}
  
  				rng = normalizeTableCellSelection(rng);
  				rng.collapse(true);
  				rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
  			}
  
  			selection.moveToBookmark({id: id, keep: 1});
  
  			return {id: id};
  		};
  
  		/**
  		 * Restores the selection to the specified bookmark.
  		 *
  		 * @method moveToBookmark
  		 * @param {Object} bookmark Bookmark to restore selection from.
  		 * @return {Boolean} true/false if it was successful or not.
  		 * @example
  		 * // Stores a bookmark of the current selection
  		 * var bm = maya.activeEditor.selection.getBookmark();
  		 *
  		 * maya.activeEditor.setContent(maya.activeEditor.getContent() + 'Some new content');
  		 *
  		 * // Restore the selection bookmark
  		 * maya.activeEditor.selection.moveToBookmark(bm);
  		 */
  		this.moveToBookmark = function(bookmark) {
  			var rng, root, startContainer, endContainer, startOffset, endOffset;
  
  			function setEndPoint(start) {
  				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
  
  				if (point) {
  					offset = point[0];
  
  					// Find container node
  					for (node = root, i = point.length - 1; i >= 1; i--) {
  						children = node.childNodes;
  
  						if (point[i] > children.length - 1) {
  							return;
  						}
  
  						node = children[point[i]];
  					}
  
  					// Move text offset to best suitable location
  					if (node.nodeType === 3) {
  						offset = Math.min(point[0], node.nodeValue.length);
  					}
  
  					// Move element offset to best suitable location
  					if (node.nodeType === 1) {
  						offset = Math.min(point[0], node.childNodes.length);
  					}
  
  					// Set offset within container node
  					if (start) {
  						rng.setStart(node, offset);
  					} else {
  						rng.setEnd(node, offset);
  					}
  				}
  
  				return true;
  			}
  
  			function restoreEndPoint(suffix) {
  				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
  
  				if (marker) {
  					node = marker.parentNode;
  
  					if (suffix == 'start') {
  						if (!keep) {
  							idx = dom.nodeIndex(marker);
  						} else {
  							node = marker.firstChild;
  							idx = 1;
  						}
  
  						startContainer = endContainer = node;
  						startOffset = endOffset = idx;
  					} else {
  						if (!keep) {
  							idx = dom.nodeIndex(marker);
  						} else {
  							node = marker.firstChild;
  							idx = 1;
  						}
  
  						endContainer = node;
  						endOffset = idx;
  					}
  
  					if (!keep) {
  						prev = marker.previousSibling;
  						next = marker.nextSibling;
  
  						// Remove all marker text nodes
  						Tools.each(Tools.grep(marker.childNodes), function(node) {
  							if (node.nodeType == 3) {
  								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
  							}
  						});
  
  						// Remove marker but keep children if for example contents where inserted into the marker
  						// Also remove duplicated instances of the marker for example by a
  						// split operation or by WebKit auto split on paste feature
  						while ((marker = dom.get(bookmark.id + '_' + suffix))) {
  							dom.remove(marker, 1);
  						}
  
  						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
  						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
  						// isn't worth the effort. Sorry, Opera but it's just a fact
  						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
  							idx = prev.nodeValue.length;
  							prev.appendData(next.nodeValue);
  							dom.remove(next);
  
  							if (suffix == 'start') {
  								startContainer = endContainer = prev;
  								startOffset = endOffset = idx;
  							} else {
  								endContainer = prev;
  								endOffset = idx;
  							}
  						}
  					}
  				}
  			}
  
  			function addBogus(node) {
  				// Adds a bogus BR element for empty block elements
  				if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
  					node.innerHTML = '<br data-mce-bogus="1" />';
  				}
  
  				return node;
  			}
  
  			if (bookmark) {
  				if (bookmark.start) {
  					rng = dom.createRng();
  					root = dom.getRoot();
  
  					if (selection.tridentSel) {
  						return selection.tridentSel.moveToBookmark(bookmark);
  					}
  
  					if (setEndPoint(true) && setEndPoint()) {
  						selection.setRng(rng);
  					}
  				} else if (bookmark.id) {
  					// Restore start/end points
  					restoreEndPoint('start');
  					restoreEndPoint('end');
  
  					if (startContainer) {
  						rng = dom.createRng();
  						rng.setStart(addBogus(startContainer), startOffset);
  						rng.setEnd(addBogus(endContainer), endOffset);
  						selection.setRng(rng);
  					}
  				} else if (bookmark.name) {
  					selection.select(dom.select(bookmark.name)[bookmark.index]);
  				} else if (bookmark.rng) {
  					selection.setRng(bookmark.rng);
  				}
  			}
  		};
  	}
  
  	/**
  	 * Returns true/false if the specified node is a bookmark node or not.
  	 *
  	 * @static
  	 * @method isBookmarkNode
  	 * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
  	 * @return {Boolean} true/false if the node is a bookmark node or not.
  	 */
  	BookmarkManager.isBookmarkNode = function(node) {
  		return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
  	};
  
  	return BookmarkManager;
  });
  
  // Included from: js/maya/classes/dom/Selection.js
  
  /**
   * Selection.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles text and control selection it's an crossbrowser utility class.
   * Consult the maya Wiki API for more details and examples on how to use this class.
   *
   * @class maya.dom.Selection
   * @example
   * // Getting the currently selected node for the active editor
   * alert(maya.activeEditor.selection.getNode().nodeName);
   */
  define("maya/dom/Selection", [
  	"maya/dom/TreeWalker",
  	"maya/dom/TridentSelection",
  	"maya/dom/ControlSelection",
  	"maya/dom/RangeUtils",
  	"maya/dom/BookmarkManager",
  	"maya/Env",
  	"maya/util/Tools"
  ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) {
  	var each = Tools.each, trim = Tools.trim;
  	var isIE = Env.ie;
  
  	/**
  	 * Constructs a new selection instance.
  	 *
  	 * @constructor
  	 * @method Selection
  	 * @param {maya.dom.DOMUtils} dom DOMUtils object reference.
  	 * @param {Window} win Window to bind the selection object to.
  	 * @param {maya.dom.Serializer} serializer DOM serialization class to use for getContent.
  	 */
  	function Selection(dom, win, serializer, editor) {
  		var self = this;
  
  		self.dom = dom;
  		self.win = win;
  		self.serializer = serializer;
  		self.editor = editor;
  		self.bookmarkManager = new BookmarkManager(self);
  		self.controlSelection = new ControlSelection(self, editor);
  
  		// No W3C Range support
  		if (!self.win.getSelection) {
  			self.tridentSel = new TridentSelection(self);
  		}
  	}
  
  	Selection.prototype = {
  		/**
  		 * Move the selection cursor range to the specified node and offset.
  		 * If there is no node specified it will move it to the first suitable location within the body.
  		 *
  		 * @method setCursorLocation
  		 * @param {Node} node Optional node to put the cursor in.
  		 * @param {Number} offset Optional offset from the start of the node to put the cursor at.
  		 */
  		setCursorLocation: function(node, offset) {
  			var self = this, rng = self.dom.createRng();
  
  			if (!node) {
  				self._moveEndPoint(rng, self.editor.getBody(), true);
  				self.setRng(rng);
  			} else {
  				rng.setStart(node, offset);
  				rng.setEnd(node, offset);
  				self.setRng(rng);
  				self.collapse(false);
  			}
  		},
  
  		/**
  		 * Returns the selected contents using the DOM serializer passed in to this class.
  		 *
  		 * @method getContent
  		 * @param {Object} s Optional settings class with for example output format text or html.
  		 * @return {String} Selected contents in for example HTML format.
  		 * @example
  		 * // Alerts the currently selected contents
  		 * alert(maya.activeEditor.selection.getContent());
  		 *
  		 * // Alerts the currently selected contents as plain text
  		 * alert(maya.activeEditor.selection.getContent({format: 'text'}));
  		 */
  		getContent: function(args) {
  			var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
  			var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
  
  			args = args || {};
  			whiteSpaceBefore = whiteSpaceAfter = '';
  			args.get = true;
  			args.format = args.format || 'html';
  			args.selection = true;
  			self.editor.fire('BeforeGetContent', args);
  
  			if (args.format == 'text') {
  				return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
  			}
  
  			if (rng.cloneContents) {
  				fragment = rng.cloneContents();
  
  				if (fragment) {
  					tmpElm.appendChild(fragment);
  				}
  			} else if (rng.item !== undefined || rng.htmlText !== undefined) {
  				// IE will produce invalid markup if elements are present that
  				// it doesn't understand like custom elements or HTML5 elements.
  				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
  				tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
  				tmpElm.removeChild(tmpElm.firstChild);
  			} else {
  				tmpElm.innerHTML = rng.toString();
  			}
  
  			// Keep whitespace before and after
  			if (/^\s/.test(tmpElm.innerHTML)) {
  				whiteSpaceBefore = ' ';
  			}
  
  			if (/\s+$/.test(tmpElm.innerHTML)) {
  				whiteSpaceAfter = ' ';
  			}
  
  			args.getInner = true;
  
  			args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
  			self.editor.fire('GetContent', args);
  
  			return args.content;
  		},
  
  		/**
  		 * Sets the current selection to the specified content. If any contents is selected it will be replaced
  		 * with the contents passed in to this function. If there is no selection the contents will be inserted
  		 * where the caret is placed in the editor/page.
  		 *
  		 * @method setContent
  		 * @param {String} content HTML contents to set could also be other formats depending on settings.
  		 * @param {Object} args Optional settings object with for example data format.
  		 * @example
  		 * // Inserts some HTML contents at the current selection
  		 * maya.activeEditor.selection.setContent('<strong>Some contents</strong>');
  		 */
  		setContent: function(content, args) {
  			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
  
  			args = args || {format: 'html'};
  			args.set = true;
  			args.selection = true;
  			content = args.content = content;
  
  			// Dispatch before set content event
  			if (!args.no_events) {
  				self.editor.fire('BeforeSetContent', args);
  			}
  
  			content = args.content;
  
  			if (rng.insertNode) {
  				// Make caret marker since insertNode places the caret in the beginning of text after insert
  				content += '<span id="__caret">_</span>';
  
  				// Delete and insert new node
  				if (rng.startContainer == doc && rng.endContainer == doc) {
  					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
  					doc.body.innerHTML = content;
  				} else {
  					rng.deleteContents();
  
  					if (doc.body.childNodes.length === 0) {
  						doc.body.innerHTML = content;
  					} else {
  						// createContextualFragment doesn't exists in IE 9 DOMRanges
  						if (rng.createContextualFragment) {
  							rng.insertNode(rng.createContextualFragment(content));
  						} else {
  							// Fake createContextualFragment call in IE 9
  							frag = doc.createDocumentFragment();
  							temp = doc.createElement('div');
  
  							frag.appendChild(temp);
  							temp.outerHTML = content;
  
  							rng.insertNode(frag);
  						}
  					}
  				}
  
  				// Move to caret marker
  				caretNode = self.dom.get('__caret');
  
  				// Make sure we wrap it compleatly, Opera fails with a simple select call
  				rng = doc.createRange();
  				rng.setStartBefore(caretNode);
  				rng.setEndBefore(caretNode);
  				self.setRng(rng);
  
  				// Remove the caret position
  				self.dom.remove('__caret');
  
  				try {
  					self.setRng(rng);
  				} catch (ex) {
  					// Might fail on Opera for some odd reason
  				}
  			} else {
  				if (rng.item) {
  					// Delete content and get caret text selection
  					doc.execCommand('Delete', false, null);
  					rng = self.getRng();
  				}
  
  				// Explorer removes spaces from the beginning of pasted contents
  				if (/^\s+/.test(content)) {
  					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
  					self.dom.remove('__mce_tmp');
  				} else {
  					rng.pasteHTML(content);
  				}
  			}
  
  			// Dispatch set content event
  			if (!args.no_events) {
  				self.editor.fire('SetContent', args);
  			}
  		},
  
  		/**
  		 * Returns the start element of a selection range. If the start is in a text
  		 * node the parent element will be returned.
  		 *
  		 * @method getStart
  		 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
  		 * @return {Element} Start element of selection range.
  		 */
  		getStart: function(real) {
  			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
  
  			if (rng.duplicate || rng.item) {
  				// Control selection, return first item
  				if (rng.item) {
  					return rng.item(0);
  				}
  
  				// Get start element
  				checkRng = rng.duplicate();
  				checkRng.collapse(1);
  				startElement = checkRng.parentElement();
  				if (startElement.ownerDocument !== self.dom.doc) {
  					startElement = self.dom.getRoot();
  				}
  
  				// Check if range parent is inside the start element, then return the inner parent element
  				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
  				parentElement = node = rng.parentElement();
  				while ((node = node.parentNode)) {
  					if (node == startElement) {
  						startElement = parentElement;
  						break;
  					}
  				}
  
  				return startElement;
  			} else {
  				startElement = rng.startContainer;
  
  				if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
  					if (!real || !rng.collapsed) {
  						startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
  					}
  				}
  
  				if (startElement && startElement.nodeType == 3) {
  					return startElement.parentNode;
  				}
  
  				return startElement;
  			}
  		},
  
  		/**
  		 * Returns the end element of a selection range. If the end is in a text
  		 * node the parent element will be returned.
  		 *
  		 * @method getEnd
  		 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
  		 * @return {Element} End element of selection range.
  		 */
  		getEnd: function(real) {
  			var self = this, rng = self.getRng(), endElement, endOffset;
  
  			if (rng.duplicate || rng.item) {
  				if (rng.item) {
  					return rng.item(0);
  				}
  
  				rng = rng.duplicate();
  				rng.collapse(0);
  				endElement = rng.parentElement();
  				if (endElement.ownerDocument !== self.dom.doc) {
  					endElement = self.dom.getRoot();
  				}
  
  				if (endElement && endElement.nodeName == 'BODY') {
  					return endElement.lastChild || endElement;
  				}
  
  				return endElement;
  			} else {
  				endElement = rng.endContainer;
  				endOffset = rng.endOffset;
  
  				if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
  					if (!real || !rng.collapsed) {
  						endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
  					}
  				}
  
  				if (endElement && endElement.nodeType == 3) {
  					return endElement.parentNode;
  				}
  
  				return endElement;
  			}
  		},
  
  		/**
  		 * Returns a bookmark location for the current selection. This bookmark object
  		 * can then be used to restore the selection after some content modification to the document.
  		 *
  		 * @method getBookmark
  		 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
  		 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
  		 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
  		 * @example
  		 * // Stores a bookmark of the current selection
  		 * var bm = maya.activeEditor.selection.getBookmark();
  		 *
  		 * maya.activeEditor.setContent(maya.activeEditor.getContent() + 'Some new content');
  		 *
  		 * // Restore the selection bookmark
  		 * maya.activeEditor.selection.moveToBookmark(bm);
  		 */
  		getBookmark: function(type, normalized) {
  			return this.bookmarkManager.getBookmark(type, normalized);
  		},
  
  		/**
  		 * Restores the selection to the specified bookmark.
  		 *
  		 * @method moveToBookmark
  		 * @param {Object} bookmark Bookmark to restore selection from.
  		 * @return {Boolean} true/false if it was successful or not.
  		 * @example
  		 * // Stores a bookmark of the current selection
  		 * var bm = maya.activeEditor.selection.getBookmark();
  		 *
  		 * maya.activeEditor.setContent(maya.activeEditor.getContent() + 'Some new content');
  		 *
  		 * // Restore the selection bookmark
  		 * maya.activeEditor.selection.moveToBookmark(bm);
  		 */
  		moveToBookmark: function(bookmark) {
  			return this.bookmarkManager.moveToBookmark(bookmark);
  		},
  
  		/**
  		 * Selects the specified element. This will place the start and end of the selection range around the element.
  		 *
  		 * @method select
  		 * @param {Element} node HMTL DOM element to select.
  		 * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
  		 * @return {Element} Selected element the same element as the one that got passed in.
  		 * @example
  		 * // Select the first paragraph in the active editor
  		 * maya.activeEditor.selection.select(maya.activeEditor.dom.select('p')[0]);
  		 */
  		select: function(node, content) {
  			var self = this, dom = self.dom, rng = dom.createRng(), idx;
  
  			// Clear stored range set by FocusManager
  			self.lastFocusBookmark = null;
  
  			if (node) {
  				if (!content && self.controlSelection.controlSelect(node)) {
  					return;
  				}
  
  				idx = dom.nodeIndex(node);
  				rng.setStart(node.parentNode, idx);
  				rng.setEnd(node.parentNode, idx + 1);
  
  				// Find first/last text node or BR element
  				if (content) {
  					self._moveEndPoint(rng, node, true);
  					self._moveEndPoint(rng, node);
  				}
  
  				self.setRng(rng);
  			}
  
  			return node;
  		},
  
  		/**
  		 * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
  		 *
  		 * @method isCollapsed
  		 * @return {Boolean} true/false state if the selection range is collapsed or not.
  		 * Collapsed means if it's a caret or a larger selection.
  		 */
  		isCollapsed: function() {
  			var self = this, rng = self.getRng(), sel = self.getSel();
  
  			if (!rng || rng.item) {
  				return false;
  			}
  
  			if (rng.compareEndPoints) {
  				return rng.compareEndPoints('StartToEnd', rng) === 0;
  			}
  
  			return !sel || rng.collapsed;
  		},
  
  		/**
  		 * Collapse the selection to start or end of range.
  		 *
  		 * @method collapse
  		 * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to start.
  		 */
  		collapse: function(toStart) {
  			var self = this, rng = self.getRng(), node;
  
  			// Control range on IE
  			if (rng.item) {
  				node = rng.item(0);
  				rng = self.win.document.body.createTextRange();
  				rng.moveToElementText(node);
  			}
  
  			rng.collapse(!!toStart);
  			self.setRng(rng);
  		},
  
  		/**
  		 * Returns the browsers internal selection object.
  		 *
  		 * @method getSel
  		 * @return {Selection} Internal browser selection object.
  		 */
  		getSel: function() {
  			var win = this.win;
  
  			return win.getSelection ? win.getSelection() : win.document.selection;
  		},
  
  		/**
  		 * Returns the browsers internal range object.
  		 *
  		 * @method getRng
  		 * @param {Boolean} w3c Forces a compatible W3C range on IE.
  		 * @return {Range} Internal browser range object.
  		 * @see http://www.quirksmode.org/dom/range_intro.html
  		 * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
  		 */
  		getRng: function(w3c) {
  			var self = this, selection, rng, elm, doc = self.win.document, ieRng;
  
  			function tryCompareBoundaryPoints(how, sourceRange, destinationRange) {
  				try {
  					return sourceRange.compareBoundaryPoints(how, destinationRange);
  				} catch (ex) {
  					// Gecko throws wrong document exception if the range points
  					// to nodes that where removed from the dom #6690
  					// Browsers should mutate existing DOMRange instances so that they always point
  					// to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
  					// For performance reasons just return -1
  					return -1;
  				}
  			}
  
  			// Use last rng passed from FocusManager if it's available this enables
  			// calls to editor.selection.getStart() to work when caret focus is lost on IE
  			if (!w3c && self.lastFocusBookmark) {
  				var bookmark = self.lastFocusBookmark;
  
  				// Convert bookmark to range IE 11 fix
  				if (bookmark.startContainer) {
  					rng = doc.createRange();
  					rng.setStart(bookmark.startContainer, bookmark.startOffset);
  					rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  				} else {
  					rng = bookmark;
  				}
  
  				return rng;
  			}
  
  			// Found tridentSel object then we need to use that one
  			if (w3c && self.tridentSel) {
  				return self.tridentSel.getRangeAt(0);
  			}
  
  			try {
  				if ((selection = self.getSel())) {
  					if (selection.rangeCount > 0) {
  						rng = selection.getRangeAt(0);
  					} else {
  						rng = selection.createRange ? selection.createRange() : doc.createRange();
  					}
  				}
  			} catch (ex) {
  				// IE throws unspecified error here if maya is placed in a frame/iframe
  			}
  
  			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
  			// IE 11 doesn't support the selection object so we check for that as well
  			if (isIE && rng && rng.setStart && doc.selection) {
  				try {
  					// IE will sometimes throw an exception here
  					ieRng = doc.selection.createRange();
  				} catch (ex) {
  
  				}
  
  				if (ieRng && ieRng.item) {
  					elm = ieRng.item(0);
  					rng = doc.createRange();
  					rng.setStartBefore(elm);
  					rng.setEndAfter(elm);
  				}
  			}
  
  			// No range found then create an empty one
  			// This can occur when the editor is placed in a hidden container element on Gecko
  			// Or on IE when there was an exception
  			if (!rng) {
  				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
  			}
  
  			// If range is at start of document then move it to start of body
  			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
  				elm = self.dom.getRoot();
  				rng.setStart(elm, 0);
  				rng.setEnd(elm, 0);
  			}
  
  			if (self.selectedRange && self.explicitRange) {
  				if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
  					tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
  					// Safari, Opera and Chrome only ever select text which causes the range to change.
  					// This lets us use the originally set range if the selection hasn't been changed by the user.
  					rng = self.explicitRange;
  				} else {
  					self.selectedRange = null;
  					self.explicitRange = null;
  				}
  			}
  
  			return rng;
  		},
  
  		/**
  		 * Changes the selection to the specified DOM range.
  		 *
  		 * @method setRng
  		 * @param {Range} rng Range to select.
  		 */
  		setRng: function(rng, forward) {
  			var self = this, sel;
  
  			if (!rng) {
  				return;
  			}
  
  			// Is IE specific range
  			if (rng.select) {
  				try {
  					rng.select();
  				} catch (ex) {
  					// Needed for some odd IE bug #1843306
  				}
  
  				return;
  			}
  
  			if (!self.tridentSel) {
  				sel = self.getSel();
  
  				if (sel) {
  					self.explicitRange = rng;
  
  					try {
  						sel.removeAllRanges();
  						sel.addRange(rng);
  					} catch (ex) {
  						// IE might throw errors here if the editor is within a hidden container and selection is changed
  					}
  
  					// Forward is set to false and we have an extend function
  					if (forward === false && sel.extend) {
  						sel.collapse(rng.endContainer, rng.endOffset);
  						sel.extend(rng.startContainer, rng.startOffset);
  					}
  
  					// adding range isn't always successful so we need to check range count otherwise an exception can occur
  					self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
  				}
  			} else {
  				// Is W3C Range fake range on IE
  				if (rng.cloneRange) {
  					try {
  						self.tridentSel.addRange(rng);
  						return;
  					} catch (ex) {
  						//IE9 throws an error here if called before selection is placed in the editor
  					}
  				}
  			}
  		},
  
  		/**
  		 * Sets the current selection to the specified DOM element.
  		 *
  		 * @method setNode
  		 * @param {Element} elm Element to set as the contents of the selection.
  		 * @return {Element} Returns the element that got passed in.
  		 * @example
  		 * // Inserts a DOM node at current selection/caret location
  		 * maya.activeEditor.selection.setNode(maya.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
  		 */
  		setNode: function(elm) {
  			var self = this;
  
  			self.setContent(self.dom.getOuterHTML(elm));
  
  			return elm;
  		},
  
  		/**
  		 * Returns the currently selected element or the common ancestor element for both start and end of the selection.
  		 *
  		 * @method getNode
  		 * @return {Element} Currently selected element or common ancestor element.
  		 * @example
  		 * // Alerts the currently selected elements node name
  		 * alert(maya.activeEditor.selection.getNode().nodeName);
  		 */
  		getNode: function() {
  			var self = this, rng = self.getRng(), elm;
  			var startContainer = rng.startContainer, endContainer = rng.endContainer;
  			var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot();
  
  			function skipEmptyTextNodes(node, forwards) {
  				var orig = node;
  
  				while (node && node.nodeType === 3 && node.length === 0) {
  					node = forwards ? node.nextSibling : node.previousSibling;
  				}
  
  				return node || orig;
  			}
  
  			// Range maybe lost after the editor is made visible again
  			if (!rng) {
  				return root;
  			}
  
  			if (rng.setStart) {
  				elm = rng.commonAncestorContainer;
  
  				// Handle selection a image or other control like element such as anchors
  				if (!rng.collapsed) {
  					if (startContainer == endContainer) {
  						if (endOffset - startOffset < 2) {
  							if (startContainer.hasChildNodes()) {
  								elm = startContainer.childNodes[startOffset];
  							}
  						}
  					}
  
  					// If the anchor node is a element instead of a text node then return this element
  					//if (maya.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
  					//	return sel.anchorNode.childNodes[sel.anchorOffset];
  
  					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
  					// This happens when you double click an underlined word in FireFox.
  					if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
  						if (startContainer.length === startOffset) {
  							startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
  						} else {
  							startContainer = startContainer.parentNode;
  						}
  
  						if (endOffset === 0) {
  							endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
  						} else {
  							endContainer = endContainer.parentNode;
  						}
  
  						if (startContainer && startContainer === endContainer) {
  							return startContainer;
  						}
  					}
  				}
  
  				if (elm && elm.nodeType == 3) {
  					return elm.parentNode;
  				}
  
  				return elm;
  			}
  
  			elm = rng.item ? rng.item(0) : rng.parentElement();
  
  			// IE 7 might return elements outside the iframe
  			if (elm.ownerDocument !== self.win.document) {
  				elm = root;
  			}
  
  			return elm;
  		},
  
  		getSelectedBlocks: function(startElm, endElm) {
  			var self = this, dom = self.dom, node, root, selectedBlocks = [];
  
  			root = dom.getRoot();
  			startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
  			endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
  
  			if (startElm && startElm != root) {
  				selectedBlocks.push(startElm);
  			}
  
  			if (startElm && endElm && startElm != endElm) {
  				node = startElm;
  
  				var walker = new TreeWalker(startElm, root);
  				while ((node = walker.next()) && node != endElm) {
  					if (dom.isBlock(node)) {
  						selectedBlocks.push(node);
  					}
  				}
  			}
  
  			if (endElm && startElm != endElm && endElm != root) {
  				selectedBlocks.push(endElm);
  			}
  
  			return selectedBlocks;
  		},
  
  		isForward: function() {
  			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
  
  			// No support for selection direction then always return true
  			if (!sel || !sel.anchorNode || !sel.focusNode) {
  				return true;
  			}
  
  			anchorRange = dom.createRng();
  			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
  			anchorRange.collapse(true);
  
  			focusRange = dom.createRng();
  			focusRange.setStart(sel.focusNode, sel.focusOffset);
  			focusRange.collapse(true);
  
  			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
  		},
  
  		normalize: function() {
  			var self = this, rng = self.getRng();
  
  			if (Env.range && new RangeUtils(self.dom).normalize(rng)) {
  				self.setRng(rng, self.isForward());
  			}
  
  			return rng;
  		},
  
  		/**
  		 * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback.
  		 *
  		 * @method selectorChanged
  		 * @param {String} selector CSS selector to check for.
  		 * @param {function} callback Callback with state and args when the selector is matches or not.
  		 */
  		selectorChanged: function(selector, callback) {
  			var self = this, currentSelectors;
  
  			if (!self.selectorChangedData) {
  				self.selectorChangedData = {};
  				currentSelectors = {};
  
  				self.editor.on('NodeChange', function(e) {
  					var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
  
  					// Check for new matching selectors
  					each(self.selectorChangedData, function(callbacks, selector) {
  						each(parents, function(node) {
  							if (dom.is(node, selector)) {
  								if (!currentSelectors[selector]) {
  									// Execute callbacks
  									each(callbacks, function(callback) {
  										callback(true, {node: node, selector: selector, parents: parents});
  									});
  
  									currentSelectors[selector] = callbacks;
  								}
  
  								matchedSelectors[selector] = callbacks;
  								return false;
  							}
  						});
  					});
  
  					// Check if current selectors still match
  					each(currentSelectors, function(callbacks, selector) {
  						if (!matchedSelectors[selector]) {
  							delete currentSelectors[selector];
  
  							each(callbacks, function(callback) {
  								callback(false, {node: node, selector: selector, parents: parents});
  							});
  						}
  					});
  				});
  			}
  
  			// Add selector listeners
  			if (!self.selectorChangedData[selector]) {
  				self.selectorChangedData[selector] = [];
  			}
  
  			self.selectorChangedData[selector].push(callback);
  
  			return self;
  		},
  
  		getScrollContainer: function() {
  			var scrollContainer, node = this.dom.getRoot();
  
  			while (node && node.nodeName != 'BODY') {
  				if (node.scrollHeight > node.clientHeight) {
  					scrollContainer = node;
  					break;
  				}
  
  				node = node.parentNode;
  			}
  
  			return scrollContainer;
  		},
  
  		scrollIntoView: function(elm) {
  			var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH;
  
  			function getPos(elm) {
  				var x = 0, y = 0;
  
  				var offsetParent = elm;
  				while (offsetParent && offsetParent.nodeType) {
  					x += offsetParent.offsetLeft || 0;
  					y += offsetParent.offsetTop || 0;
  					offsetParent = offsetParent.offsetParent;
  				}
  
  				return {x: x, y: y};
  			}
  
  			if (root.nodeName != 'BODY') {
  				var scrollContainer = self.getScrollContainer();
  				if (scrollContainer) {
  					y = getPos(elm).y - getPos(scrollContainer).y;
  					viewPortH = scrollContainer.clientHeight;
  					viewPortY = scrollContainer.scrollTop;
  					if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
  						scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
  					}
  
  					return;
  				}
  			}
  
  			viewPort = dom.getViewPort(self.editor.getWin());
  			y = dom.getPos(elm).y;
  			viewPortY = viewPort.y;
  			viewPortH = viewPort.h;
  			if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
  				self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
  			}
  		},
  
  		placeCaretAt: function(clientX, clientY) {
  			var doc = this.editor.getDoc(), rng, point;
  
  			if (doc.caretPositionFromPoint) {
  				point = doc.caretPositionFromPoint(clientX, clientY);
  				rng = doc.createRange();
  				rng.setStart(point.offsetNode, point.offset);
  				rng.collapse(true);
  			} else if (doc.caretRangeFromPoint) {
  				rng = doc.caretRangeFromPoint(clientX, clientY);
  			} else if (doc.body.createTextRange) {
  				rng = doc.body.createTextRange();
  
  				try {
  					rng.moveToPoint(clientX, clientY);
  					rng.collapse(true);
  				} catch (ex) {
  					rng.collapse(clientY < doc.body.clientHeight);
  				}
  			}
  
  			this.setRng(rng);
  		},
  
  		_moveEndPoint: function(rng, node, start) {
  			var root = node, walker = new TreeWalker(node, root);
  			var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
  
  			do {
  				// Text node
  				if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
  					if (start) {
  						rng.setStart(node, 0);
  					} else {
  						rng.setEnd(node, node.nodeValue.length);
  					}
  
  					return;
  				}
  
  				// BR/IMG/INPUT elements but not table cells
  				if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) {
  					if (start) {
  						rng.setStartBefore(node);
  					} else {
  						if (node.nodeName == 'BR') {
  							rng.setEndBefore(node);
  						} else {
  							rng.setEndAfter(node);
  						}
  					}
  
  					return;
  				}
  
  				// Found empty text block old IE can place the selection inside those
  				if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
  					if (start) {
  						rng.setStart(node, 0);
  					} else {
  						rng.setEnd(node, 0);
  					}
  
  					return;
  				}
  			} while ((node = (start ? walker.next() : walker.prev())));
  
  			// Failed to find any text node or other suitable location then move to the root of body
  			if (root.nodeName == 'BODY') {
  				if (start) {
  					rng.setStart(root, 0);
  				} else {
  					rng.setEnd(root, root.childNodes.length);
  				}
  			}
  		},
  
  		destroy: function() {
  			this.win = null;
  			this.controlSelection.destroy();
  		}
  	};
  
  	return Selection;
  });
  
  // Included from: js/maya/classes/dom/ElementUtils.js
  
  /**
   * ElementUtils.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Utility class for various element specific functions.
   *
   * @private
   */
  define("maya/dom/ElementUtils", [
  	"maya/dom/BookmarkManager",
  	"maya/util/Tools"
  ], function(BookmarkManager, Tools) {
  	var each = Tools.each;
  
  	function ElementUtils(dom) {
  		/**
  		 * Compares two nodes and checks if it's attributes and styles matches.
  		 * This doesn't compare classes as items since their order is significant.
  		 *
  		 * @method compare
  		 * @param {Node} node1 First node to compare with.
  		 * @param {Node} node2 Second node to compare with.
  		 * @return {boolean} True/false if the nodes are the same or not.
  		 */
  		this.compare = function(node1, node2) {
  			// Not the same name
  			if (node1.nodeName != node2.nodeName) {
  				return false;
  			}
  
  			/**
  			 * Returns all the nodes attributes excluding internal ones, styles and classes.
  			 *
  			 * @private
  			 * @param {Node} node Node to get attributes from.
  			 * @return {Object} Name/value object with attributes and attribute values.
  			 */
  			function getAttribs(node) {
  				var attribs = {};
  
  				each(dom.getAttribs(node), function(attr) {
  					var name = attr.nodeName.toLowerCase();
  
  					// Don't compare internal attributes or style
  					if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
  						attribs[name] = dom.getAttrib(node, name);
  					}
  				});
  
  				return attribs;
  			}
  
  			/**
  			 * Compares two objects checks if it's key + value exists in the other one.
  			 *
  			 * @private
  			 * @param {Object} obj1 First object to compare.
  			 * @param {Object} obj2 Second object to compare.
  			 * @return {boolean} True/false if the objects matches or not.
  			 */
  			function compareObjects(obj1, obj2) {
  				var value, name;
  
  				for (name in obj1) {
  					// Obj1 has item obj2 doesn't have
  					if (obj1.hasOwnProperty(name)) {
  						value = obj2[name];
  
  						// Obj2 doesn't have obj1 item
  						if (typeof value == "undefined") {
  							return false;
  						}
  
  						// Obj2 item has a different value
  						if (obj1[name] != value) {
  							return false;
  						}
  
  						// Delete similar value
  						delete obj2[name];
  					}
  				}
  
  				// Check if obj 2 has something obj 1 doesn't have
  				for (name in obj2) {
  					// Obj2 has item obj1 doesn't have
  					if (obj2.hasOwnProperty(name)) {
  						return false;
  					}
  				}
  
  				return true;
  			}
  
  			// Attribs are not the same
  			if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
  				return false;
  			}
  
  			// Styles are not the same
  			if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
  				return false;
  			}
  
  			return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
  		};
  	}
  
  	return ElementUtils;
  });
  
  // Included from: js/maya/classes/fmt/Preview.js
  
  /**
   * Preview.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Internal class for generating previews styles for formats.
   *
   * Example:
   *  Preview.getCssText(editor, 'bold');
   *
   * @class maya.fmt.Preview
   * @private
   */
  define("maya/fmt/Preview", [
  	"maya/util/Tools"
  ], function(Tools) {
  	var each = Tools.each;
  
  	function getCssText(editor, format) {
  		var name, previewElm, dom = editor.dom;
  		var previewCss = '', parentFontSize, previewStyles;
  
  		previewStyles = editor.settings.preview_styles;
  
  		// No preview forced
  		if (previewStyles === false) {
  			return '';
  		}
  
  		// Default preview
  		if (!previewStyles) {
  			previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
  				'text-transform color background-color border border-radius outline text-shadow';
  		}
  
  		// Removes any variables since these can't be previewed
  		function removeVars(val) {
  			return val.replace(/%(\w+)/g, '');
  		}
  
  		// Create block/inline element to use for preview
  		if (typeof format == "string") {
  			format = editor.formatter.get(format);
  			if (!format) {
  				return;
  			}
  
  			format = format[0];
  		}
  
  		name = format.block || format.inline || 'span';
  		previewElm = dom.create(name);
  
  		// Add format styles to preview element
  		each(format.styles, function(value, name) {
  			value = removeVars(value);
  
  			if (value) {
  				dom.setStyle(previewElm, name, value);
  			}
  		});
  
  		// Add attributes to preview element
  		each(format.attributes, function(value, name) {
  			value = removeVars(value);
  
  			if (value) {
  				dom.setAttrib(previewElm, name, value);
  			}
  		});
  
  		// Add classes to preview element
  		each(format.classes, function(value) {
  			value = removeVars(value);
  
  			if (!dom.hasClass(previewElm, value)) {
  				dom.addClass(previewElm, value);
  			}
  		});
  
  		editor.fire('PreviewFormats');
  
  		// Add the previewElm outside the visual area
  		dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
  		editor.getBody().appendChild(previewElm);
  
  		// Get parent container font size so we can compute px values out of em/% for older IE:s
  		parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
  		parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
  
  		each(previewStyles.split(' '), function(name) {
  			var value = dom.getStyle(previewElm, name, true);
  
  			// If background is transparent then check if the body has a background color we can use
  			if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
  				value = dom.getStyle(editor.getBody(), name, true);
  
  				// Ignore white since it's the default color, not the nicest fix
  				// TODO: Fix this by detecting runtime style
  				if (dom.toHex(value).toLowerCase() == '#ffffff') {
  					return;
  				}
  			}
  
  			if (name == 'color') {
  				// Ignore black since it's the default color, not the nicest fix
  				// TODO: Fix this by detecting runtime style
  				if (dom.toHex(value).toLowerCase() == '#000000') {
  					return;
  				}
  			}
  
  			// Old IE won't calculate the font size so we need to do that manually
  			if (name == 'font-size') {
  				if (/em|%$/.test(value)) {
  					if (parentFontSize === 0) {
  						return;
  					}
  
  					// Convert font size from em/% to px
  					value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
  					value = (value * parentFontSize) + 'px';
  				}
  			}
  
  			if (name == "border" && value) {
  				previewCss += 'padding:0 2px;';
  			}
  
  			previewCss += name + ':' + value + ';';
  		});
  
  		editor.fire('AfterPreviewFormats');
  
  		//previewCss += 'line-height:normal';
  
  		dom.remove(previewElm);
  
  		return previewCss;
  	}
  
  	return {
  		getCssText: getCssText
  	};
  });
  
  // Included from: js/maya/classes/Formatter.js
  
  /**
   * Formatter.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Text formatter engine class. This class is used to apply formats like bold, italic, font size
   * etc to the current selection or specific nodes. This engine was build to replace the browsers
   * default formatting logic for execCommand due to it's inconsistent and buggy behavior.
   *
   * @class maya.Formatter
   * @example
   *  maya.activeEditor.formatter.register('mycustomformat', {
   *    inline: 'span',
   *    styles: {color: '#ff0000'}
   *  });
   *
   *  maya.activeEditor.formatter.apply('mycustomformat');
   */
  define("maya/Formatter", [
  	"maya/dom/TreeWalker",
  	"maya/dom/RangeUtils",
  	"maya/dom/BookmarkManager",
  	"maya/dom/ElementUtils",
  	"maya/util/Tools",
  	"maya/fmt/Preview"
  ], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) {
  	/**
  	 * Constructs a new formatter instance.
  	 *
  	 * @constructor Formatter
  	 * @param {maya.Editor} ed Editor instance to construct the formatter engine to.
  	 */
  	return function(ed) {
  		var formats = {},
  			dom = ed.dom,
  			selection = ed.selection,
  			rangeUtils = new RangeUtils(dom),
  			isValid = ed.schema.isValidChild,
  			isBlock = dom.isBlock,
  			forcedRootBlock = ed.settings.forced_root_block,
  			nodeIndex = dom.nodeIndex,
  			INVISIBLE_CHAR = '\uFEFF',
  			MCE_ATTR_RE = /^(src|href|style)$/,
  			FALSE = false,
  			TRUE = true,
  			formatChangeData,
  			undef,
  			getContentEditable = dom.getContentEditable,
  			disableCaretContainer,
  			markCaretContainersBogus,
  			isBookmarkNode = BookmarkManager.isBookmarkNode;
  
  		var each = Tools.each,
  			grep = Tools.grep,
  			walk = Tools.walk,
  			extend = Tools.extend;
  
  		function isTextBlock(name) {
  			if (name.nodeType) {
  				name = name.nodeName;
  			}
  
  			return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
  		}
  
  		function getParents(node, selector) {
  			return dom.getParents(node, selector, dom.getRoot());
  		}
  
  		function isCaretNode(node) {
  			return node.nodeType === 1 && node.id === '_mce_caret';
  		}
  
  		function defaultFormats() {
  			register({
  				valigntop: [
  					{selector: 'td,th', styles: {'verticalAlign': 'top'}}
  				],
  
  				valignmiddle: [
  					{selector: 'td,th', styles: {'verticalAlign': 'middle'}}
  				],
  
  				valignbottom: [
  					{selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
  				],
  
  				alignleft: [
  					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
  					{selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
  				],
  
  				aligncenter: [
  					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
  					{selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
  					{selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
  				],
  
  				alignright: [
  					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
  					{selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
  				],
  
  				alignjustify: [
  					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
  				],
  
  				bold: [
  					{inline: 'strong', remove: 'all'},
  					{inline: 'span', styles: {fontWeight: 'bold'}},
  					{inline: 'b', remove: 'all'}
  				],
  
  				italic: [
  					{inline: 'em', remove: 'all'},
  					{inline: 'span', styles: {fontStyle: 'italic'}},
  					{inline: 'i', remove: 'all'}
  				],
  
  				underline: [
  					{inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
  					{inline: 'u', remove: 'all'}
  				],
  
  				strikethrough: [
  					{inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
  					{inline: 'strike', remove: 'all'}
  				],
  
  				forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true},
  				hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true},
  				fontname: {inline: 'span', styles: {fontFamily: '%value'}},
  				fontsize: {inline: 'span', styles: {fontSize: '%value'}},
  				fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
  				blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
  				subscript: {inline: 'sub'},
  				superscript: {inline: 'sup'},
  				code: {inline: 'code'},
  
  				link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
  					onmatch: function() {
  						return true;
  					},
  
  					onformat: function(elm, fmt, vars) {
  						each(vars, function(value, key) {
  							dom.setAttrib(elm, key, value);
  						});
  					}
  				},
  
  				removeformat: [
  					{
  						selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins',
  						remove: 'all',
  						split: true,
  						expand: false,
  						block_expand: true,
  						deep: true
  					},
  					{selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
  					{selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
  				]
  			});
  
  			// Register default block formats
  			each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
  				register(name, {block: name, remove: 'all'});
  			});
  
  			// Register user defined formats
  			register(ed.settings.formats);
  		}
  
  		function addKeyboardShortcuts() {
  			// Add some inline shortcuts
  			ed.addShortcut('meta+b', 'bold_desc', 'Bold');
  			ed.addShortcut('meta+i', 'italic_desc', 'Italic');
  			ed.addShortcut('meta+u', 'underline_desc', 'Underline');
  
  			// BlockFormat shortcuts keys
  			for (var i = 1; i <= 6; i++) {
  				ed.addShortcut('meta+shift+' + i, '', ['FormatBlock', false, 'h' + i]);
  			}
  
  			ed.addShortcut('meta+shift+7', '', ['FormatBlock', false, 'p']);
  			ed.addShortcut('meta+shift+8', '', ['FormatBlock', false, 'div']);
  			ed.addShortcut('meta+shift+9', '', ['FormatBlock', false, 'address']);
  		}
  
  		// Public functions
  
  		/**
  		 * Returns the format by name or all formats if no name is specified.
  		 *
  		 * @method get
  		 * @param {String} name Optional name to retrive by.
  		 * @return {Array/Object} Array/Object with all registred formats or a specific format.
  		 */
  		function get(name) {
  			return name ? formats[name] : formats;
  		}
  
  		/**
  		 * Registers a specific format by name.
  		 *
  		 * @method register
  		 * @param {Object/String} name Name of the format for example "bold".
  		 * @param {Object/Array} format Optional format object or array of format variants
  		 * can only be omitted if the first arg is an object.
  		 */
  		function register(name, format) {
  			if (name) {
  				if (typeof name !== 'string') {
  					each(name, function(format, name) {
  						register(name, format);
  					});
  				} else {
  					// Force format into array and add it to internal collection
  					format = format.length ? format : [format];
  
  					each(format, function(format) {
  						// Set deep to false by default on selector formats this to avoid removing
  						// alignment on images inside paragraphs when alignment is changed on paragraphs
  						if (format.deep === undef) {
  							format.deep = !format.selector;
  						}
  
  						// Default to true
  						if (format.split === undef) {
  							format.split = !format.selector || format.inline;
  						}
  
  						// Default to true
  						if (format.remove === undef && format.selector && !format.inline) {
  							format.remove = 'none';
  						}
  
  						// Mark format as a mixed format inline + block level
  						if (format.selector && format.inline) {
  							format.mixed = true;
  							format.block_expand = true;
  						}
  
  						// Split classes if needed
  						if (typeof format.classes === 'string') {
  							format.classes = format.classes.split(/\s+/);
  						}
  					});
  
  					formats[name] = format;
  				}
  			}
  		}
  
  		/**
  		 * Unregister a specific format by name.
  		 *
  		 * @method unregister
  		 * @param {String} name Name of the format for example "bold".
  		 */
  		function unregister(name) {
  			if (name && formats[name]) {
  				delete formats[name];
  			}
  
  			return formats;
  		}
  
  		function getTextDecoration(node) {
  			var decoration;
  
  			ed.dom.getParent(node, function(n) {
  				decoration = ed.dom.getStyle(n, 'text-decoration');
  				return decoration && decoration !== 'none';
  			});
  
  			return decoration;
  		}
  
  		function processUnderlineAndColor(node) {
  			var textDecoration;
  			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
  				textDecoration = getTextDecoration(node.parentNode);
  				if (ed.dom.getStyle(node, 'color') && textDecoration) {
  					ed.dom.setStyle(node, 'text-decoration', textDecoration);
  				} else if (ed.dom.getStyle(node, 'text-decoration') === textDecoration) {
  					ed.dom.setStyle(node, 'text-decoration', null);
  				}
  			}
  		}
  
  		/**
  		 * Applies the specified format to the current selection or specified node.
  		 *
  		 * @method apply
  		 * @param {String} name Name of format to apply.
  		 * @param {Object} vars Optional list of variables to replace within format before applying it.
  		 * @param {Node} node Optional node to apply the format to defaults to current selection.
  		 */
  		function apply(name, vars, node) {
  			var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
  
  			function setElementFormat(elm, fmt) {
  				fmt = fmt || format;
  
  				if (elm) {
  					if (fmt.onformat) {
  						fmt.onformat(elm, fmt, vars, node);
  					}
  
  					each(fmt.styles, function(value, name) {
  						dom.setStyle(elm, name, replaceVars(value, vars));
  					});
  
  					// Needed for the WebKit span spam bug
  					// TODO: Remove this once WebKit/Blink fixes this
  					if (fmt.styles) {
  						var styleVal = dom.getAttrib(elm, 'style');
  
  						if (styleVal) {
  							elm.setAttribute('data-mce-style', styleVal);
  						}
  					}
  
  					each(fmt.attributes, function(value, name) {
  						dom.setAttrib(elm, name, replaceVars(value, vars));
  					});
  
  					each(fmt.classes, function(value) {
  						value = replaceVars(value, vars);
  
  						if (!dom.hasClass(elm, value)) {
  							dom.addClass(elm, value);
  						}
  					});
  				}
  			}
  
  			function adjustSelectionToVisibleSelection() {
  				function findSelectionEnd(start, end) {
  					var walker = new TreeWalker(end);
  					for (node = walker.current(); node; node = walker.prev()) {
  						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
  							return node;
  						}
  					}
  				}
  
  				// Adjust selection so that a end container with a end offset of zero is not included in the selection
  				// as this isn't visible to the user.
  				var rng = ed.selection.getRng();
  				var start = rng.startContainer;
  				var end = rng.endContainer;
  
  				if (start != end && rng.endOffset === 0) {
  					var newEnd = findSelectionEnd(start, end);
  					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
  
  					rng.setEnd(newEnd, endOffset);
  				}
  
  				return rng;
  			}
  
  			function applyRngStyle(rng, bookmark, node_specific) {
  				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
  
  				// Setup wrapper element
  				wrapName = format.inline || format.block;
  				wrapElm = dom.create(wrapName);
  				setElementFormat(wrapElm);
  
  				rangeUtils.walk(rng, function(nodes) {
  					var currentWrapElm;
  
  					/**
  					 * Process a list of nodes wrap them.
  					 */
  					function process(node) {
  						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
  
  						lastContentEditable = contentEditable;
  						nodeName = node.nodeName.toLowerCase();
  						parentName = node.parentNode.nodeName.toLowerCase();
  
  						// Node has a contentEditable value
  						if (node.nodeType === 1 && getContentEditable(node)) {
  							lastContentEditable = contentEditable;
  							contentEditable = getContentEditable(node) === "true";
  							hasContentEditableState = true; // We don't want to wrap the container only it's children
  						}
  
  						// Stop wrapping on br elements
  						if (isEq(nodeName, 'br')) {
  							currentWrapElm = 0;
  
  							// Remove any br elements when we wrap things
  							if (format.block) {
  								dom.remove(node);
  							}
  
  							return;
  						}
  
  						// If node is wrapper type
  						if (format.wrapper && matchNode(node, name, vars)) {
  							currentWrapElm = 0;
  							return;
  						}
  
  						// Can we rename the block
  						// TODO: Break this if up, too complex
  						if (contentEditable && !hasContentEditableState && format.block &&
  							!format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
  							node = dom.rename(node, wrapName);
  							setElementFormat(node);
  							newWrappers.push(node);
  							currentWrapElm = 0;
  							return;
  						}
  
  						// Handle selector patterns
  						if (format.selector) {
  							// Look for matching formats
  							each(formatList, function(format) {
  								// Check collapsed state if it exists
  								if ('collapsed' in format && format.collapsed !== isCollapsed) {
  									return;
  								}
  
  								if (dom.is(node, format.selector) && !isCaretNode(node)) {
  									setElementFormat(node, format);
  									found = true;
  								}
  							});
  
  							// Continue processing if a selector match wasn't found and a inline element is defined
  							if (!format.inline || found) {
  								currentWrapElm = 0;
  								return;
  							}
  						}
  
  						// Is it valid to wrap this item
  						// TODO: Break this if up, too complex
  						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
  								!(!node_specific && node.nodeType === 3 &&
  								node.nodeValue.length === 1 &&
  								node.nodeValue.charCodeAt(0) === 65279) &&
  								!isCaretNode(node) &&
  								(!format.inline || !isBlock(node))) {
  							// Start wrapping
  							if (!currentWrapElm) {
  								// Wrap the node
  								currentWrapElm = dom.clone(wrapElm, FALSE);
  								node.parentNode.insertBefore(currentWrapElm, node);
  								newWrappers.push(currentWrapElm);
  							}
  
  							currentWrapElm.appendChild(node);
  						} else {
  							// Start a new wrapper for possible children
  							currentWrapElm = 0;
  
  							each(grep(node.childNodes), process);
  
  							if (hasContentEditableState) {
  								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  							}
  
  							// End the last wrapper
  							currentWrapElm = 0;
  						}
  					}
  
  					// Process siblings from range
  					each(nodes, process);
  				});
  
  				// Apply formats to links as well to get the color of the underline to change as well
  				if (format.links === true) {
  					each(newWrappers, function(node) {
  						function process(node) {
  							if (node.nodeName === 'A') {
  								setElementFormat(node, format);
  							}
  
  							each(grep(node.childNodes), process);
  						}
  
  						process(node);
  					});
  				}
  
  				// Cleanup
  				each(newWrappers, function(node) {
  					var childCount;
  
  					function getChildCount(node) {
  						var count = 0;
  
  						each(node.childNodes, function(node) {
  							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
  								count++;
  							}
  						});
  
  						return count;
  					}
  
  					function mergeStyles(node) {
  						var child, clone;
  
  						each(node.childNodes, function(node) {
  							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
  								child = node;
  								return FALSE; // break loop
  							}
  						});
  
  						// If child was found and of the same type as the current node
  						if (child && !isBookmarkNode(child) && matchName(child, format)) {
  							clone = dom.clone(child, FALSE);
  							setElementFormat(clone);
  
  							dom.replace(clone, node, TRUE);
  							dom.remove(child, 1);
  						}
  
  						return clone || node;
  					}
  
  					childCount = getChildCount(node);
  
  					// Remove empty nodes but only if there is multiple wrappers and they are not block
  					// elements so never remove single <h1></h1> since that would remove the
  					// currrent empty block element where the caret is at
  					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
  						dom.remove(node, 1);
  						return;
  					}
  
  					if (format.inline || format.wrapper) {
  						// Merges the current node with it's children of similar type to reduce the number of elements
  						if (!format.exact && childCount === 1) {
  							node = mergeStyles(node);
  						}
  
  						// Remove/merge children
  						each(formatList, function(format) {
  							// Merge all children of similar type will move styles from child to parent
  							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
  							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
  							each(dom.select(format.inline, node), function(child) {
  								if (isBookmarkNode(child)) {
  									return;
  								}
  
  								removeFormat(format, vars, child, format.exact ? child : null);
  							});
  						});
  
  						// Remove child if direct parent is of same type
  						if (matchNode(node.parentNode, name, vars)) {
  							dom.remove(node, 1);
  							node = 0;
  							return TRUE;
  						}
  
  						// Look for parent with similar style format
  						if (format.merge_with_parents) {
  							dom.getParent(node.parentNode, function(parent) {
  								if (matchNode(parent, name, vars)) {
  									dom.remove(node, 1);
  									node = 0;
  									return TRUE;
  								}
  							});
  						}
  
  						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
  						if (node && format.merge_siblings !== false) {
  							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
  							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
  						}
  					}
  				});
  			}
  
  			if (format) {
  				if (node) {
  					if (node.nodeType) {
  						rng = dom.createRng();
  						rng.setStartBefore(node);
  						rng.setEndAfter(node);
  						applyRngStyle(expandRng(rng, formatList), null, true);
  					} else {
  						applyRngStyle(node, null, true);
  					}
  				} else {
  					if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
  						// Obtain selection node before selection is unselected by applyRngStyle()
  						var curSelNode = ed.selection.getNode();
  
  						// If the formats have a default block and we can't find a parent block then
  						// start wrapping it with a DIV this is for forced_root_blocks: false
  						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
  						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
  							apply(formatList[0].defaultBlock);
  						}
  
  						// Apply formatting to selection
  						ed.selection.setRng(adjustSelectionToVisibleSelection());
  						bookmark = selection.getBookmark();
  						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
  
  						// Colored nodes should be underlined so that the color of the underline matches the text color.
  						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
  							walk(curSelNode, processUnderlineAndColor, 'childNodes');
  							processUnderlineAndColor(curSelNode);
  						}
  
  						selection.moveToBookmark(bookmark);
  						moveStart(selection.getRng(TRUE));
  						ed.nodeChanged();
  					} else {
  						performCaretAction('apply', name, vars);
  					}
  				}
  			}
  		}
  
  		/**
  		 * Removes the specified format from the current selection or specified node.
  		 *
  		 * @method remove
  		 * @param {String} name Name of format to remove.
  		 * @param {Object} vars Optional list of variables to replace within format before removing it.
  		 * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
  		 */
  		function remove(name, vars, node, similar) {
  			var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
  
  			// Merges the styles for each node
  			function process(node) {
  				var children, i, l, lastContentEditable, hasContentEditableState;
  
  				// Node has a contentEditable value
  				if (node.nodeType === 1 && getContentEditable(node)) {
  					lastContentEditable = contentEditable;
  					contentEditable = getContentEditable(node) === "true";
  					hasContentEditableState = true; // We don't want to wrap the container only it's children
  				}
  
  				// Grab the children first since the nodelist might be changed
  				children = grep(node.childNodes);
  
  				// Process current node
  				if (contentEditable && !hasContentEditableState) {
  					for (i = 0, l = formatList.length; i < l; i++) {
  						if (removeFormat(formatList[i], vars, node, node)) {
  							break;
  						}
  					}
  				}
  
  				// Process the children
  				if (format.deep) {
  					if (children.length) {
  						for (i = 0, l = children.length; i < l; i++) {
  							process(children[i]);
  						}
  
  						if (hasContentEditableState) {
  							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  						}
  					}
  				}
  			}
  
  			function findFormatRoot(container) {
  				var formatRoot;
  
  				// Find format root
  				each(getParents(container.parentNode).reverse(), function(parent) {
  					var format;
  
  					// Find format root element
  					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
  						// Is the node matching the format we are looking for
  						format = matchNode(parent, name, vars, similar);
  						if (format && format.split !== false) {
  							formatRoot = parent;
  						}
  					}
  				});
  
  				return formatRoot;
  			}
  
  			function wrapAndSplit(formatRoot, container, target, split) {
  				var parent, clone, lastClone, firstClone, i, formatRootParent;
  
  				// Format root found then clone formats and split it
  				if (formatRoot) {
  					formatRootParent = formatRoot.parentNode;
  
  					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
  						clone = dom.clone(parent, FALSE);
  
  						for (i = 0; i < formatList.length; i++) {
  							if (removeFormat(formatList[i], vars, clone, clone)) {
  								clone = 0;
  								break;
  							}
  						}
  
  						// Build wrapper node
  						if (clone) {
  							if (lastClone) {
  								clone.appendChild(lastClone);
  							}
  
  							if (!firstClone) {
  								firstClone = clone;
  							}
  
  							lastClone = clone;
  						}
  					}
  
  					// Never split block elements if the format is mixed
  					if (split && (!format.mixed || !isBlock(formatRoot))) {
  						container = dom.split(formatRoot, container);
  					}
  
  					// Wrap container in cloned formats
  					if (lastClone) {
  						target.parentNode.insertBefore(lastClone, target);
  						firstClone.appendChild(target);
  					}
  				}
  
  				return container;
  			}
  
  			function splitToFormatRoot(container) {
  				return wrapAndSplit(findFormatRoot(container), container, container, true);
  			}
  
  			function unwrap(start) {
  				var node = dom.get(start ? '_start' : '_end'),
  					out = node[start ? 'firstChild' : 'lastChild'];
  
  				// If the end is placed within the start the result will be removed
  				// So this checks if the out node is a bookmark node if it is it
  				// checks for another more suitable node
  				if (isBookmarkNode(out)) {
  					out = out[start ? 'firstChild' : 'lastChild'];
  				}
  
  				// Since dom.remove removes empty text nodes then we need to try to find a better node
  				if (out.nodeType == 3 && out.data.length === 0) {
  					out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling;
  				}
  
  				dom.remove(node, true);
  
  				return out;
  			}
  
  			function removeRngStyle(rng) {
  				var startContainer, endContainer;
  				var commonAncestorContainer = rng.commonAncestorContainer;
  
  				rng = expandRng(rng, formatList, TRUE);
  
  				if (format.split) {
  					startContainer = getContainer(rng, TRUE);
  					endContainer = getContainer(rng);
  
  					if (startContainer != endContainer) {
  						// WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
  						// so let's see if we can use the first child instead
  						// This will happen if you triple click a table cell and use remove formatting
  						if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
  							if (startContainer.nodeName == "TR") {
  								startContainer = startContainer.firstChild.firstChild || startContainer;
  							} else {
  								startContainer = startContainer.firstChild || startContainer;
  							}
  						}
  
  						// Try to adjust endContainer as well if cells on the same row were selected - bug #6410
  						if (commonAncestorContainer &&
  							/^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
  							/^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) {
  							endContainer = endContainer.firstChild || endContainer;
  						}
  
  						// Wrap start/end nodes in span element since these might be cloned/moved
  						startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
  						endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
  
  						// Split start/end
  						splitToFormatRoot(startContainer);
  						splitToFormatRoot(endContainer);
  
  						// Unwrap start/end to get real elements again
  						startContainer = unwrap(TRUE);
  						endContainer = unwrap();
  					} else {
  						startContainer = endContainer = splitToFormatRoot(startContainer);
  					}
  
  					// Update range positions since they might have changed after the split operations
  					rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer;
  					rng.startOffset = nodeIndex(startContainer);
  					rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer;
  					rng.endOffset = nodeIndex(endContainer) + 1;
  				}
  
  				// Remove items between start/end
  				rangeUtils.walk(rng, function(nodes) {
  					each(nodes, function(node) {
  						process(node);
  
  						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
  						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
  							node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
  							removeFormat({
  								'deep': false,
  								'exact': true,
  								'inline': 'span',
  								'styles': {
  									'textDecoration': 'underline'
  								}
  							}, null, node);
  						}
  					});
  				});
  			}
  
  			// Handle node
  			if (node) {
  				if (node.nodeType) {
  					rng = dom.createRng();
  					rng.setStartBefore(node);
  					rng.setEndAfter(node);
  					removeRngStyle(rng);
  				} else {
  					removeRngStyle(node);
  				}
  
  				return;
  			}
  
  			if (!selection.isCollapsed() || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
  				bookmark = selection.getBookmark();
  				removeRngStyle(selection.getRng(TRUE));
  				selection.moveToBookmark(bookmark);
  
  				// Check if start element still has formatting then we are at: "<b>text|</b>text"
  				// and need to move the start into the next text node
  				if (format.inline && match(name, vars, selection.getStart())) {
  					moveStart(selection.getRng(true));
  				}
  
  				ed.nodeChanged();
  			} else {
  				performCaretAction('remove', name, vars, similar);
  			}
  		}
  
  		/**
  		 * Toggles the specified format on/off.
  		 *
  		 * @method toggle
  		 * @param {String} name Name of format to apply/remove.
  		 * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
  		 * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
  		 */
  		function toggle(name, vars, node) {
  			var fmt = get(name);
  
  			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
  				remove(name, vars, node);
  			} else {
  				apply(name, vars, node);
  			}
  		}
  
  		/**
  		 * Return true/false if the specified node has the specified format.
  		 *
  		 * @method matchNode
  		 * @param {Node} node Node to check the format on.
  		 * @param {String} name Format name to check.
  		 * @param {Object} vars Optional list of variables to replace before checking it.
  		 * @param {Boolean} similar Match format that has similar properties.
  		 * @return {Object} Returns the format object it matches or undefined if it doesn't match.
  		 */
  		function matchNode(node, name, vars, similar) {
  			var formatList = get(name), format, i, classes;
  
  			function matchItems(node, format, item_name) {
  				var key, value, items = format[item_name], i;
  
  				// Custom match
  				if (format.onmatch) {
  					return format.onmatch(node, format, item_name);
  				}
  
  				// Check all items
  				if (items) {
  					// Non indexed object
  					if (items.length === undef) {
  						for (key in items) {
  							if (items.hasOwnProperty(key)) {
  								if (item_name === 'attributes') {
  									value = dom.getAttrib(node, key);
  								} else {
  									value = getStyle(node, key);
  								}
  
  								if (similar && !value && !format.exact) {
  									return;
  								}
  
  								if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
  									return;
  								}
  							}
  						}
  					} else {
  						// Only one match needed for indexed arrays
  						for (i = 0; i < items.length; i++) {
  							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
  								return format;
  							}
  						}
  					}
  				}
  
  				return format;
  			}
  
  			if (formatList && node) {
  				// Check each format in list
  				for (i = 0; i < formatList.length; i++) {
  					format = formatList[i];
  
  					// Name name, attributes, styles and classes
  					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
  						// Match classes
  						if ((classes = format.classes)) {
  							for (i = 0; i < classes.length; i++) {
  								if (!dom.hasClass(node, classes[i])) {
  									return;
  								}
  							}
  						}
  
  						return format;
  					}
  				}
  			}
  		}
  
  		/**
  		 * Matches the current selection or specified node against the specified format name.
  		 *
  		 * @method match
  		 * @param {String} name Name of format to match.
  		 * @param {Object} vars Optional list of variables to replace before checking it.
  		 * @param {Node} node Optional node to check.
  		 * @return {boolean} true/false if the specified selection/node matches the format.
  		 */
  		function match(name, vars, node) {
  			var startNode;
  
  			function matchParents(node) {
  				var root = dom.getRoot();
  
  				if (node === root) {
  					return false;
  				}
  
  				// Find first node with similar format settings
  				node = dom.getParent(node, function(node) {
  					return node.parentNode === root || !!matchNode(node, name, vars, true);
  				});
  
  				// Do an exact check on the similar format element
  				return matchNode(node, name, vars);
  			}
  
  			// Check specified node
  			if (node) {
  				return matchParents(node);
  			}
  
  			// Check selected node
  			node = selection.getNode();
  			if (matchParents(node)) {
  				return TRUE;
  			}
  
  			// Check start node if it's different
  			startNode = selection.getStart();
  			if (startNode != node) {
  				if (matchParents(startNode)) {
  					return TRUE;
  				}
  			}
  
  			return FALSE;
  		}
  
  		/**
  		 * Matches the current selection against the array of formats and returns a new array with matching formats.
  		 *
  		 * @method matchAll
  		 * @param {Array} names Name of format to match.
  		 * @param {Object} vars Optional list of variables to replace before checking it.
  		 * @return {Array} Array with matched formats.
  		 */
  		function matchAll(names, vars) {
  			var startElement, matchedFormatNames = [], checkedMap = {};
  
  			// Check start of selection for formats
  			startElement = selection.getStart();
  			dom.getParent(startElement, function(node) {
  				var i, name;
  
  				for (i = 0; i < names.length; i++) {
  					name = names[i];
  
  					if (!checkedMap[name] && matchNode(node, name, vars)) {
  						checkedMap[name] = true;
  						matchedFormatNames.push(name);
  					}
  				}
  			}, dom.getRoot());
  
  			return matchedFormatNames;
  		}
  
  		/**
  		 * Returns true/false if the specified format can be applied to the current selection or not. It
  		 * will currently only check the state for selector formats, it returns true on all other format types.
  		 *
  		 * @method canApply
  		 * @param {String} name Name of format to check.
  		 * @return {boolean} true/false if the specified format can be applied to the current selection/node.
  		 */
  		function canApply(name) {
  			var formatList = get(name), startNode, parents, i, x, selector;
  
  			if (formatList) {
  				startNode = selection.getStart();
  				parents = getParents(startNode);
  
  				for (x = formatList.length - 1; x >= 0; x--) {
  					selector = formatList[x].selector;
  
  					// Format is not selector based then always return TRUE
  					// Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
  					if (!selector || formatList[x].defaultBlock) {
  						return TRUE;
  					}
  
  					for (i = parents.length - 1; i >= 0; i--) {
  						if (dom.is(parents[i], selector)) {
  							return TRUE;
  						}
  					}
  				}
  			}
  
  			return FALSE;
  		}
  
  		/**
  		 * Executes the specified callback when the current selection matches the formats or not.
  		 *
  		 * @method formatChanged
  		 * @param {String} formats Comma separated list of formats to check for.
  		 * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
  		 * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
  		 */
  		function formatChanged(formats, callback, similar) {
  			var currentFormats;
  
  			// Setup format node change logic
  			if (!formatChangeData) {
  				formatChangeData = {};
  				currentFormats = {};
  
  				ed.on('NodeChange', function(e) {
  					var parents = getParents(e.element), matchedFormats = {};
  
  					// Ignore bogus nodes like the <a> tag created by moveStart()
  					parents = Tools.grep(parents, function(node) {
  						return node.nodeType == 1 && !node.getAttribute('data-mce-bogus');
  					});
  
  					// Check for new formats
  					each(formatChangeData, function(callbacks, format) {
  						each(parents, function(node) {
  							if (matchNode(node, format, {}, callbacks.similar)) {
  								if (!currentFormats[format]) {
  									// Execute callbacks
  									each(callbacks, function(callback) {
  										callback(true, {node: node, format: format, parents: parents});
  									});
  
  									currentFormats[format] = callbacks;
  								}
  
  								matchedFormats[format] = callbacks;
  								return false;
  							}
  						});
  					});
  
  					// Check if current formats still match
  					each(currentFormats, function(callbacks, format) {
  						if (!matchedFormats[format]) {
  							delete currentFormats[format];
  
  							each(callbacks, function(callback) {
  								callback(false, {node: e.element, format: format, parents: parents});
  							});
  						}
  					});
  				});
  			}
  
  			// Add format listeners
  			each(formats.split(','), function(format) {
  				if (!formatChangeData[format]) {
  					formatChangeData[format] = [];
  					formatChangeData[format].similar = similar;
  				}
  
  				formatChangeData[format].push(callback);
  			});
  
  			return this;
  		}
  
  		/**
  		 * Returns a preview css text for the specified format.
  		 *
  		 * @method getCssText
  		 * @param {String/Object} format Format to generate preview css text for.
  		 * @return {String} Css text for the specified format.
  		 * @example
  		 * var cssText1 = editor.formatter.getCssText('bold');
  		 * var cssText2 = editor.formatter.getCssText({inline: 'b'});
  		 */
  		function getCssText(format) {
  			return Preview.getCssText(ed, format);
  		}
  
  		// Expose to public
  		extend(this, {
  			get: get,
  			register: register,
  			unregister: unregister,
  			apply: apply,
  			remove: remove,
  			toggle: toggle,
  			match: match,
  			matchAll: matchAll,
  			matchNode: matchNode,
  			canApply: canApply,
  			formatChanged: formatChanged,
  			getCssText: getCssText
  		});
  
  		// Initialize
  		defaultFormats();
  		addKeyboardShortcuts();
  		ed.on('BeforeGetContent', function(e) {
  			if (markCaretContainersBogus && e.format != 'raw') {
  				markCaretContainersBogus();
  			}
  		});
  		ed.on('mouseup keydown', function(e) {
  			if (disableCaretContainer) {
  				disableCaretContainer(e);
  			}
  		});
  
  		// Private functions
  
  		/**
  		 * Checks if the specified nodes name matches the format inline/block or selector.
  		 *
  		 * @private
  		 * @param {Node} node Node to match against the specified format.
  		 * @param {Object} format Format object o match with.
  		 * @return {boolean} true/false if the format matches.
  		 */
  		function matchName(node, format) {
  			// Check for inline match
  			if (isEq(node, format.inline)) {
  				return TRUE;
  			}
  
  			// Check for block match
  			if (isEq(node, format.block)) {
  				return TRUE;
  			}
  
  			// Check for selector match
  			if (format.selector) {
  				return node.nodeType == 1 && dom.is(node, format.selector);
  			}
  		}
  
  		/**
  		 * Compares two string/nodes regardless of their case.
  		 *
  		 * @private
  		 * @param {String/Node} Node or string to compare.
  		 * @param {String/Node} Node or string to compare.
  		 * @return {boolean} True/false if they match.
  		 */
  		function isEq(str1, str2) {
  			str1 = str1 || '';
  			str2 = str2 || '';
  
  			str1 = '' + (str1.nodeName || str1);
  			str2 = '' + (str2.nodeName || str2);
  
  			return str1.toLowerCase() == str2.toLowerCase();
  		}
  
  		/**
  		 * Returns the style by name on the specified node. This method modifies the style
  		 * contents to make it more easy to match. This will resolve a few browser issues.
  		 *
  		 * @private
  		 * @param {Node} node to get style from.
  		 * @param {String} name Style name to get.
  		 * @return {String} Style item value.
  		 */
  		function getStyle(node, name) {
  			return normalizeStyleValue(dom.getStyle(node, name), name);
  		}
  
  		/**
  		 * Normalize style value by name. This method modifies the style contents
  		 * to make it more easy to match. This will resolve a few browser issues.
  		 *
  		 * @private
  		 * @param {Node} node to get style from.
  		 * @param {String} name Style name to get.
  		 * @return {String} Style item value.
  		 */
  		function normalizeStyleValue(value, name) {
  			// Force the format to hex
  			if (name == 'color' || name == 'backgroundColor') {
  				value = dom.toHex(value);
  			}
  
  			// Opera will return bold as 700
  			if (name == 'fontWeight' && value == 700) {
  				value = 'bold';
  			}
  
  			// Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
  			if (name == 'fontFamily') {
  				value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
  			}
  
  			return '' + value;
  		}
  
  		/**
  		 * Replaces variables in the value. The variable format is %var.
  		 *
  		 * @private
  		 * @param {String} value Value to replace variables in.
  		 * @param {Object} vars Name/value array with variables to replace.
  		 * @return {String} New value with replaced variables.
  		 */
  		function replaceVars(value, vars) {
  			if (typeof value != "string") {
  				value = value(vars);
  			} else if (vars) {
  				value = value.replace(/%(\w+)/g, function(str, name) {
  					return vars[name] || str;
  				});
  			}
  
  			return value;
  		}
  
  		function isWhiteSpaceNode(node) {
  			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
  		}
  
  		function wrap(node, name, attrs) {
  			var wrapper = dom.create(name, attrs);
  
  			node.parentNode.insertBefore(wrapper, node);
  			wrapper.appendChild(node);
  
  			return wrapper;
  		}
  
  		/**
  		 * Expands the specified range like object to depending on format.
  		 *
  		 * For example on block formats it will move the start/end position
  		 * to the beginning of the current block.
  		 *
  		 * @private
  		 * @param {Object} rng Range like object.
  		 * @param {Array} formats Array with formats to expand by.
  		 * @return {Object} Expanded range like object.
  		 */
  		function expandRng(rng, format, remove) {
  			var lastIdx, leaf, endPoint,
  				startContainer = rng.startContainer,
  				startOffset = rng.startOffset,
  				endContainer = rng.endContainer,
  				endOffset = rng.endOffset;
  
  			// This function walks up the tree if there is no siblings before/after the node
  			function findParentContainer(start) {
  				var container, parent, sibling, siblingName, root;
  
  				container = parent = start ? startContainer : endContainer;
  				siblingName = start ? 'previousSibling' : 'nextSibling';
  				root = dom.getRoot();
  
  				function isBogusBr(node) {
  					return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
  				}
  
  				// If it's a text node and the offset is inside the text
  				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
  					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
  						return container;
  					}
  				}
  
  				/*eslint no-constant-condition:0 */
  				while (true) {
  					// Stop expanding on block elements
  					if (!format[0].block_expand && isBlock(parent)) {
  						return parent;
  					}
  
  					// Walk left/right
  					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
  						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
  							return parent;
  						}
  					}
  
  					// Check if we can move up are we at root level or body level
  					if (parent.parentNode == root) {
  						container = parent;
  						break;
  					}
  
  					parent = parent.parentNode;
  				}
  
  				return container;
  			}
  
  			// This function walks down the tree to find the leaf at the selection.
  			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
  			function findLeaf(node, offset) {
  				if (offset === undef) {
  					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  				}
  
  				while (node && node.hasChildNodes()) {
  					node = node.childNodes[offset];
  					if (node) {
  						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  					}
  				}
  				return {node: node, offset: offset};
  			}
  
  			// If index based start position then resolve it
  			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  				lastIdx = startContainer.childNodes.length - 1;
  				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
  
  				if (startContainer.nodeType == 3) {
  					startOffset = 0;
  				}
  			}
  
  			// If index based end position then resolve it
  			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  				lastIdx = endContainer.childNodes.length - 1;
  				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
  
  				if (endContainer.nodeType == 3) {
  					endOffset = endContainer.nodeValue.length;
  				}
  			}
  
  			// Expands the node to the closes contentEditable false element if it exists
  			function findParentContentEditable(node) {
  				var parent = node;
  
  				while (parent) {
  					if (parent.nodeType === 1 && getContentEditable(parent)) {
  						return getContentEditable(parent) === "false" ? parent : node;
  					}
  
  					parent = parent.parentNode;
  				}
  
  				return node;
  			}
  
  			function findWordEndPoint(container, offset, start) {
  				var walker, node, pos, lastTextNode;
  
  				function findSpace(node, offset) {
  					var pos, pos2, str = node.nodeValue;
  
  					if (typeof offset == "undefined") {
  						offset = start ? str.length : 0;
  					}
  
  					if (start) {
  						pos = str.lastIndexOf(' ', offset);
  						pos2 = str.lastIndexOf('\u00a0', offset);
  						pos = pos > pos2 ? pos : pos2;
  
  						// Include the space on remove to avoid tag soup
  						if (pos !== -1 && !remove) {
  							pos++;
  						}
  					} else {
  						pos = str.indexOf(' ', offset);
  						pos2 = str.indexOf('\u00a0', offset);
  						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
  					}
  
  					return pos;
  				}
  
  				if (container.nodeType === 3) {
  					pos = findSpace(container, offset);
  
  					if (pos !== -1) {
  						return {container: container, offset: pos};
  					}
  
  					lastTextNode = container;
  				}
  
  				// Walk the nodes inside the block
  				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
  				while ((node = walker[start ? 'prev' : 'next']())) {
  					if (node.nodeType === 3) {
  						lastTextNode = node;
  						pos = findSpace(node);
  
  						if (pos !== -1) {
  							return {container: node, offset: pos};
  						}
  					} else if (isBlock(node)) {
  						break;
  					}
  				}
  
  				if (lastTextNode) {
  					if (start) {
  						offset = 0;
  					} else {
  						offset = lastTextNode.length;
  					}
  
  					return {container: lastTextNode, offset: offset};
  				}
  			}
  
  			function findSelectorEndPoint(container, sibling_name) {
  				var parents, i, y, curFormat;
  
  				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
  					container = container[sibling_name];
  				}
  
  				parents = getParents(container);
  				for (i = 0; i < parents.length; i++) {
  					for (y = 0; y < format.length; y++) {
  						curFormat = format[y];
  
  						// If collapsed state is set then skip formats that doesn't match that
  						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
  							continue;
  						}
  
  						if (dom.is(parents[i], curFormat.selector)) {
  							return parents[i];
  						}
  					}
  				}
  
  				return container;
  			}
  
  			function findBlockEndPoint(container, sibling_name) {
  				var node, root = dom.getRoot();
  
  				// Expand to block of similar type
  				if (!format[0].wrapper) {
  					node = dom.getParent(container, format[0].block, root);
  				}
  
  				// Expand to first wrappable block element or any block element
  				if (!node) {
  					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
  						// Fixes #6183 where it would expand to editable parent element in inline mode
  						return node != root && isTextBlock(node);
  					});
  				}
  
  				// Exclude inner lists from wrapping
  				if (node && format[0].wrapper) {
  					node = getParents(node, 'ul,ol').reverse()[0] || node;
  				}
  
  				// Didn't find a block element look for first/last wrappable element
  				if (!node) {
  					node = container;
  
  					while (node[sibling_name] && !isBlock(node[sibling_name])) {
  						node = node[sibling_name];
  
  						// Break on BR but include it will be removed later on
  						// we can't remove it now since we need to check if it can be wrapped
  						if (isEq(node, 'br')) {
  							break;
  						}
  					}
  				}
  
  				return node || container;
  			}
  
  			// Expand to closest contentEditable element
  			startContainer = findParentContentEditable(startContainer);
  			endContainer = findParentContentEditable(endContainer);
  
  			// Exclude bookmark nodes if possible
  			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
  				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
  				startContainer = startContainer.nextSibling || startContainer;
  
  				if (startContainer.nodeType == 3) {
  					startOffset = 0;
  				}
  			}
  
  			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
  				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
  				endContainer = endContainer.previousSibling || endContainer;
  
  				if (endContainer.nodeType == 3) {
  					endOffset = endContainer.length;
  				}
  			}
  
  			if (format[0].inline) {
  				if (rng.collapsed) {
  					// Expand left to closest word boundary
  					endPoint = findWordEndPoint(startContainer, startOffset, true);
  					if (endPoint) {
  						startContainer = endPoint.container;
  						startOffset = endPoint.offset;
  					}
  
  					// Expand right to closest word boundary
  					endPoint = findWordEndPoint(endContainer, endOffset);
  					if (endPoint) {
  						endContainer = endPoint.container;
  						endOffset = endPoint.offset;
  					}
  				}
  
  				// Avoid applying formatting to a trailing space.
  				leaf = findLeaf(endContainer, endOffset);
  				if (leaf.node) {
  					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
  						leaf = findLeaf(leaf.node.previousSibling);
  					}
  
  					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
  							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
  
  						if (leaf.offset > 1) {
  							endContainer = leaf.node;
  							endContainer.splitText(leaf.offset - 1);
  						}
  					}
  				}
  			}
  
  			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
  			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
  			// This will reduce the number of wrapper elements that needs to be created
  			// Move start point up the tree
  			if (format[0].inline || format[0].block_expand) {
  				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
  					startContainer = findParentContainer(true);
  				}
  
  				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
  					endContainer = findParentContainer();
  				}
  			}
  
  			// Expand start/end container to matching selector
  			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
  				// Find new startContainer/endContainer if there is better one
  				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
  				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
  			}
  
  			// Expand start/end container to matching block element or text node
  			if (format[0].block || format[0].selector) {
  				// Find new startContainer/endContainer if there is better one
  				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
  				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
  
  				// Non block element then try to expand up the leaf
  				if (format[0].block) {
  					if (!isBlock(startContainer)) {
  						startContainer = findParentContainer(true);
  					}
  
  					if (!isBlock(endContainer)) {
  						endContainer = findParentContainer();
  					}
  				}
  			}
  
  			// Setup index for startContainer
  			if (startContainer.nodeType == 1) {
  				startOffset = nodeIndex(startContainer);
  				startContainer = startContainer.parentNode;
  			}
  
  			// Setup index for endContainer
  			if (endContainer.nodeType == 1) {
  				endOffset = nodeIndex(endContainer) + 1;
  				endContainer = endContainer.parentNode;
  			}
  
  			// Return new range like object
  			return {
  				startContainer: startContainer,
  				startOffset: startOffset,
  				endContainer: endContainer,
  				endOffset: endOffset
  			};
  		}
  
  		function isColorFormatAndAnchor(node, format) {
  			return format.links && node.tagName == 'A';
  		}
  
  		/**
  		 * Removes the specified format for the specified node. It will also remove the node if it doesn't have
  		 * any attributes if the format specifies it to do so.
  		 *
  		 * @private
  		 * @param {Object} format Format object with items to remove from node.
  		 * @param {Object} vars Name/value object with variables to apply to format.
  		 * @param {Node} node Node to remove the format styles on.
  		 * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
  		 * @return {Boolean} True/false if the node was removed or not.
  		 */
  		function removeFormat(format, vars, node, compare_node) {
  			var i, attrs, stylesModified;
  
  			// Check if node matches format
  			if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) {
  				return FALSE;
  			}
  
  			// Should we compare with format attribs and styles
  			if (format.remove != 'all') {
  				// Remove styles
  				each(format.styles, function(value, name) {
  					value = normalizeStyleValue(replaceVars(value, vars), name);
  
  					// Indexed array
  					if (typeof name === 'number') {
  						name = value;
  						compare_node = 0;
  					}
  
  					if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
  						dom.setStyle(node, name, '');
  					}
  
  					stylesModified = 1;
  				});
  
  				// Remove style attribute if it's empty
  				if (stylesModified && dom.getAttrib(node, 'style') === '') {
  					node.removeAttribute('style');
  					node.removeAttribute('data-mce-style');
  				}
  
  				// Remove attributes
  				each(format.attributes, function(value, name) {
  					var valueOut;
  
  					value = replaceVars(value, vars);
  
  					// Indexed array
  					if (typeof name === 'number') {
  						name = value;
  						compare_node = 0;
  					}
  
  					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
  						// Keep internal classes
  						if (name == 'class') {
  							value = dom.getAttrib(node, name);
  							if (value) {
  								// Build new class value where everything is removed except the internal prefixed classes
  								valueOut = '';
  								each(value.split(/\s+/), function(cls) {
  									if (/mce\-\w+/.test(cls)) {
  										valueOut += (valueOut ? ' ' : '') + cls;
  									}
  								});
  
  								// We got some internal classes left
  								if (valueOut) {
  									dom.setAttrib(node, name, valueOut);
  									return;
  								}
  							}
  						}
  
  						// IE6 has a bug where the attribute doesn't get removed correctly
  						if (name == "class") {
  							node.removeAttribute('className');
  						}
  
  						// Remove mce prefixed attributes
  						if (MCE_ATTR_RE.test(name)) {
  							node.removeAttribute('data-mce-' + name);
  						}
  
  						node.removeAttribute(name);
  					}
  				});
  
  				// Remove classes
  				each(format.classes, function(value) {
  					value = replaceVars(value, vars);
  
  					if (!compare_node || dom.hasClass(compare_node, value)) {
  						dom.removeClass(node, value);
  					}
  				});
  
  				// Check for non internal attributes
  				attrs = dom.getAttribs(node);
  				for (i = 0; i < attrs.length; i++) {
  					if (attrs[i].nodeName.indexOf('_') !== 0) {
  						return FALSE;
  					}
  				}
  			}
  
  			// Remove the inline child if it's empty for example <b> or <span>
  			if (format.remove != 'none') {
  				removeNode(node, format);
  				return TRUE;
  			}
  		}
  
  		/**
  		 * Removes the node and wrap it's children in paragraphs before doing so or
  		 * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
  		 *
  		 * If the div in the node below gets removed:
  		 *  text<div>text</div>text
  		 *
  		 * Output becomes:
  		 *  text<div><br />text<br /></div>text
  		 *
  		 * So when the div is removed the result is:
  		 *  text<br />text<br />text
  		 *
  		 * @private
  		 * @param {Node} node Node to remove + apply BR/P elements to.
  		 * @param {Object} format Format rule.
  		 * @return {Node} Input node.
  		 */
  		function removeNode(node, format) {
  			var parentNode = node.parentNode, rootBlockElm;
  
  			function find(node, next, inc) {
  				node = getNonWhiteSpaceSibling(node, next, inc);
  
  				return !node || (node.nodeName == 'BR' || isBlock(node));
  			}
  
  			if (format.block) {
  				if (!forcedRootBlock) {
  					// Append BR elements if needed before we remove the block
  					if (isBlock(node) && !isBlock(parentNode)) {
  						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
  							node.insertBefore(dom.create('br'), node.firstChild);
  						}
  
  						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
  							node.appendChild(dom.create('br'));
  						}
  					}
  				} else {
  					// Wrap the block in a forcedRootBlock if we are at the root of document
  					if (parentNode == dom.getRoot()) {
  						if (!format.list_block || !isEq(node, format.list_block)) {
  							each(grep(node.childNodes), function(node) {
  								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
  									if (!rootBlockElm) {
  										rootBlockElm = wrap(node, forcedRootBlock);
  										dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
  									} else {
  										rootBlockElm.appendChild(node);
  									}
  								} else {
  									rootBlockElm = 0;
  								}
  							});
  						}
  					}
  				}
  			}
  
  			// Never remove nodes that isn't the specified inline element if a selector is specified too
  			if (format.selector && format.inline && !isEq(format.inline, node)) {
  				return;
  			}
  
  			dom.remove(node, 1);
  		}
  
  		/**
  		 * Returns the next/previous non whitespace node.
  		 *
  		 * @private
  		 * @param {Node} node Node to start at.
  		 * @param {boolean} next (Optional) Include next or previous node defaults to previous.
  		 * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
  		 * @return {Node} Next or previous node or undefined if it wasn't found.
  		 */
  		function getNonWhiteSpaceSibling(node, next, inc) {
  			if (node) {
  				next = next ? 'nextSibling' : 'previousSibling';
  
  				for (node = inc ? node : node[next]; node; node = node[next]) {
  					if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
  						return node;
  					}
  				}
  			}
  		}
  
  		/**
  		 * Merges the next/previous sibling element if they match.
  		 *
  		 * @private
  		 * @param {Node} prev Previous node to compare/merge.
  		 * @param {Node} next Next node to compare/merge.
  		 * @return {Node} Next node if we didn't merge and prev node if we did.
  		 */
  		function mergeSiblings(prev, next) {
  			var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
  
  			function findElementSibling(node, sibling_name) {
  				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
  					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
  						return node;
  					}
  
  					if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
  						return sibling;
  					}
  				}
  
  				return node;
  			}
  
  			// Check if next/prev exists and that they are elements
  			if (prev && next) {
  				// If previous sibling is empty then jump over it
  				prev = findElementSibling(prev, 'previousSibling');
  				next = findElementSibling(next, 'nextSibling');
  
  				// Compare next and previous nodes
  				if (elementUtils.compare(prev, next)) {
  					// Append nodes between
  					for (sibling = prev.nextSibling; sibling && sibling != next;) {
  						tmpSibling = sibling;
  						sibling = sibling.nextSibling;
  						prev.appendChild(tmpSibling);
  					}
  
  					// Remove next node
  					dom.remove(next);
  
  					// Move children into prev node
  					each(grep(next.childNodes), function(node) {
  						prev.appendChild(node);
  					});
  
  					return prev;
  				}
  			}
  
  			return next;
  		}
  
  		function getContainer(rng, start) {
  			var container, offset, lastIdx;
  
  			container = rng[start ? 'startContainer' : 'endContainer'];
  			offset = rng[start ? 'startOffset' : 'endOffset'];
  
  			if (container.nodeType == 1) {
  				lastIdx = container.childNodes.length - 1;
  
  				if (!start && offset) {
  					offset--;
  				}
  
  				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
  			}
  
  			// If start text node is excluded then walk to the next node
  			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
  				container = new TreeWalker(container, ed.getBody()).next() || container;
  			}
  
  			// If end text node is excluded then walk to the previous node
  			if (container.nodeType === 3 && !start && offset === 0) {
  				container = new TreeWalker(container, ed.getBody()).prev() || container;
  			}
  
  			return container;
  		}
  
  		function performCaretAction(type, name, vars, similar) {
  			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
  
  			// Creates a caret container bogus element
  			function createCaretContainer(fill) {
  				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
  
  				if (fill) {
  					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
  				}
  
  				return caretContainer;
  			}
  
  			function isCaretContainerEmpty(node, nodes) {
  				while (node) {
  					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
  						return false;
  					}
  
  					// Collect nodes
  					if (nodes && node.nodeType === 1) {
  						nodes.push(node);
  					}
  
  					node = node.firstChild;
  				}
  
  				return true;
  			}
  
  			// Returns any parent caret container element
  			function getParentCaretContainer(node) {
  				while (node) {
  					if (node.id === caretContainerId) {
  						return node;
  					}
  
  					node = node.parentNode;
  				}
  			}
  
  			// Finds the first text node in the specified node
  			function findFirstTextNode(node) {
  				var walker;
  
  				if (node) {
  					walker = new TreeWalker(node, node);
  
  					for (node = walker.current(); node; node = walker.next()) {
  						if (node.nodeType === 3) {
  							return node;
  						}
  					}
  				}
  			}
  
  			// Removes the caret container for the specified node or all on the current document
  			function removeCaretContainer(node, move_caret) {
  				var child, rng;
  
  				if (!node) {
  					node = getParentCaretContainer(selection.getStart());
  
  					if (!node) {
  						while ((node = dom.get(caretContainerId))) {
  							removeCaretContainer(node, false);
  						}
  					}
  				} else {
  					rng = selection.getRng(true);
  
  					if (isCaretContainerEmpty(node)) {
  						if (move_caret !== false) {
  							rng.setStartBefore(node);
  							rng.setEndBefore(node);
  						}
  
  						dom.remove(node);
  					} else {
  						child = findFirstTextNode(node);
  
  						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
  							child.deleteData(0, 1);
  
  							// Fix for bug #6976
  							if (rng.startContainer == child && rng.startOffset > 0) {
  								rng.setStart(child, rng.startOffset - 1);
  							}
  
  							if (rng.endContainer == child && rng.endOffset > 0) {
  								rng.setEnd(child, rng.endOffset - 1);
  							}
  						}
  
  						dom.remove(node, 1);
  					}
  
  					selection.setRng(rng);
  				}
  			}
  
  			// Applies formatting to the caret postion
  			function applyCaretFormat() {
  				var rng, caretContainer, textNode, offset, bookmark, container, text;
  
  				rng = selection.getRng(true);
  				offset = rng.startOffset;
  				container = rng.startContainer;
  				text = container.nodeValue;
  
  				caretContainer = getParentCaretContainer(selection.getStart());
  				if (caretContainer) {
  					textNode = findFirstTextNode(caretContainer);
  				}
  
  				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
  				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
  					// Get bookmark of caret position
  					bookmark = selection.getBookmark();
  
  					// Collapse bookmark range (WebKit)
  					rng.collapse(true);
  
  					// Expand the range to the closest word and split it at those points
  					rng = expandRng(rng, get(name));
  					rng = rangeUtils.split(rng);
  
  					// Apply the format to the range
  					apply(name, vars, rng);
  
  					// Move selection back to caret position
  					selection.moveToBookmark(bookmark);
  				} else {
  					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
  						caretContainer = createCaretContainer(true);
  						textNode = caretContainer.firstChild;
  
  						rng.insertNode(caretContainer);
  						offset = 1;
  
  						apply(name, vars, caretContainer);
  					} else {
  						apply(name, vars, caretContainer);
  					}
  
  					// Move selection to text node
  					selection.setCursorLocation(textNode, offset);
  				}
  			}
  
  			function removeCaretFormat() {
  				var rng = selection.getRng(true), container, offset, bookmark,
  					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
  
  				container = rng.startContainer;
  				offset = rng.startOffset;
  				node = container;
  
  				if (container.nodeType == 3) {
  					if (offset != container.nodeValue.length) {
  						hasContentAfter = true;
  					}
  
  					node = node.parentNode;
  				}
  
  				while (node) {
  					if (matchNode(node, name, vars, similar)) {
  						formatNode = node;
  						break;
  					}
  
  					if (node.nextSibling) {
  						hasContentAfter = true;
  					}
  
  					parents.push(node);
  					node = node.parentNode;
  				}
  
  				// Node doesn't have the specified format
  				if (!formatNode) {
  					return;
  				}
  
  				// Is there contents after the caret then remove the format on the element
  				if (hasContentAfter) {
  					// Get bookmark of caret position
  					bookmark = selection.getBookmark();
  
  					// Collapse bookmark range (WebKit)
  					rng.collapse(true);
  
  					// Expand the range to the closest word and split it at those points
  					rng = expandRng(rng, get(name), true);
  					rng = rangeUtils.split(rng);
  
  					// Remove the format from the range
  					remove(name, vars, rng);
  
  					// Move selection back to caret position
  					selection.moveToBookmark(bookmark);
  				} else {
  					caretContainer = createCaretContainer();
  
  					node = caretContainer;
  					for (i = parents.length - 1; i >= 0; i--) {
  						node.appendChild(dom.clone(parents[i], false));
  						node = node.firstChild;
  					}
  
  					// Insert invisible character into inner most format element
  					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
  					node = node.firstChild;
  
  					var block = dom.getParent(formatNode, isTextBlock);
  
  					if (block && dom.isEmpty(block)) {
  						// Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
  						formatNode.parentNode.replaceChild(caretContainer, formatNode);
  					} else {
  						// Insert caret container after the formated node
  						dom.insertAfter(caretContainer, formatNode);
  					}
  
  					// Move selection to text node
  					selection.setCursorLocation(node, 1);
  
  					// If the formatNode is empty, we can remove it safely.
  					if (dom.isEmpty(formatNode)) {
  						dom.remove(formatNode);
  					}
  				}
  			}
  
  			// Checks if the parent caret container node isn't empty if that is the case it
  			// will remove the bogus state on all children that isn't empty
  			function unmarkBogusCaretParents() {
  				var caretContainer;
  
  				caretContainer = getParentCaretContainer(selection.getStart());
  				if (caretContainer && !dom.isEmpty(caretContainer)) {
  					walk(caretContainer, function(node) {
  						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
  							dom.setAttrib(node, 'data-mce-bogus', null);
  						}
  					}, 'childNodes');
  				}
  			}
  
  			// Only bind the caret events once
  			if (!ed._hasCaretEvents) {
  				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
  				markCaretContainersBogus = function() {
  					var nodes = [], i;
  
  					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
  						// Mark children
  						i = nodes.length;
  						while (i--) {
  							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
  						}
  					}
  				};
  
  				disableCaretContainer = function(e) {
  					var keyCode = e.keyCode;
  
  					removeCaretContainer();
  
  					// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
  					// Backspace key needs to check if the range is collapsed due to bug #6780
  					if ((keyCode == 8 && selection.isCollapsed()) || keyCode == 37 || keyCode == 39) {
  						removeCaretContainer(getParentCaretContainer(selection.getStart()));
  					}
  
  					unmarkBogusCaretParents();
  				};
  
  				// Remove bogus state if they got filled by contents using editor.selection.setContent
  				ed.on('SetContent', function(e) {
  					if (e.selection) {
  						unmarkBogusCaretParents();
  					}
  				});
  				ed._hasCaretEvents = true;
  			}
  
  			// Do apply or remove caret format
  			if (type == "apply") {
  				applyCaretFormat();
  			} else {
  				removeCaretFormat();
  			}
  		}
  
  		/**
  		 * Moves the start to the first suitable text node.
  		 */
  		function moveStart(rng) {
  			var container = rng.startContainer,
  					offset = rng.startOffset, isAtEndOfText,
  					walker, node, nodes, tmpNode;
  
  			// Convert text node into index if possible
  			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
  				// Get the parent container location and walk from there
  				offset = nodeIndex(container);
  				container = container.parentNode;
  				isAtEndOfText = true;
  			}
  
  			// Move startContainer/startOffset in to a suitable node
  			if (container.nodeType == 1) {
  				nodes = container.childNodes;
  				container = nodes[Math.min(offset, nodes.length - 1)];
  				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
  
  				// If offset is at end of the parent node walk to the next one
  				if (offset > nodes.length - 1 || isAtEndOfText) {
  					walker.next();
  				}
  
  				for (node = walker.current(); node; node = walker.next()) {
  					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
  						// IE has a "neat" feature where it moves the start node into the closest element
  						// we can avoid this by inserting an element before it and then remove it after we set the selection
  						tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
  						node.parentNode.insertBefore(tmpNode, node);
  
  						// Set selection and remove tmpNode
  						rng.setStart(node, 0);
  						selection.setRng(rng);
  						dom.remove(tmpNode);
  
  						return;
  					}
  				}
  			}
  		}
  	};
  });
  
  // Included from: js/maya/classes/UndoManager.js
  
  /**
   * UndoManager.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed.
   *
   * @class maya.UndoManager
   */
  define("maya/UndoManager", [
  	"maya/util/VK",
  	"maya/Env",
  	"maya/util/Tools",
  	"maya/html/SaxParser"
  ], function(VK, Env, Tools, SaxParser) {
  	var trim = Tools.trim, trimContentRegExp;
  
  	trimContentRegExp = new RegExp([
  		'<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
  		'\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
  	].join('|'), 'gi');
  
  	return function(editor) {
  		var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
  
  		/**
  		 * Returns a trimmed version of the editor contents to be used for the undo level. This
  		 * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also
  		 * remove the data-mce-selected attributes used for selection of objects and caret containers.
  		 * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will
  		 * be removed by the serialization logic when you save.
  		 *
  		 * @private
  		 * @return {String} HTML contents of the editor excluding some internal bogus elements.
  		 */
  		function getContent() {
  			var content = editor.getContent({format: 'raw', no_events: 1});
  			var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
  			var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;
  
  			content = content.replace(trimContentRegExp, '');
  			shortEndedElements = schema.getShortEndedElements();
  
  			// Remove all bogus elements marked with "all"
  			while ((matches = bogusAllRegExp.exec(content))) {
  				index = bogusAllRegExp.lastIndex;
  				matchLength = matches[0].length;
  
  				if (shortEndedElements[matches[1]]) {
  					endTagIndex = index;
  				} else {
  					endTagIndex = SaxParser.findEndTag(schema, content, index);
  				}
  
  				content = content.substring(0, index - matchLength) + content.substring(endTagIndex);
  				bogusAllRegExp.lastIndex = index - matchLength;
  			}
  
  			return trim(content);
  		}
  
  		function addNonTypingUndoLevel(e) {
  			self.typing = false;
  			self.add({}, e);
  		}
  
  		// Add initial undo level when the editor is initialized
  		editor.on('init', function() {
  			self.add();
  		});
  
  		// Get position before an execCommand is processed
  		editor.on('BeforeExecCommand', function(e) {
  			var cmd = e.command;
  
  			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  				self.beforeChange();
  			}
  		});
  
  		// Add undo level after an execCommand call was made
  		editor.on('ExecCommand', function(e) {
  			var cmd = e.command;
  
  			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  				addNonTypingUndoLevel(e);
  			}
  		});
  
  		editor.on('ObjectResizeStart', function() {
  			self.beforeChange();
  		});
  
  		editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
  		editor.on('DragEnd', addNonTypingUndoLevel);
  
  		editor.on('KeyUp', function(e) {
  			var keyCode = e.keyCode;
  
  			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
  				addNonTypingUndoLevel();
  				editor.nodeChanged();
  			}
  
  			if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
  				editor.nodeChanged();
  			}
  
  			// Fire a TypingUndo event on the first character entered
  			if (isFirstTypedCharacter && self.typing) {
  				// Make the it dirty if the content was changed after typing the first character
  				if (!editor.isDirty()) {
  					editor.isNotDirty = !data[0] || getContent() == data[0].content;
  
  					// Fire initial change event
  					if (!editor.isNotDirty) {
  						editor.fire('change', {level: data[0], lastLevel: null});
  					}
  				}
  
  				editor.fire('TypingUndo');
  				isFirstTypedCharacter = false;
  				editor.nodeChanged();
  			}
  		});
  
  		editor.on('KeyDown', function(e) {
  			var keyCode = e.keyCode;
  
  			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
  			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
  				if (self.typing) {
  					addNonTypingUndoLevel(e);
  				}
  
  				return;
  			}
  
  			// If key isn't shift,ctrl,alt,capslock,metakey
  			var modKey = VK.modifierPressed(e);
  			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) {
  				self.beforeChange();
  				self.typing = true;
  				self.add({}, e);
  				isFirstTypedCharacter = true;
  			}
  		});
  
  		editor.on('MouseDown', function(e) {
  			if (self.typing) {
  				addNonTypingUndoLevel(e);
  			}
  		});
  
  		// Add keyboard shortcuts for undo/redo keys
  		editor.addShortcut('meta+z', '', 'Undo');
  		editor.addShortcut('meta+y,meta+shift+z', '', 'Redo');
  
  		editor.on('AddUndo Undo Redo ClearUndos', function(e) {
  			if (!e.isDefaultPrevented()) {
  				editor.nodeChanged();
  			}
  		});
  
  		self = {
  			// Explose for debugging reasons
  			data: data,
  
  			/**
  			 * State if the user is currently typing or not. This will add a typing operation into one undo
  			 * level instead of one new level for each keystroke.
  			 *
  			 * @field {Boolean} typing
  			 */
  			typing: false,
  
  			/**
  			 * Stores away a bookmark to be used when performing an undo action so that the selection is before
  			 * the change has been made.
  			 *
  			 * @method beforeChange
  			 */
  			beforeChange: function() {
  				if (!locks) {
  					beforeBookmark = editor.selection.getBookmark(2, true);
  				}
  			},
  
  			/**
  			 * Adds a new undo level/snapshot to the undo list.
  			 *
  			 * @method add
  			 * @param {Object} level Optional undo level object to add.
  			 * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
  			 * @return {Object} Undo level that got added or null it a level wasn't needed.
  			 */
  			add: function(level, event) {
  				var i, settings = editor.settings, lastLevel;
  
  				level = level || {};
  				level.content = getContent();
  
  				if (locks || editor.removed) {
  					return null;
  				}
  
  				lastLevel = data[index];
  				if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
  					return null;
  				}
  
  				// Add undo level if needed
  				if (lastLevel && lastLevel.content == level.content) {
  					return null;
  				}
  
  				// Set before bookmark on previous level
  				if (data[index]) {
  					data[index].beforeBookmark = beforeBookmark;
  				}
  
  				// Time to compress
  				if (settings.custom_undo_redo_levels) {
  					if (data.length > settings.custom_undo_redo_levels) {
  						for (i = 0; i < data.length - 1; i++) {
  							data[i] = data[i + 1];
  						}
  
  						data.length--;
  						index = data.length;
  					}
  				}
  
  				// Get a non intrusive normalized bookmark
  				level.bookmark = editor.selection.getBookmark(2, true);
  
  				// Crop array if needed
  				if (index < data.length - 1) {
  					data.length = index + 1;
  				}
  
  				data.push(level);
  				index = data.length - 1;
  
  				var args = {level: level, lastLevel: lastLevel, originalEvent: event};
  
  				editor.fire('AddUndo', args);
  
  				if (index > 0) {
  					editor.isNotDirty = false;
  					editor.fire('change', args);
  				}
  
  				return level;
  			},
  
  			/**
  			 * Undoes the last action.
  			 *
  			 * @method undo
  			 * @return {Object} Undo level or null if no undo was performed.
  			 */
  			undo: function() {
  				var level;
  
  				if (self.typing) {
  					self.add();
  					self.typing = false;
  				}
  
  				if (index > 0) {
  					level = data[--index];
  
  					// Undo to first index then set dirty state to false
  					if (index === 0) {
  						editor.isNotDirty = true;
  					}
  
  					editor.setContent(level.content, {format: 'raw'});
  					editor.selection.moveToBookmark(level.beforeBookmark);
  
  					editor.fire('undo', {level: level});
  				}
  
  				return level;
  			},
  
  			/**
  			 * Redoes the last action.
  			 *
  			 * @method redo
  			 * @return {Object} Redo level or null if no redo was performed.
  			 */
  			redo: function() {
  				var level;
  
  				if (index < data.length - 1) {
  					level = data[++index];
  
  					editor.setContent(level.content, {format: 'raw'});
  					editor.selection.moveToBookmark(level.bookmark);
  
  					editor.fire('redo', {level: level});
  				}
  
  				return level;
  			},
  
  			/**
  			 * Removes all undo levels.
  			 *
  			 * @method clear
  			 */
  			clear: function() {
  				data = [];
  				index = 0;
  				self.typing = false;
  				editor.fire('ClearUndos');
  			},
  
  			/**
  			 * Returns true/false if the undo manager has any undo levels.
  			 *
  			 * @method hasUndo
  			 * @return {Boolean} true/false if the undo manager has any undo levels.
  			 */
  			hasUndo: function() {
  				// Has undo levels or typing and content isn't the same as the initial level
  				return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
  			},
  
  			/**
  			 * Returns true/false if the undo manager has any redo levels.
  			 *
  			 * @method hasRedo
  			 * @return {Boolean} true/false if the undo manager has any redo levels.
  			 */
  			hasRedo: function() {
  				return index < data.length - 1 && !this.typing;
  			},
  
  			/**
  			 * Executes the specified function in an undo transation. The selection
  			 * before the modification will be stored to the undo stack and if the DOM changes
  			 * it will add a new undo level. Any methods within the transation that adds undo levels will
  			 * be ignored. So a transation can include calls to execCommand or editor.insertContent.
  			 *
  			 * @method transact
  			 * @param {function} callback Function to execute dom manipulation logic in.
  			 */
  			transact: function(callback) {
  				self.beforeChange();
  
  				try {
  					locks++;
  					callback();
  				} finally {
  					locks--;
  				}
  
  				self.add();
  			}
  		};
  
  		return self;
  	};
  });
  
  // Included from: js/maya/classes/EnterKey.js
  
  /**
   * EnterKey.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Contains logic for handling the enter key to split/generate block elements.
   */
  define("maya/EnterKey", [
  	"maya/dom/TreeWalker",
  	"maya/dom/RangeUtils",
  	"maya/Env"
  ], function(TreeWalker, RangeUtils, Env) {
  	var isIE = Env.ie && Env.ie < 11;
  
  	return function(editor) {
  		var dom = editor.dom, selection = editor.selection, settings = editor.settings;
  		var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(),
  			moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements();
  
  		function handleEnterKey(evt) {
  			var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
  				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
  
  			// Returns true if the block can be split into two blocks or not
  			function canSplitBlock(node) {
  				return node &&
  					dom.isBlock(node) &&
  					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
  					!/^(fixed|absolute)/i.test(node.style.position) &&
  					dom.getContentEditable(node) !== "true";
  			}
  
  			// Renders empty block on IE
  			function renderBlockOnIE(block) {
  				var oldRng;
  
  				if (dom.isBlock(block)) {
  					oldRng = selection.getRng();
  					block.appendChild(dom.create('span', null, '\u00a0'));
  					selection.select(block);
  					block.lastChild.outerHTML = '';
  					selection.setRng(oldRng);
  				}
  			}
  
  			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
  			function trimInlineElementsOnLeftSideOfBlock(block) {
  				var node = block, firstChilds = [], i;
  
  				if (!node) {
  					return;
  				}
  
  				// Find inner most first child ex: <p><i><b>*</b></i></p>
  				while ((node = node.firstChild)) {
  					if (dom.isBlock(node)) {
  						return;
  					}
  
  					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  						firstChilds.push(node);
  					}
  				}
  
  				i = firstChilds.length;
  				while (i--) {
  					node = firstChilds[i];
  					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
  						dom.remove(node);
  					} else {
  						// Remove <a> </a> see #5381
  						if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
  							dom.remove(node);
  						}
  					}
  				}
  			}
  
  			// Moves the caret to a suitable position within the root for example in the first non
  			// pure whitespace text node or before an image
  			function moveToCaretPosition(root) {
  				var walker, node, rng, lastNode = root, tempElm;
  				function firstNonWhiteSpaceNodeSibling(node) {
  					while (node) {
  						if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
  							return node;
  						}
  
  						node = node.nextSibling;
  					}
  				}
  
  				if (!root) {
  					return;
  				}
  
  				// Old IE versions doesn't properly render blocks with br elements in them
  				// For example <p><br></p> wont be rendered correctly in a contentEditable area
  				// until you remove the br producing <p></p>
  				if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
  					if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
  						dom.remove(parentBlock.firstChild);
  					}
  				}
  
  				if (/^(LI|DT|DD)$/.test(root.nodeName)) {
  					var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
  
  					if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
  						root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
  					}
  				}
  
  				rng = dom.createRng();
  
  				// Normalize whitespace to remove empty text nodes. Fix for: #6904
  				// Gecko will be able to place the caret in empty text nodes but it won't render propery
  				// Older IE versions will sometimes crash so for now ignore all IE versions
  				if (!Env.ie) {
  					root.normalize();
  				}
  
  				if (root.hasChildNodes()) {
  					walker = new TreeWalker(root, root);
  
  					while ((node = walker.current())) {
  						if (node.nodeType == 3) {
  							rng.setStart(node, 0);
  							rng.setEnd(node, 0);
  							break;
  						}
  
  						if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
  							rng.setStartBefore(node);
  							rng.setEndBefore(node);
  							break;
  						}
  
  						lastNode = node;
  						node = walker.next();
  					}
  
  					if (!node) {
  						rng.setStart(lastNode, 0);
  						rng.setEnd(lastNode, 0);
  					}
  				} else {
  					if (root.nodeName == 'BR') {
  						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
  							// Trick on older IE versions to render the caret before the BR between two lists
  							if (!documentMode || documentMode < 9) {
  								tempElm = dom.create('br');
  								root.parentNode.insertBefore(tempElm, root);
  							}
  
  							rng.setStartBefore(root);
  							rng.setEndBefore(root);
  						} else {
  							rng.setStartAfter(root);
  							rng.setEndAfter(root);
  						}
  					} else {
  						rng.setStart(root, 0);
  						rng.setEnd(root, 0);
  					}
  				}
  
  				selection.setRng(rng);
  
  				// Remove tempElm created for old IE:s
  				dom.remove(tempElm);
  				selection.scrollIntoView(root);
  			}
  
  			function setForcedBlockAttrs(node) {
  				var forcedRootBlockName = settings.forced_root_block;
  
  				if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
  					dom.setAttribs(node, settings.forced_root_block_attrs);
  				}
  			}
  
  			// Creates a new block element by cloning the current one or creating a new one if the name is specified
  			// This function will also copy any text formatting from the parent block and add it to the new one
  			function createNewBlock(name) {
  				var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
  
  				if (name || parentBlockName == "TABLE") {
  					block = dom.create(name || newBlockName);
  					setForcedBlockAttrs(block);
  				} else {
  					block = parentBlock.cloneNode(false);
  				}
  
  				caretNode = block;
  
  				// Clone any parent styles
  				if (settings.keep_styles !== false) {
  					do {
  						if (textInlineElements[node.nodeName]) {
  							// Never clone a caret containers
  							if (node.id == '_mce_caret') {
  								continue;
  							}
  
  							clonedNode = node.cloneNode(false);
  							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
  
  							if (block.hasChildNodes()) {
  								clonedNode.appendChild(block.firstChild);
  								block.appendChild(clonedNode);
  							} else {
  								caretNode = clonedNode;
  								block.appendChild(clonedNode);
  							}
  						}
  					} while ((node = node.parentNode));
  				}
  
  				// BR is needed in empty blocks on non IE browsers
  				if (!isIE) {
  					caretNode.innerHTML = '<br data-mce-bogus="1">';
  				}
  
  				return block;
  			}
  
  			// Returns true/false if the caret is at the start/end of the parent block element
  			function isCaretAtStartOrEndOfBlock(start) {
  				var walker, node, name;
  
  				// Caret is in the middle of a text node like "a|b"
  				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
  					return false;
  				}
  
  				// If after the last element in block node edge case for #5091
  				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
  					return true;
  				}
  
  				// If the caret if before the first element in parentBlock
  				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
  					return true;
  				}
  
  				// Caret can be before/after a table
  				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
  					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
  				}
  
  				// Walk the DOM and look for text nodes or non empty elements
  				walker = new TreeWalker(container, parentBlock);
  
  				// If caret is in beginning or end of a text block then jump to the next/previous node
  				if (container.nodeType == 3) {
  					if (start && offset === 0) {
  						walker.prev();
  					} else if (!start && offset == container.nodeValue.length) {
  						walker.next();
  					}
  				}
  
  				while ((node = walker.current())) {
  					if (node.nodeType === 1) {
  						// Ignore bogus elements
  						if (!node.getAttribute('data-mce-bogus')) {
  							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
  							name = node.nodeName.toLowerCase();
  							if (nonEmptyElementsMap[name] && name !== 'br') {
  								return false;
  							}
  						}
  					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
  						return false;
  					}
  
  					if (start) {
  						walker.prev();
  					} else {
  						walker.next();
  					}
  				}
  
  				return true;
  			}
  
  			// Wraps any text nodes or inline elements in the specified forced root block name
  			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
  				var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
  
  				// Not in a block element or in a table cell or caption
  				parentBlock = dom.getParent(container, dom.isBlock);
  				rootBlockName = editor.getBody().nodeName.toLowerCase();
  				if (!parentBlock || !canSplitBlock(parentBlock)) {
  					parentBlock = parentBlock || editableRoot;
  
  					if (!parentBlock.hasChildNodes()) {
  						newBlock = dom.create(blockName);
  						setForcedBlockAttrs(newBlock);
  						parentBlock.appendChild(newBlock);
  						rng.setStart(newBlock, 0);
  						rng.setEnd(newBlock, 0);
  						return newBlock;
  					}
  
  					// Find parent that is the first child of parentBlock
  					node = container;
  					while (node.parentNode != parentBlock) {
  						node = node.parentNode;
  					}
  
  					// Loop left to find start node start wrapping at
  					while (node && !dom.isBlock(node)) {
  						startNode = node;
  						node = node.previousSibling;
  					}
  
  					if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
  						newBlock = dom.create(blockName);
  						setForcedBlockAttrs(newBlock);
  						startNode.parentNode.insertBefore(newBlock, startNode);
  
  						// Start wrapping until we hit a block
  						node = startNode;
  						while (node && !dom.isBlock(node)) {
  							next = node.nextSibling;
  							newBlock.appendChild(node);
  							node = next;
  						}
  
  						// Restore range to it's past location
  						rng.setStart(container, offset);
  						rng.setEnd(container, offset);
  					}
  				}
  
  				return container;
  			}
  
  			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
  			function handleEmptyListItem() {
  				function isFirstOrLastLi(first) {
  					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
  
  					// Find first/last element since there might be whitespace there
  					while (node) {
  						if (node.nodeType == 1) {
  							break;
  						}
  
  						node = node[first ? 'nextSibling' : 'previousSibling'];
  					}
  
  					return node === parentBlock;
  				}
  
  				function getContainerBlock() {
  					var containerBlockParent = containerBlock.parentNode;
  
  					if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
  						return containerBlockParent;
  					}
  
  					return containerBlock;
  				}
  
  				// Check if we are in an nested list
  				var containerBlockParentName = containerBlock.parentNode.nodeName;
  				if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
  					newBlockName = 'LI';
  				}
  
  				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
  
  				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
  					if (containerBlockParentName == 'LI') {
  						// Nested list is inside a LI
  						dom.insertAfter(newBlock, getContainerBlock());
  					} else {
  						// Is first and last list item then replace the OL/UL with a text block
  						dom.replace(newBlock, containerBlock);
  					}
  				} else if (isFirstOrLastLi(true)) {
  					if (containerBlockParentName == 'LI') {
  						// List nested in an LI then move the list to a new sibling LI
  						dom.insertAfter(newBlock, getContainerBlock());
  						newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
  						newBlock.appendChild(containerBlock);
  					} else {
  						// First LI in list then remove LI and add text block before list
  						containerBlock.parentNode.insertBefore(newBlock, containerBlock);
  					}
  				} else if (isFirstOrLastLi()) {
  					// Last LI in list then remove LI and add text block after list
  					dom.insertAfter(newBlock, getContainerBlock());
  					renderBlockOnIE(newBlock);
  				} else {
  					// Middle LI in list the split the list and insert a text block in the middle
  					// Extract after fragment and insert it after the current block
  					containerBlock = getContainerBlock();
  					tmpRng = rng.cloneRange();
  					tmpRng.setStartAfter(parentBlock);
  					tmpRng.setEndAfter(containerBlock);
  					fragment = tmpRng.extractContents();
  
  					if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
  						newBlock = fragment.firstChild;
  						dom.insertAfter(fragment, containerBlock);
  					} else {
  						dom.insertAfter(fragment, containerBlock);
  						dom.insertAfter(newBlock, containerBlock);
  					}
  				}
  
  				dom.remove(parentBlock);
  				moveToCaretPosition(newBlock);
  				undoManager.add();
  			}
  
  			// Inserts a BR element if the forced_root_block option is set to false or empty string
  			function insertBr() {
  				editor.execCommand("InsertLineBreak", false, evt);
  			}
  
  			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
  			function trimLeadingLineBreaks(node) {
  				do {
  					if (node.nodeType === 3) {
  						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
  					}
  
  					node = node.firstChild;
  				} while (node);
  			}
  
  			function getEditableRoot(node) {
  				var root = dom.getRoot(), parent, editableRoot;
  
  				// Get all parents until we hit a non editable parent or the root
  				parent = node;
  				while (parent !== root && dom.getContentEditable(parent) !== "false") {
  					if (dom.getContentEditable(parent) === "true") {
  						editableRoot = parent;
  					}
  
  					parent = parent.parentNode;
  				}
  
  				return parent !== root ? editableRoot : root;
  			}
  
  			// Adds a BR at the end of blocks that only contains an IMG or INPUT since
  			// these might be floated and then they won't expand the block
  			function addBrToBlockIfNeeded(block) {
  				var lastChild;
  
  				// IE will render the blocks correctly other browsers needs a BR
  				if (!isIE) {
  					block.normalize(); // Remove empty text nodes that got left behind by the extract
  
  					// Check if the block is empty or contains a floated last child
  					lastChild = block.lastChild;
  					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
  						dom.add(block, 'br');
  					}
  				}
  			}
  
  			rng = selection.getRng(true);
  
  			// Event is blocked by some other handler for example the lists plugin
  			if (evt.isDefaultPrevented()) {
  				return;
  			}
  
  			// Delete any selected contents
  			if (!rng.collapsed) {
  				editor.execCommand('Delete');
  				return;
  			}
  
  			// Setup range items and newBlockName
  			new RangeUtils(dom).normalize(rng);
  			container = rng.startContainer;
  			offset = rng.startOffset;
  			newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
  			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
  			documentMode = dom.doc.documentMode;
  			shiftKey = evt.shiftKey;
  
  			// Resolve node index
  			if (container.nodeType == 1 && container.hasChildNodes()) {
  				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
  
  				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  				if (isAfterLastNodeInContainer && container.nodeType == 3) {
  					offset = container.nodeValue.length;
  				} else {
  					offset = 0;
  				}
  			}
  
  			// Get editable root node normaly the body element but sometimes a div or span
  			editableRoot = getEditableRoot(container);
  
  			// If there is no editable root then enter is done inside a contentEditable false element
  			if (!editableRoot) {
  				return;
  			}
  
  			undoManager.beforeChange();
  
  			// If editable root isn't block nor the root of the editor
  			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
  				if (!newBlockName || shiftKey) {
  					insertBr();
  				}
  
  				return;
  			}
  
  			// Wrap the current node and it's sibling in a default block if it's needed.
  			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
  			// This won't happen if root blocks are disabled or the shiftKey is pressed
  			if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
  				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
  			}
  
  			// Find parent block and setup empty block paddings
  			parentBlock = dom.getParent(container, dom.isBlock);
  			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
  
  			// Setup block names
  			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  
  			// Enter inside block contained within a LI then split or insert before/after LI
  			if (containerBlockName == 'LI' && !evt.ctrlKey) {
  				parentBlock = containerBlock;
  				parentBlockName = containerBlockName;
  			}
  
  			// Handle enter in list item
  			if (/^(LI|DT|DD)$/.test(parentBlockName)) {
  				if (!newBlockName && shiftKey) {
  					insertBr();
  					return;
  				}
  
  				// Handle enter inside an empty list item
  				if (dom.isEmpty(parentBlock)) {
  					handleEmptyListItem();
  					return;
  				}
  			}
  
  			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
  			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
  				if (!shiftKey) {
  					insertBr();
  					return;
  				}
  			} else {
  				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
  				if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
  					insertBr();
  					return;
  				}
  			}
  
  			// If parent block is root then never insert new blocks
  			if (newBlockName && parentBlock === editor.getBody()) {
  				return;
  			}
  
  			// Default block name if it's not configured
  			newBlockName = newBlockName || 'P';
  
  			// Insert new block before/after the parent block depending on caret location
  			if (isCaretAtStartOrEndOfBlock()) {
  				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
  				if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
  					newBlock = createNewBlock(newBlockName);
  				} else {
  					newBlock = createNewBlock();
  				}
  
  				// Split the current container block element if enter is pressed inside an empty inner block element
  				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
  					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
  					newBlock = dom.split(containerBlock, parentBlock);
  				} else {
  					dom.insertAfter(newBlock, parentBlock);
  				}
  
  				moveToCaretPosition(newBlock);
  			} else if (isCaretAtStartOrEndOfBlock(true)) {
  				// Insert new block before
  				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
  				renderBlockOnIE(newBlock);
  				moveToCaretPosition(parentBlock);
  			} else {
  				// Extract after fragment and insert it after the current block
  				tmpRng = rng.cloneRange();
  				tmpRng.setEndAfter(parentBlock);
  				fragment = tmpRng.extractContents();
  				trimLeadingLineBreaks(fragment);
  				newBlock = fragment.firstChild;
  				dom.insertAfter(fragment, parentBlock);
  				trimInlineElementsOnLeftSideOfBlock(newBlock);
  				addBrToBlockIfNeeded(parentBlock);
  				moveToCaretPosition(newBlock);
  			}
  
  			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
  
  			// Allow custom handling of new blocks
  			editor.fire('NewBlock', {newBlock: newBlock});
  
  			undoManager.add();
  		}
  
  		editor.on('keydown', function(evt) {
  			if (evt.keyCode == 13) {
  				if (handleEnterKey(evt) !== false) {
  					evt.preventDefault();
  				}
  			}
  		});
  	};
  });
  
  // Included from: js/maya/classes/ForceBlocks.js
  
  /**
   * ForceBlocks.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  define("maya/ForceBlocks", [], function() {
  	return function(editor) {
  		var settings = editor.settings, dom = editor.dom, selection = editor.selection;
  		var schema = editor.schema, blockElements = schema.getBlockElements();
  
  		function addRootBlocks() {
  			var node = selection.getStart(), rootNode = editor.getBody(), rng;
  			var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
  			var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
  			var tmpRng, rootNodeName, forcedRootBlock;
  
  			forcedRootBlock = settings.forced_root_block;
  
  			if (!node || node.nodeType !== 1 || !forcedRootBlock) {
  				return;
  			}
  
  			// Check if node is wrapped in block
  			while (node && node != rootNode) {
  				if (blockElements[node.nodeName]) {
  					return;
  				}
  
  				node = node.parentNode;
  			}
  
  			// Get current selection
  			rng = selection.getRng();
  			if (rng.setStart) {
  				startContainer = rng.startContainer;
  				startOffset = rng.startOffset;
  				endContainer = rng.endContainer;
  				endOffset = rng.endOffset;
  
  				try {
  					restoreSelection = editor.getDoc().activeElement === rootNode;
  				} catch (ex) {
  					// IE throws unspecified error here sometimes
  				}
  			} else {
  				// Force control range into text range
  				if (rng.item) {
  					node = rng.item(0);
  					rng = editor.getDoc().body.createTextRange();
  					rng.moveToElementText(node);
  				}
  
  				restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
  				tmpRng = rng.duplicate();
  				tmpRng.collapse(true);
  				startOffset = tmpRng.move('character', offset) * -1;
  
  				if (!tmpRng.collapsed) {
  					tmpRng = rng.duplicate();
  					tmpRng.collapse(false);
  					endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
  				}
  			}
  
  			// Wrap non block elements and text nodes
  			node = rootNode.firstChild;
  			rootNodeName = rootNode.nodeName.toLowerCase();
  			while (node) {
  				// TODO: Break this up, too complex
  				if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
  					schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
  					// Remove empty text nodes
  					if (node.nodeType === 3 && node.nodeValue.length === 0) {
  						tempNode = node;
  						node = node.nextSibling;
  						dom.remove(tempNode);
  						continue;
  					}
  
  					if (!rootBlockNode) {
  						rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
  						node.parentNode.insertBefore(rootBlockNode, node);
  						wrapped = true;
  					}
  
  					tempNode = node;
  					node = node.nextSibling;
  					rootBlockNode.appendChild(tempNode);
  				} else {
  					rootBlockNode = null;
  					node = node.nextSibling;
  				}
  			}
  
  			if (wrapped && restoreSelection) {
  				if (rng.setStart) {
  					rng.setStart(startContainer, startOffset);
  					rng.setEnd(endContainer, endOffset);
  					selection.setRng(rng);
  				} else {
  					// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
  					try {
  						rng = editor.getDoc().body.createTextRange();
  						rng.moveToElementText(rootNode);
  						rng.collapse(true);
  						rng.moveStart('character', startOffset);
  
  						if (endOffset > 0) {
  							rng.moveEnd('character', endOffset);
  						}
  
  						rng.select();
  					} catch (ex) {
  						// Ignore
  					}
  				}
  
  				editor.nodeChanged();
  			}
  		}
  
  		// Force root blocks
  		if (settings.forced_root_block) {
  			editor.on('NodeChange', addRootBlocks);
  		}
  	};
  });
  
  // Included from: js/maya/classes/EditorCommands.js
  
  /**
   * EditorCommands.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class enables you to add custom editor commands and it contains
   * overrides for native browser commands to address various bugs and issues.
   *
   * @class maya.EditorCommands
   */
  define("maya/EditorCommands", [
  	"maya/html/Serializer",
  	"maya/Env",
  	"maya/util/Tools",
  	"maya/dom/ElementUtils",
  	"maya/dom/RangeUtils",
  	"maya/dom/TreeWalker"
  ], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
  	// Added for compression purposes
  	var each = Tools.each, extend = Tools.extend;
  	var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
  	var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
  	var TRUE = true, FALSE = false;
  
  	return function(editor) {
  		var dom, selection, formatter,
  			commands = {state: {}, exec: {}, value: {}},
  			settings = editor.settings,
  			bookmark;
  
  		editor.on('PreInit', function() {
  			dom = editor.dom;
  			selection = editor.selection;
  			settings = editor.settings;
  			formatter = editor.formatter;
  		});
  
  		/**
  		 * Executes the specified command.
  		 *
  		 * @method execCommand
  		 * @param {String} command Command to execute.
  		 * @param {Boolean} ui Optional user interface state.
  		 * @param {Object} value Optional value for command.
  		 * @return {Boolean} true/false if the command was found or not.
  		 */
  		function execCommand(command, ui, value, args) {
  			var func, customCommand, state = 0;
  
  			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
  				editor.focus();
  			}
  
  			args = extend({}, args);
  			args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
  			if (args.isDefaultPrevented()) {
  				return false;
  			}
  
  			customCommand = command.toLowerCase();
  			if ((func = commands.exec[customCommand])) {
  				func(customCommand, ui, value);
  				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  				return true;
  			}
  
  			// Plugin commands
  			each(editor.plugins, function(p) {
  				if (p.execCommand && p.execCommand(command, ui, value)) {
  					editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  					state = true;
  					return false;
  				}
  			});
  
  			if (state) {
  				return state;
  			}
  
  			// Theme commands
  			if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
  				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  				return true;
  			}
  
  			// Browser commands
  			try {
  				state = editor.getDoc().execCommand(command, ui, value);
  			} catch (ex) {
  				// Ignore old IE errors
  			}
  
  			if (state) {
  				editor.fire('ExecCommand', {command: command, ui: ui, value: value});
  				return true;
  			}
  
  			return false;
  		}
  
  		/**
  		 * Queries the current state for a command for example if the current selection is "bold".
  		 *
  		 * @method queryCommandState
  		 * @param {String} command Command to check the state of.
  		 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
  		 */
  		function queryCommandState(command) {
  			var func;
  
  			// Is hidden then return undefined
  			if (editor._isHidden()) {
  				return;
  			}
  
  			command = command.toLowerCase();
  			if ((func = commands.state[command])) {
  				return func(command);
  			}
  
  			// Browser commands
  			try {
  				return editor.getDoc().queryCommandState(command);
  			} catch (ex) {
  				// Fails sometimes see bug: 1896577
  			}
  
  			return false;
  		}
  
  		/**
  		 * Queries the command value for example the current fontsize.
  		 *
  		 * @method queryCommandValue
  		 * @param {String} command Command to check the value of.
  		 * @return {Object} Command value of false if it's not found.
  		 */
  		function queryCommandValue(command) {
  			var func;
  
  			// Is hidden then return undefined
  			if (editor._isHidden()) {
  				return;
  			}
  
  			command = command.toLowerCase();
  			if ((func = commands.value[command])) {
  				return func(command);
  			}
  
  			// Browser commands
  			try {
  				return editor.getDoc().queryCommandValue(command);
  			} catch (ex) {
  				// Fails sometimes see bug: 1896577
  			}
  		}
  
  		/**
  		 * Adds commands to the command collection.
  		 *
  		 * @method addCommands
  		 * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
  		 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
  		 */
  		function addCommands(command_list, type) {
  			type = type || 'exec';
  
  			each(command_list, function(callback, command) {
  				each(command.toLowerCase().split(','), function(command) {
  					commands[type][command] = callback;
  				});
  			});
  		}
  
  		function addCommand(command, callback, scope) {
  			command = command.toLowerCase();
  			commands.exec[command] = function(command, ui, value, args) {
  				return callback.call(scope || editor, ui, value, args);
  			};
  		}
  
  		/**
  		 * Returns true/false if the command is supported or not.
  		 *
  		 * @method queryCommandSupported
  		 * @param {String} cmd Command that we check support for.
  		 * @return {Boolean} true/false if the command is supported or not.
  		 */
  		function queryCommandSupported(command) {
  			command = command.toLowerCase();
  
  			if (commands.exec[command]) {
  				return true;
  			}
  
  			// Browser commands
  			try {
  				return editor.getDoc().queryCommandSupported(command);
  			} catch (ex) {
  				// Fails sometimes see bug: 1896577
  			}
  
  			return false;
  		}
  
  		function addQueryStateHandler(command, callback, scope) {
  			command = command.toLowerCase();
  			commands.state[command] = function() {
  				return callback.call(scope || editor);
  			};
  		}
  
  		function addQueryValueHandler(command, callback, scope) {
  			command = command.toLowerCase();
  			commands.value[command] = function() {
  				return callback.call(scope || editor);
  			};
  		}
  
  		function hasCustomCommand(command) {
  			command = command.toLowerCase();
  			return !!commands.exec[command];
  		}
  
  		// Expose public methods
  		extend(this, {
  			execCommand: execCommand,
  			queryCommandState: queryCommandState,
  			queryCommandValue: queryCommandValue,
  			queryCommandSupported: queryCommandSupported,
  			addCommands: addCommands,
  			addCommand: addCommand,
  			addQueryStateHandler: addQueryStateHandler,
  			addQueryValueHandler: addQueryValueHandler,
  			hasCustomCommand: hasCustomCommand
  		});
  
  		// Private methods
  
  		function execNativeCommand(command, ui, value) {
  			if (ui === undefined) {
  				ui = FALSE;
  			}
  
  			if (value === undefined) {
  				value = null;
  			}
  
  			return editor.getDoc().execCommand(command, ui, value);
  		}
  
  		function isFormatMatch(name) {
  			return formatter.match(name);
  		}
  
  		function toggleFormat(name, value) {
  			formatter.toggle(name, value ? {value: value} : undefined);
  			editor.nodeChanged();
  		}
  
  		function storeSelection(type) {
  			bookmark = selection.getBookmark(type);
  		}
  
  		function restoreSelection() {
  			selection.moveToBookmark(bookmark);
  		}
  
  		// Add execCommand overrides
  		addCommands({
  			// Ignore these, added for compatibility
  			'mceResetDesignMode,mceBeginUndoLevel': function() {},
  
  			// Add undo manager logic
  			'mceEndUndoLevel,mceAddUndoLevel': function() {
  				editor.undoManager.add();
  			},
  
  			'Cut,Copy,Paste': function(command) {
  				var doc = editor.getDoc(), failed;
  
  				// Try executing the native command
  				try {
  					execNativeCommand(command);
  				} catch (ex) {
  					// Command failed
  					failed = TRUE;
  				}
  
  				// Present alert message about clipboard access not being available
  				if (failed || !doc.queryCommandSupported(command)) {
  					var msg = editor.translate(
  						"Your browser doesn't support direct access to the clipboard. " +
  						"Please use the Ctrl+X/C/V keyboard shortcuts instead."
  					);
  
  					if (Env.mac) {
  						msg = msg.replace(/Ctrl\+/g, '\u2318+');
  					}
  
  					editor.windowManager.alert(msg);
  				}
  			},
  
  			// Override unlink command
  			unlink: function() {
  				if (selection.isCollapsed()) {
  					var elm = selection.getNode();
  					if (elm.tagName == 'A') {
  						editor.dom.remove(elm, true);
  					}
  
  					return;
  				}
  
  				formatter.remove("link");
  			},
  
  			// Override justify commands to use the text formatter engine
  			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
  				var align = command.substring(7);
  
  				if (align == 'full') {
  					align = 'justify';
  				}
  
  				// Remove all other alignments first
  				each('left,center,right,justify'.split(','), function(name) {
  					if (align != name) {
  						formatter.remove('align' + name);
  					}
  				});
  
  				toggleFormat('align' + align);
  				execCommand('mceRepaint');
  			},
  
  			// Override list commands to fix WebKit bug
  			'InsertUnorderedList,InsertOrderedList': function(command) {
  				var listElm, listParent;
  
  				execNativeCommand(command);
  
  				// WebKit produces lists within block elements so we need to split them
  				// we will replace the native list creation logic to custom logic later on
  				// TODO: Remove this when the list creation logic is removed
  				listElm = dom.getParent(selection.getNode(), 'ol,ul');
  				if (listElm) {
  					listParent = listElm.parentNode;
  
  					// If list is within a text block then split that block
  					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
  						storeSelection();
  						dom.split(listParent, listElm);
  						restoreSelection();
  					}
  				}
  			},
  
  			// Override commands to use the text formatter engine
  			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  				toggleFormat(command);
  			},
  
  			// Override commands to use the text formatter engine
  			'ForeColor,HiliteColor,FontName': function(command, ui, value) {
  				toggleFormat(command, value);
  			},
  
  			FontSize: function(command, ui, value) {
  				var fontClasses, fontSizes;
  
  				// Convert font size 1-7 to styles
  				if (value >= 1 && value <= 7) {
  					fontSizes = explode(settings.font_size_style_values);
  					fontClasses = explode(settings.font_size_classes);
  
  					if (fontClasses) {
  						value = fontClasses[value - 1] || value;
  					} else {
  						value = fontSizes[value - 1] || value;
  					}
  				}
  
  				toggleFormat(command, value);
  			},
  
  			RemoveFormat: function(command) {
  				formatter.remove(command);
  			},
  
  			mceBlockQuote: function() {
  				toggleFormat('blockquote');
  			},
  
  			FormatBlock: function(command, ui, value) {
  				return toggleFormat(value || 'p');
  			},
  
  			mceCleanup: function() {
  				var bookmark = selection.getBookmark();
  
  				editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
  
  				selection.moveToBookmark(bookmark);
  			},
  
  			mceRemoveNode: function(command, ui, value) {
  				var node = value || selection.getNode();
  
  				// Make sure that the body node isn't removed
  				if (node != editor.getBody()) {
  					storeSelection();
  					editor.dom.remove(node, TRUE);
  					restoreSelection();
  				}
  			},
  
  			mceSelectNodeDepth: function(command, ui, value) {
  				var counter = 0;
  
  				dom.getParent(selection.getNode(), function(node) {
  					if (node.nodeType == 1 && counter++ == value) {
  						selection.select(node);
  						return FALSE;
  					}
  				}, editor.getBody());
  			},
  
  			mceSelectNode: function(command, ui, value) {
  				selection.select(value);
  			},
  
  			mceInsertContent: function(command, ui, value) {
  				var parser, serializer, parentNode, rootNode, fragment, args;
  				var marker, rng, node, node2, bookmarkHtml, merge;
  				var textInlineElements = editor.schema.getTextInlineElements();
  
  				function trimOrPaddLeftRight(html) {
  					var rng, container, offset;
  
  					rng = selection.getRng(true);
  					container = rng.startContainer;
  					offset = rng.startOffset;
  
  					function hasSiblingText(siblingName) {
  						return container[siblingName] && container[siblingName].nodeType == 3;
  					}
  
  					if (container.nodeType == 3) {
  						if (offset > 0) {
  							html = html.replace(/^&nbsp;/, ' ');
  						} else if (!hasSiblingText('previousSibling')) {
  							html = html.replace(/^ /, '&nbsp;');
  						}
  
  						if (offset < container.length) {
  							html = html.replace(/&nbsp;(<br>|)$/, ' ');
  						} else if (!hasSiblingText('nextSibling')) {
  							html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
  						}
  					}
  
  					return html;
  				}
  
  				// Removes &nbsp; from a [b] c -> a &nbsp;c -> a c
  				function trimNbspAfterDeleteAndPaddValue() {
  					var rng, container, offset;
  
  					rng = selection.getRng(true);
  					container = rng.startContainer;
  					offset = rng.startOffset;
  
  					if (container.nodeType == 3 && rng.collapsed) {
  						if (container.data[offset] === '\u00a0') {
  							container.deleteData(offset, 1);
  
  							if (!/[\u00a0| ]$/.test(value)) {
  								value += ' ';
  							}
  						} else if (container.data[offset - 1] === '\u00a0') {
  							container.deleteData(offset - 1, 1);
  
  							if (!/[\u00a0| ]$/.test(value)) {
  								value = ' ' + value;
  							}
  						}
  					}
  				}
  
  				function markInlineFormatElements(fragment) {
  					if (merge) {
  						for (node = fragment.firstChild; node; node = node.walk(true)) {
  							if (textInlineElements[node.name]) {
  								node.attr('data-mce-new', "true");
  							}
  						}
  					}
  				}
  
  				function reduceInlineTextElements() {
  					if (merge) {
  						var root = editor.getBody(), elementUtils = new ElementUtils(dom);
  
  						each(dom.select('*[data-mce-new]'), function(node) {
  							node.removeAttribute('data-mce-new');
  
  							for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
  								if (elementUtils.compare(testNode, node)) {
  									dom.remove(node, true);
  								}
  							}
  						});
  					}
  				}
  
  				if (typeof value != 'string') {
  					merge = value.merge;
  					value = value.content;
  				}
  
  				// Check for whitespace before/after value
  				if (/^ | $/.test(value)) {
  					value = trimOrPaddLeftRight(value);
  				}
  
  				// Setup parser and serializer
  				parser = editor.parser;
  				serializer = new Serializer({}, editor.schema);
  				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#x200B;</span>';
  
  				// Run beforeSetContent handlers on the HTML to be inserted
  				args = {content: value, format: 'html', selection: true};
  				editor.fire('BeforeSetContent', args);
  				value = args.content;
  
  				// Add caret at end of contents if it's missing
  				if (value.indexOf('{$caret}') == -1) {
  					value += '{$caret}';
  				}
  
  				// Replace the caret marker with a span bookmark element
  				value = value.replace(/\{\$caret\}/, bookmarkHtml);
  
  				// If selection is at <body>|<p></p> then move it into <body><p>|</p>
  				rng = selection.getRng();
  				var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
  				var body = editor.getBody();
  				if (caretElement === body && selection.isCollapsed()) {
  					if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
  						rng = dom.createRng();
  						rng.setStart(body.firstChild, 0);
  						rng.setEnd(body.firstChild, 0);
  						selection.setRng(rng);
  					}
  				}
  
  				// Insert node maker where we will insert the new HTML and get it's parent
  				if (!selection.isCollapsed()) {
  					editor.getDoc().execCommand('Delete', false, null);
  					trimNbspAfterDeleteAndPaddValue();
  				}
  
  				parentNode = selection.getNode();
  
  				// Parse the fragment within the context of the parent node
  				var parserArgs = {context: parentNode.nodeName.toLowerCase()};
  				fragment = parser.parse(value, parserArgs);
  
  				markInlineFormatElements(fragment);
  
  				// Move the caret to a more suitable location
  				node = fragment.lastChild;
  				if (node.attr('id') == 'mce_marker') {
  					marker = node;
  
  					for (node = node.prev; node; node = node.walk(true)) {
  						if (node.type == 3 || !dom.isBlock(node.name)) {
  							if (editor.schema.isValidChild(node.parent.name, 'span')) {
  								node.parent.insert(marker, node, node.name === 'br');
  							}
  							break;
  						}
  					}
  				}
  
  				// If parser says valid we can insert the contents into that parent
  				if (!parserArgs.invalid) {
  					value = serializer.serialize(fragment);
  
  					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
  					node = parentNode.firstChild;
  					node2 = parentNode.lastChild;
  					if (!node || (node === node2 && node.nodeName === 'BR')) {
  						dom.setHTML(parentNode, value);
  					} else {
  						selection.setContent(value);
  					}
  				} else {
  					// If the fragment was invalid within that context then we need
  					// to parse and process the parent it's inserted into
  
  					// Insert bookmark node and get the parent
  					selection.setContent(bookmarkHtml);
  					parentNode = selection.getNode();
  					rootNode = editor.getBody();
  
  					// Opera will return the document node when selection is in root
  					if (parentNode.nodeType == 9) {
  						parentNode = node = rootNode;
  					} else {
  						node = parentNode;
  					}
  
  					// Find the ancestor just before the root element
  					while (node !== rootNode) {
  						parentNode = node;
  						node = node.parentNode;
  					}
  
  					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
  					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
  					value = serializer.serialize(
  						parser.parse(
  							// Need to replace by using a function since $ in the contents would otherwise be a problem
  							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
  								return serializer.serialize(fragment);
  							})
  						)
  					);
  
  					// Set the inner/outer HTML depending on if we are in the root or not
  					if (parentNode == rootNode) {
  						dom.setHTML(rootNode, value);
  					} else {
  						dom.setOuterHTML(parentNode, value);
  					}
  				}
  
  				reduceInlineTextElements();
  
  				marker = dom.get('mce_marker');
  				selection.scrollIntoView(marker);
  
  				// Move selection before marker and remove it
  				rng = dom.createRng();
  
  				// If previous sibling is a text node set the selection to the end of that node
  				node = marker.previousSibling;
  				if (node && node.nodeType == 3) {
  					rng.setStart(node, node.nodeValue.length);
  
  					// TODO: Why can't we normalize on IE
  					if (!isIE) {
  						node2 = marker.nextSibling;
  						if (node2 && node2.nodeType == 3) {
  							node.appendData(node2.data);
  							node2.parentNode.removeChild(node2);
  						}
  					}
  				} else {
  					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
  					rng.setStartBefore(marker);
  					rng.setEndBefore(marker);
  				}
  
  				// Remove the marker node and set the new range
  				dom.remove(marker);
  				selection.setRng(rng);
  
  				// Dispatch after event and add any visual elements needed
  				editor.fire('SetContent', args);
  				editor.addVisual();
  			},
  
  			mceInsertRawHTML: function(command, ui, value) {
  				selection.setContent('tiny_mce_marker');
  				editor.setContent(
  					editor.getContent().replace(/tiny_mce_marker/g, function() {
  						return value;
  					})
  				);
  			},
  
  			mceToggleFormat: function(command, ui, value) {
  				toggleFormat(value);
  			},
  
  			mceSetContent: function(command, ui, value) {
  				editor.setContent(value);
  			},
  
  			'Indent,Outdent': function(command) {
  				var intentValue, indentUnit, value;
  
  				// Setup indent level
  				intentValue = settings.indentation;
  				indentUnit = /[a-z%]+$/i.exec(intentValue);
  				intentValue = parseInt(intentValue, 10);
  
  				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
  					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
  					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
  						formatter.apply('div');
  					}
  
  					each(selection.getSelectedBlocks(), function(element) {
  						if (element.nodeName != "LI") {
  							var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
  
  							indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
  
  							if (command == 'outdent') {
  								value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
  								dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
  							} else {
  								value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
  								dom.setStyle(element, indentStyleName, value);
  							}
  						}
  					});
  				} else {
  					execNativeCommand(command);
  				}
  			},
  
  			mceRepaint: function() {
  				if (isGecko) {
  					try {
  						storeSelection(TRUE);
  
  						if (selection.getSel()) {
  							selection.getSel().selectAllChildren(editor.getBody());
  						}
  
  						selection.collapse(TRUE);
  						restoreSelection();
  					} catch (ex) {
  						// Ignore
  					}
  				}
  			},
  
  			InsertHorizontalRule: function() {
  				editor.execCommand('mceInsertContent', false, '<hr />');
  			},
  
  			mceToggleVisualAid: function() {
  				editor.hasVisual = !editor.hasVisual;
  				editor.addVisual();
  			},
  
  			mceReplaceContent: function(command, ui, value) {
  				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
  			},
  
  			mceInsertLink: function(command, ui, value) {
  				var anchor;
  
  				if (typeof value == 'string') {
  					value = {href: value};
  				}
  
  				anchor = dom.getParent(selection.getNode(), 'a');
  
  				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
  				value.href = value.href.replace(' ', '%20');
  
  				// Remove existing links if there could be child links or that the href isn't specified
  				if (!anchor || !value.href) {
  					formatter.remove('link');
  				}
  
  				// Apply new link to selection
  				if (value.href) {
  					formatter.apply('link', value, anchor);
  				}
  			},
  
  			selectAll: function() {
  				var root = dom.getRoot(), rng;
  
  				if (selection.getRng().setStart) {
  					rng = dom.createRng();
  					rng.setStart(root, 0);
  					rng.setEnd(root, root.childNodes.length);
  					selection.setRng(rng);
  				} else {
  					// IE will render it's own root level block elements and sometimes
  					// even put font elements in them when the user starts typing. So we need to
  					// move the selection to a more suitable element from this:
  					// <body>|<p></p></body> to this: <body><p>|</p></body>
  					rng = selection.getRng();
  					if (!rng.item) {
  						rng.moveToElementText(root);
  						rng.select();
  					}
  				}
  			},
  
  			"delete": function() {
  				execNativeCommand("Delete");
  
  				// Check if body is empty after the delete call if so then set the contents
  				// to an empty string and move the caret to any block produced by that operation
  				// this fixes the issue with root blocks not being properly produced after a delete call on IE
  				var body = editor.getBody();
  
  				if (dom.isEmpty(body)) {
  					editor.setContent('');
  
  					if (body.firstChild && dom.isBlock(body.firstChild)) {
  						editor.selection.setCursorLocation(body.firstChild, 0);
  					} else {
  						editor.selection.setCursorLocation(body, 0);
  					}
  				}
  			},
  
  			mceNewDocument: function() {
  				editor.setContent('');
  			},
  
  			InsertLineBreak: function(command, ui, value) {
  				// We load the current event in from EnterKey.js when appropriate to heed
  				// certain event-specific variations such as ctrl-enter in a list
  				var evt = value;
  				var brElm, extraBr, marker;
  				var rng = selection.getRng(true);
  				new RangeUtils(dom).normalize(rng);
  
  				var offset = rng.startOffset;
  				var container = rng.startContainer;
  
  				// Resolve node index
  				if (container.nodeType == 1 && container.hasChildNodes()) {
  					var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
  
  					container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  					if (isAfterLastNodeInContainer && container.nodeType == 3) {
  						offset = container.nodeValue.length;
  					} else {
  						offset = 0;
  					}
  				}
  
  				var parentBlock = dom.getParent(container, dom.isBlock);
  				var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  				var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
  				var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  
  				// Enter inside block contained within a LI then split or insert before/after LI
  				var isControlKey = evt && evt.ctrlKey;
  				if (containerBlockName == 'LI' && !isControlKey) {
  					parentBlock = containerBlock;
  					parentBlockName = containerBlockName;
  				}
  
  				// Walks the parent block to the right and look for BR elements
  				function hasRightSideContent() {
  					var walker = new TreeWalker(container, parentBlock), node;
  					var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
  
  					while ((node = walker.next())) {
  						if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
  							return true;
  						}
  					}
  				}
  
  				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
  					// Insert extra BR element at the end block elements
  					if (!isOldIE && !hasRightSideContent()) {
  						brElm = dom.create('br');
  						rng.insertNode(brElm);
  						rng.setStartAfter(brElm);
  						rng.setEndAfter(brElm);
  						extraBr = true;
  					}
  				}
  
  				brElm = dom.create('br');
  				rng.insertNode(brElm);
  
  				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
  				var documentMode = dom.doc.documentMode;
  				if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
  					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
  				}
  
  				// Insert temp marker and scroll to that
  				marker = dom.create('span', {}, '&nbsp;');
  				brElm.parentNode.insertBefore(marker, brElm);
  				selection.scrollIntoView(marker);
  				dom.remove(marker);
  
  				if (!extraBr) {
  					rng.setStartAfter(brElm);
  					rng.setEndAfter(brElm);
  				} else {
  					rng.setStartBefore(brElm);
  					rng.setEndBefore(brElm);
  				}
  
  				selection.setRng(rng);
  				editor.undoManager.add();
  
  				return TRUE;
  			}
  		});
  
  		// Add queryCommandState overrides
  		addCommands({
  			// Override justify commands
  			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
  				var name = 'align' + command.substring(7);
  				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
  				var matches = map(nodes, function(node) {
  					return !!formatter.matchNode(node, name);
  				});
  				return inArray(matches, TRUE) !== -1;
  			},
  
  			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  				return isFormatMatch(command);
  			},
  
  			mceBlockQuote: function() {
  				return isFormatMatch('blockquote');
  			},
  
  			Outdent: function() {
  				var node;
  
  				if (settings.inline_styles) {
  					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  						return TRUE;
  					}
  
  					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  						return TRUE;
  					}
  				}
  
  				return (
  					queryCommandState('InsertUnorderedList') ||
  					queryCommandState('InsertOrderedList') ||
  					(!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
  				);
  			},
  
  			'InsertUnorderedList,InsertOrderedList': function(command) {
  				var list = dom.getParent(selection.getNode(), 'ul,ol');
  
  				return list &&
  					(
  						command === 'insertunorderedlist' && list.tagName === 'UL' ||
  						command === 'insertorderedlist' && list.tagName === 'OL'
  					);
  			}
  		}, 'state');
  
  		// Add queryCommandValue overrides
  		addCommands({
  			'FontSize,FontName': function(command) {
  				var value = 0, parent;
  
  				if ((parent = dom.getParent(selection.getNode(), 'span'))) {
  					if (command == 'fontsize') {
  						value = parent.style.fontSize;
  					} else {
  						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
  					}
  				}
  
  				return value;
  			}
  		}, 'value');
  
  		// Add undo manager logic
  		addCommands({
  			Undo: function() {
  				editor.undoManager.undo();
  			},
  
  			Redo: function() {
  				editor.undoManager.redo();
  			}
  		});
  	};
  });
  
  // Included from: js/maya/classes/util/URI.js
  
  /**
   * URI.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles parsing, modification and serialization of URI/URL strings.
   * @class maya.util.URI
   */
  define("maya/util/URI", [
  	"maya/util/Tools"
  ], function(Tools) {
  	var each = Tools.each, trim = Tools.trim;
  	var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' ');
  	var DEFAULT_PORTS = {
  		'ftp': 21,
  		'http': 80,
  		'https': 443,
  		'mailto': 25
  	};
  
  	/**
  	 * Constructs a new URI instance.
  	 *
  	 * @constructor
  	 * @method URI
  	 * @param {String} url URI string to parse.
  	 * @param {Object} settings Optional settings object.
  	 */
  	function URI(url, settings) {
  		var self = this, baseUri, base_url;
  
  		url = trim(url);
  		settings = self.settings = settings || {};
  		baseUri = settings.base_uri;
  
  		// Strange app protocol that isn't http/https or local anchor
  		// For example: mailto,skype,tel etc.
  		if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
  			self.source = url;
  			return;
  		}
  
  		var isProtocolRelative = url.indexOf('//') === 0;
  
  		// Absolute path with no host, fake host and protocol
  		if (url.indexOf('/') === 0 && !isProtocolRelative) {
  			url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
  		}
  
  		// Relative path http:// or protocol relative //path
  		if (!/^[\w\-]*:?\/\//.test(url)) {
  			base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
  			if (settings.base_uri.protocol === "") {
  				url = '//mce_host' + self.toAbsPath(base_url, url);
  			} else {
  				url = /([^#?]*)([#?]?.*)/.exec(url);
  				url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
  			}
  		}
  
  		// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
  		url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
  
  		/*jshint maxlen: 255 */
  		/*eslint max-len: 0 */
  		url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
  
  		each(queryParts, function(v, i) {
  			var part = url[i];
  
  			// Zope 3 workaround, they use @@something
  			if (part) {
  				part = part.replace(/\(mce_at\)/g, '@@');
  			}
  
  			self[v] = part;
  		});
  
  		if (baseUri) {
  			if (!self.protocol) {
  				self.protocol = baseUri.protocol;
  			}
  
  			if (!self.userInfo) {
  				self.userInfo = baseUri.userInfo;
  			}
  
  			if (!self.port && self.host === 'mce_host') {
  				self.port = baseUri.port;
  			}
  
  			if (!self.host || self.host === 'mce_host') {
  				self.host = baseUri.host;
  			}
  
  			self.source = '';
  		}
  
  		if (isProtocolRelative) {
  			self.protocol = '';
  		}
  
  		//t.path = t.path || '/';
  	}
  
  	URI.prototype = {
  		/**
  		 * Sets the internal path part of the URI.
  		 *
  		 * @method setPath
  		 * @param {string} path Path string to set.
  		 */
  		setPath: function(path) {
  			var self = this;
  
  			path = /^(.*?)\/?(\w+)?$/.exec(path);
  
  			// Update path parts
  			self.path = path[0];
  			self.directory = path[1];
  			self.file = path[2];
  
  			// Rebuild source
  			self.source = '';
  			self.getURI();
  		},
  
  		/**
  		 * Converts the specified URI into a relative URI based on the current URI instance location.
  		 *
  		 * @method toRelative
  		 * @param {String} uri URI to convert into a relative path/URI.
  		 * @return {String} Relative URI from the point specified in the current URI instance.
  		 * @example
  		 * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
  		 * var url = new maya.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
  		 */
  		toRelative: function(uri) {
  			var self = this, output;
  
  			if (uri === "./") {
  				return uri;
  			}
  
  			uri = new URI(uri, {base_uri: self});
  
  			// Not on same domain/port or protocol
  			if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
  				(self.protocol != uri.protocol && uri.protocol !== "")) {
  				return uri.getURI();
  			}
  
  			var tu = self.getURI(), uu = uri.getURI();
  
  			// Allow usage of the base_uri when relative_urls = true
  			if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
  				return tu;
  			}
  
  			output = self.toRelPath(self.path, uri.path);
  
  			// Add query
  			if (uri.query) {
  				output += '?' + uri.query;
  			}
  
  			// Add anchor
  			if (uri.anchor) {
  				output += '#' + uri.anchor;
  			}
  
  			return output;
  		},
  
  		/**
  		 * Converts the specified URI into a absolute URI based on the current URI instance location.
  		 *
  		 * @method toAbsolute
  		 * @param {String} uri URI to convert into a relative path/URI.
  		 * @param {Boolean} noHost No host and protocol prefix.
  		 * @return {String} Absolute URI from the point specified in the current URI instance.
  		 * @example
  		 * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
  		 * var url = new maya.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
  		 */
  		toAbsolute: function(uri, noHost) {
  			uri = new URI(uri, {base_uri: this});
  
  			return uri.getURI(noHost && this.isSameOrigin(uri));
  		},
  
  		/**
  		 * Determine whether the given URI has the same origin as this URI.  Based on RFC-6454.
  		 * Supports default ports for protocols listed in DEFAULT_PORTS.  Unsupported protocols will fail safe: they
  		 * won't match, if the port specifications differ.
  		 *
  		 * @method isSameOrigin
  		 * @param {maya.util.URI} uri Uri instance to compare.
  		 * @returns {Boolean} True if the origins are the same.
  		 */
  		isSameOrigin: function(uri) {
  			if (this.host == uri.host && this.protocol == uri.protocol) {
  				if (this.port == uri.port) {
  					return true;
  				}
  
  				var defaultPort = DEFAULT_PORTS[this.protocol];
  				if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
  					return true;
  				}
  			}
  
  			return false;
  		},
  
  		/**
  		 * Converts a absolute path into a relative path.
  		 *
  		 * @method toRelPath
  		 * @param {String} base Base point to convert the path from.
  		 * @param {String} path Absolute path to convert into a relative path.
  		 */
  		toRelPath: function(base, path) {
  			var items, breakPoint = 0, out = '', i, l;
  
  			// Split the paths
  			base = base.substring(0, base.lastIndexOf('/'));
  			base = base.split('/');
  			items = path.split('/');
  
  			if (base.length >= items.length) {
  				for (i = 0, l = base.length; i < l; i++) {
  					if (i >= items.length || base[i] != items[i]) {
  						breakPoint = i + 1;
  						break;
  					}
  				}
  			}
  
  			if (base.length < items.length) {
  				for (i = 0, l = items.length; i < l; i++) {
  					if (i >= base.length || base[i] != items[i]) {
  						breakPoint = i + 1;
  						break;
  					}
  				}
  			}
  
  			if (breakPoint === 1) {
  				return path;
  			}
  
  			for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
  				out += "../";
  			}
  
  			for (i = breakPoint - 1, l = items.length; i < l; i++) {
  				if (i != breakPoint - 1) {
  					out += "/" + items[i];
  				} else {
  					out += items[i];
  				}
  			}
  
  			return out;
  		},
  
  		/**
  		 * Converts a relative path into a absolute path.
  		 *
  		 * @method toAbsPath
  		 * @param {String} base Base point to convert the path from.
  		 * @param {String} path Relative path to convert into an absolute path.
  		 */
  		toAbsPath: function(base, path) {
  			var i, nb = 0, o = [], tr, outPath;
  
  			// Split paths
  			tr = /\/$/.test(path) ? '/' : '';
  			base = base.split('/');
  			path = path.split('/');
  
  			// Remove empty chunks
  			each(base, function(k) {
  				if (k) {
  					o.push(k);
  				}
  			});
  
  			base = o;
  
  			// Merge relURLParts chunks
  			for (i = path.length - 1, o = []; i >= 0; i--) {
  				// Ignore empty or .
  				if (path[i].length === 0 || path[i] === ".") {
  					continue;
  				}
  
  				// Is parent
  				if (path[i] === '..') {
  					nb++;
  					continue;
  				}
  
  				// Move up
  				if (nb > 0) {
  					nb--;
  					continue;
  				}
  
  				o.push(path[i]);
  			}
  
  			i = base.length - nb;
  
  			// If /a/b/c or /
  			if (i <= 0) {
  				outPath = o.reverse().join('/');
  			} else {
  				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
  			}
  
  			// Add front / if it's needed
  			if (outPath.indexOf('/') !== 0) {
  				outPath = '/' + outPath;
  			}
  
  			// Add traling / if it's needed
  			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
  				outPath += tr;
  			}
  
  			return outPath;
  		},
  
  		/**
  		 * Returns the full URI of the internal structure.
  		 *
  		 * @method getURI
  		 * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
  		 */
  		getURI: function(noProtoHost) {
  			var s, self = this;
  
  			// Rebuild source
  			if (!self.source || noProtoHost) {
  				s = '';
  
  				if (!noProtoHost) {
  					if (self.protocol) {
  						s += self.protocol + '://';
  					} else {
  						s += '//';
  					}
  
  					if (self.userInfo) {
  						s += self.userInfo + '@';
  					}
  
  					if (self.host) {
  						s += self.host;
  					}
  
  					if (self.port) {
  						s += ':' + self.port;
  					}
  				}
  
  				if (self.path) {
  					s += self.path;
  				}
  
  				if (self.query) {
  					s += '?' + self.query;
  				}
  
  				if (self.anchor) {
  					s += '#' + self.anchor;
  				}
  
  				self.source = s;
  			}
  
  			return self.source;
  		}
  	};
  
  	return URI;
  });
  
  // Included from: js/maya/classes/util/Class.js
  
  /**
   * Class.js
   *
   * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved.
   */
  
  /**
   * This utilitiy class is used for easier inheritage.
   *
   * Features:
   * * Exposed super functions: this._super();
   * * Mixins
   * * Dummy functions
   * * Property functions: var value = object.value(); and object.value(newValue);
   * * Static functions
   * * Defaults settings
   */
  define("maya/util/Class", [
  	"maya/util/Tools"
  ], function(Tools) {
  	var each = Tools.each, extend = Tools.extend;
  
  	var extendClass, initializing;
  
  	function Class() {
  	}
  
  	// Provides classical inheritance, based on code made by John Resig
  	Class.extend = extendClass = function(prop) {
  		var self = this, _super = self.prototype, prototype, name, member;
  
  		// The dummy class constructor
  		function Class() {
  			var i, mixins, mixin, self = this;
  
  			// All construction is actually done in the init method
  			if (!initializing) {
  				// Run class constuctor
  				if (self.init) {
  					self.init.apply(self, arguments);
  				}
  
  				// Run mixin constructors
  				mixins = self.Mixins;
  				if (mixins) {
  					i = mixins.length;
  					while (i--) {
  						mixin = mixins[i];
  						if (mixin.init) {
  							mixin.init.apply(self, arguments);
  						}
  					}
  				}
  			}
  		}
  
  		// Dummy function, needs to be extended in order to provide functionality
  		function dummy() {
  			return this;
  		}
  
  		// Creates a overloaded method for the class
  		// this enables you to use this._super(); to call the super function
  		function createMethod(name, fn) {
  			return function() {
  				var self = this, tmp = self._super, ret;
  
  				self._super = _super[name];
  				ret = fn.apply(self, arguments);
  				self._super = tmp;
  
  				return ret;
  			};
  		}
  
  		// Instantiate a base class (but only create the instance,
  		// don't run the init constructor)
  		initializing = true;
  
  		/*eslint new-cap:0 */
  		prototype = new self();
  		initializing = false;
  
  		// Add mixins
  		if (prop.Mixins) {
  			each(prop.Mixins, function(mixin) {
  				mixin = mixin;
  
  				for (var name in mixin) {
  					if (name !== "init") {
  						prop[name] = mixin[name];
  					}
  				}
  			});
  
  			if (_super.Mixins) {
  				prop.Mixins = _super.Mixins.concat(prop.Mixins);
  			}
  		}
  
  		// Generate dummy methods
  		if (prop.Methods) {
  			each(prop.Methods.split(','), function(name) {
  				prop[name] = dummy;
  			});
  		}
  
  		// Generate property methods
  		if (prop.Properties) {
  			each(prop.Properties.split(','), function(name) {
  				var fieldName = '_' + name;
  
  				prop[name] = function(value) {
  					var self = this, undef;
  
  					// Set value
  					if (value !== undef) {
  						self[fieldName] = value;
  
  						return self;
  					}
  
  					// Get value
  					return self[fieldName];
  				};
  			});
  		}
  
  		// Static functions
  		if (prop.Statics) {
  			each(prop.Statics, function(func, name) {
  				Class[name] = func;
  			});
  		}
  
  		// Default settings
  		if (prop.Defaults && _super.Defaults) {
  			prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
  		}
  
  		// Copy the properties over onto the new prototype
  		for (name in prop) {
  			member = prop[name];
  
  			if (typeof member == "function" && _super[name]) {
  				prototype[name] = createMethod(name, member);
  			} else {
  				prototype[name] = member;
  			}
  		}
  
  		// Populate our constructed prototype object
  		Class.prototype = prototype;
  
  		// Enforce the constructor to be what we expect
  		Class.constructor = Class;
  
  		// And make this class extendible
  		Class.extend = extendClass;
  
  		return Class;
  	};
  
  	return Class;
  });
  
  // Included from: js/maya/classes/util/EventDispatcher.js
  
  /**
   * EventDispatcher.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class lets you add/remove and fire events by name on the specified scope. This makes
   * it easy to add event listener logic to any class.
   *
   * @class maya.util.EventDispatcher
   * @example
   *  var eventDispatcher = new EventDispatcher();
   *
   *  eventDispatcher.on('click', function() {console.log('data');});
   *  eventDispatcher.fire('click', {data: 123});
   */
  define("maya/util/EventDispatcher", [
  	"maya/util/Tools"
  ], function(Tools) {
  	var nativeEvents = Tools.makeMap(
  		"focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
  		"mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
  		"draggesture dragdrop drop drag submit " +
  		"compositionstart compositionend compositionupdate touchstart touchend",
  		' '
  	);
  
  	function Dispatcher(settings) {
  		var self = this, scope, bindings = {}, toggleEvent;
  
  		function returnFalse() {
  			return false;
  		}
  
  		function returnTrue() {
  			return true;
  		}
  
  		settings = settings || {};
  		scope = settings.scope || self;
  		toggleEvent = settings.toggleEvent || returnFalse;
  
  		/**
  		 * Fires the specified event by name.
  		 *
  		 * @method fire
  		 * @param {String} name Name of the event to fire.
  		 * @param {Object?} args Event arguments.
  		 * @return {Object} Event args instance passed in.
  		 * @example
  		 * instance.fire('event', {...});
  		 */
  		function fire(name, args) {
  			var handlers, i, l, callback;
  
  			name = name.toLowerCase();
  			args = args || {};
  			args.type = name;
  
  			// Setup target is there isn't one
  			if (!args.target) {
  				args.target = scope;
  			}
  
  			// Add event delegation methods if they are missing
  			if (!args.preventDefault) {
  				// Add preventDefault method
  				args.preventDefault = function() {
  					args.isDefaultPrevented = returnTrue;
  				};
  
  				// Add stopPropagation
  				args.stopPropagation = function() {
  					args.isPropagationStopped = returnTrue;
  				};
  
  				// Add stopImmediatePropagation
  				args.stopImmediatePropagation = function() {
  					args.isImmediatePropagationStopped = returnTrue;
  				};
  
  				// Add event delegation states
  				args.isDefaultPrevented = returnFalse;
  				args.isPropagationStopped = returnFalse;
  				args.isImmediatePropagationStopped = returnFalse;
  			}
  
  			if (settings.beforeFire) {
  				settings.beforeFire(args);
  			}
  
  			handlers = bindings[name];
  			if (handlers) {
  				for (i = 0, l = handlers.length; i < l; i++) {
  					callback = handlers[i];
  
  					// Unbind handlers marked with "once"
  					if (callback.once) {
  						off(name, callback.func);
  					}
  
  					// Stop immediate propagation if needed
  					if (args.isImmediatePropagationStopped()) {
  						args.stopPropagation();
  						return args;
  					}
  
  					// If callback returns false then prevent default and stop all propagation
  					if (callback.func.call(scope, args) === false) {
  						args.preventDefault();
  						return args;
  					}
  				}
  			}
  
  			return args;
  		}
  
  		/**
  		 * Binds an event listener to a specific event by name.
  		 *
  		 * @method on
  		 * @param {String} name Event name or space separated list of events to bind.
  		 * @param {callback} callback Callback to be executed when the event occurs.
  		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  		 * @return {Object} Current class instance.
  		 * @example
  		 * instance.on('event', function(e) {
  		 *     // Callback logic
  		 * });
  		 */
  		function on(name, callback, prepend, extra) {
  			var handlers, names, i;
  
  			if (callback === false) {
  				callback = returnFalse;
  			}
  
  			if (callback) {
  				callback = {
  					func: callback
  				};
  
  				if (extra) {
  					Tools.extend(callback, extra);
  				}
  
  				names = name.toLowerCase().split(' ');
  				i = names.length;
  				while (i--) {
  					name = names[i];
  					handlers = bindings[name];
  					if (!handlers) {
  						handlers = bindings[name] = [];
  						toggleEvent(name, true);
  					}
  
  					if (prepend) {
  						handlers.unshift(callback);
  					} else {
  						handlers.push(callback);
  					}
  				}
  			}
  
  			return self;
  		}
  
  		/**
  		 * Unbinds an event listener to a specific event by name.
  		 *
  		 * @method off
  		 * @param {String?} name Name of the event to unbind.
  		 * @param {callback?} callback Callback to unbind.
  		 * @return {Object} Current class instance.
  		 * @example
  		 * // Unbind specific callback
  		 * instance.off('event', handler);
  		 *
  		 * // Unbind all listeners by name
  		 * instance.off('event');
  		 *
  		 * // Unbind all events
  		 * instance.off();
  		 */
  		function off(name, callback) {
  			var i, handlers, bindingName, names, hi;
  
  			if (name) {
  				names = name.toLowerCase().split(' ');
  				i = names.length;
  				while (i--) {
  					name = names[i];
  					handlers = bindings[name];
  
  					// Unbind all handlers
  					if (!name) {
  						for (bindingName in bindings) {
  							toggleEvent(bindingName, false);
  							delete bindings[bindingName];
  						}
  
  						return self;
  					}
  
  					if (handlers) {
  						// Unbind all by name
  						if (!callback) {
  							handlers.length = 0;
  						} else {
  							// Unbind specific ones
  							hi = handlers.length;
  							while (hi--) {
  								if (handlers[hi].func === callback) {
  									handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
  									bindings[name] = handlers;
  								}
  							}
  						}
  
  						if (!handlers.length) {
  							toggleEvent(name, false);
  							delete bindings[name];
  						}
  					}
  				}
  			} else {
  				for (name in bindings) {
  					toggleEvent(name, false);
  				}
  
  				bindings = {};
  			}
  
  			return self;
  		}
  
  		/**
  		 * Binds an event listener to a specific event by name
  		 * and automatically unbind the event once the callback fires.
  		 *
  		 * @method once
  		 * @param {String} name Event name or space separated list of events to bind.
  		 * @param {callback} callback Callback to be executed when the event occurs.
  		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  		 * @return {Object} Current class instance.
  		 * @example
  		 * instance.once('event', function(e) {
  		 *     // Callback logic
  		 * });
  		 */
  		function once(name, callback, prepend) {
  			return on(name, callback, prepend, {once: true});
  		}
  
  		/**
  		 * Returns true/false if the dispatcher has a event of the specified name.
  		 *
  		 * @method has
  		 * @param {String} name Name of the event to check for.
  		 * @return {Boolean} true/false if the event exists or not.
  		 */
  		function has(name) {
  			name = name.toLowerCase();
  			return !(!bindings[name] || bindings[name].length === 0);
  		}
  
  		// Expose
  		self.fire = fire;
  		self.on = on;
  		self.off = off;
  		self.once = once;
  		self.has = has;
  	}
  
  	/**
  	 * Returns true/false if the specified event name is a native browser event or not.
  	 *
  	 * @method isNative
  	 * @param {String} name Name to check if it's native.
  	 * @return {Boolean} true/false if the event is native or not.
  	 * @static
  	 */
  	Dispatcher.isNative = function(name) {
  		return !!nativeEvents[name.toLowerCase()];
  	};
  
  	return Dispatcher;
  });
  
  // Included from: js/maya/classes/ui/Selector.js
  
  /**
   * Selector.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*eslint no-nested-ternary:0 */
  
  /**
   * Selector engine, enables you to select controls by using CSS like expressions.
   * We currently only support basic CSS expressions to reduce the size of the core
   * and the ones we support should be enough for most cases.
   *
   * @example
   * Supported expressions:
   *  element
   *  element#name
   *  element.class
   *  element[attr]
   *  element[attr*=value]
   *  element[attr~=value]
   *  element[attr!=value]
   *  element[attr^=value]
   *  element[attr$=value]
   *  element:<state>
   *  element:not(<expression>)
   *  element:first
   *  element:last
   *  element:odd
   *  element:even
   *  element element
   *  element > element
   *
   * @class maya.ui.Selector
   */
  define("maya/ui/Selector", [
  	"maya/util/Class"
  ], function(Class) {
  	"use strict";
  
  	/**
  	 * Produces an array with a unique set of objects. It will not compare the values
  	 * but the references of the objects.
  	 *
  	 * @private
  	 * @method unqiue
  	 * @param {Array} array Array to make into an array with unique items.
  	 * @return {Array} Array with unique items.
  	 */
  	function unique(array) {
  		var uniqueItems = [], i = array.length, item;
  
  		while (i--) {
  			item = array[i];
  
  			if (!item.__checked) {
  				uniqueItems.push(item);
  				item.__checked = 1;
  			}
  		}
  
  		i = uniqueItems.length;
  		while (i--) {
  			delete uniqueItems[i].__checked;
  		}
  
  		return uniqueItems;
  	}
  
  	var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
  
  	/*jshint maxlen:255 */
  	/*eslint max-len:0 */
  	var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
  		whiteSpace = /^\s*|\s*$/g,
  		Collection;
  
  	var Selector = Class.extend({
  		/**
  		 * Constructs a new Selector instance.
  		 *
  		 * @constructor
  		 * @method init
  		 * @param {String} selector CSS like selector expression.
  		 */
  		init: function(selector) {
  			var match = this.match;
  
  			function compileNameFilter(name) {
  				if (name) {
  					name = name.toLowerCase();
  
  					return function(item) {
  						return name === '*' || item.type === name;
  					};
  				}
  			}
  
  			function compileIdFilter(id) {
  				if (id) {
  					return function(item) {
  						return item._name === id;
  					};
  				}
  			}
  
  			function compileClassesFilter(classes) {
  				if (classes) {
  					classes = classes.split('.');
  
  					return function(item) {
  						var i = classes.length;
  
  						while (i--) {
  							if (!item.hasClass(classes[i])) {
  								return false;
  							}
  						}
  
  						return true;
  					};
  				}
  			}
  
  			function compileAttrFilter(name, cmp, check) {
  				if (name) {
  					return function(item) {
  						var value = item[name] ? item[name]() : '';
  
  						return !cmp ? !!check :
  							cmp === "=" ? value === check :
  							cmp === "*=" ? value.indexOf(check) >= 0 :
  							cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
  							cmp === "!=" ? value != check :
  							cmp === "^=" ? value.indexOf(check) === 0 :
  							cmp === "$=" ? value.substr(value.length - check.length) === check :
  							false;
  					};
  				}
  			}
  
  			function compilePsuedoFilter(name) {
  				var notSelectors;
  
  				if (name) {
  					name = /(?:not\((.+)\))|(.+)/i.exec(name);
  
  					if (!name[1]) {
  						name = name[2];
  
  						return function(item, index, length) {
  							return name === 'first' ? index === 0 :
  								name === 'last' ? index === length - 1 :
  								name === 'even' ? index % 2 === 0 :
  								name === 'odd' ? index % 2 === 1 :
  								item[name] ? item[name]() :
  								false;
  						};
  					} else {
  						// Compile not expression
  						notSelectors = parseChunks(name[1], []);
  
  						return function(item) {
  							return !match(item, notSelectors);
  						};
  					}
  				}
  			}
  
  			function compile(selector, filters, direct) {
  				var parts;
  
  				function add(filter) {
  					if (filter) {
  						filters.push(filter);
  					}
  				}
  
  				// Parse expression into parts
  				parts = expression.exec(selector.replace(whiteSpace, ''));
  
  				add(compileNameFilter(parts[1]));
  				add(compileIdFilter(parts[2]));
  				add(compileClassesFilter(parts[3]));
  				add(compileAttrFilter(parts[4], parts[5], parts[6]));
  				add(compilePsuedoFilter(parts[7]));
  
  				// Mark the filter with psuedo for performance
  				filters.psuedo = !!parts[7];
  				filters.direct = direct;
  
  				return filters;
  			}
  
  			// Parser logic based on Sizzle by John Resig
  			function parseChunks(selector, selectors) {
  				var parts = [], extra, matches, i;
  
  				do {
  					chunker.exec("");
  					matches = chunker.exec(selector);
  
  					if (matches) {
  						selector = matches[3];
  						parts.push(matches[1]);
  
  						if (matches[2]) {
  							extra = matches[3];
  							break;
  						}
  					}
  				} while (matches);
  
  				if (extra) {
  					parseChunks(extra, selectors);
  				}
  
  				selector = [];
  				for (i = 0; i < parts.length; i++) {
  					if (parts[i] != '>') {
  						selector.push(compile(parts[i], [], parts[i - 1] === '>'));
  					}
  				}
  
  				selectors.push(selector);
  
  				return selectors;
  			}
  
  			this._selectors = parseChunks(selector, []);
  		},
  
  		/**
  		 * Returns true/false if the selector matches the specified control.
  		 *
  		 * @method match
  		 * @param {maya.ui.Control} control Control to match agains the selector.
  		 * @param {Array} selectors Optional array of selectors, mostly used internally.
  		 * @return {Boolean} true/false state if the control matches or not.
  		 */
  		match: function(control, selectors) {
  			var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
  
  			selectors = selectors || this._selectors;
  			for (i = 0, l = selectors.length; i < l; i++) {
  				selector = selectors[i];
  				sl = selector.length;
  				item = control;
  				count = 0;
  
  				for (si = sl - 1; si >= 0; si--) {
  					filters = selector[si];
  
  					while (item) {
  						// Find the index and length since a psuedo filter like :first needs it
  						if (filters.psuedo) {
  							siblings = item.parent().items();
  							index = length = siblings.length;
  							while (index--) {
  								if (siblings[index] === item) {
  									break;
  								}
  							}
  						}
  
  						for (fi = 0, fl = filters.length; fi < fl; fi++) {
  							if (!filters[fi](item, index, length)) {
  								fi = fl + 1;
  								break;
  							}
  						}
  
  						if (fi === fl) {
  							count++;
  							break;
  						} else {
  							// If it didn't match the right most expression then
  							// break since it's no point looking at the parents
  							if (si === sl - 1) {
  								break;
  							}
  						}
  
  						item = item.parent();
  					}
  				}
  
  				// If we found all selectors then return true otherwise continue looking
  				if (count === sl) {
  					return true;
  				}
  			}
  
  			return false;
  		},
  
  		/**
  		 * Returns a maya.ui.Collection with matches of the specified selector inside the specified container.
  		 *
  		 * @method find
  		 * @param {maya.ui.Control} container Container to look for items in.
  		 * @return {maya.ui.Collection} Collection with matched elements.
  		 */
  		find: function(container) {
  			var matches = [], i, l, selectors = this._selectors;
  
  			function collect(items, selector, index) {
  				var i, l, fi, fl, item, filters = selector[index];
  
  				for (i = 0, l = items.length; i < l; i++) {
  					item = items[i];
  
  					// Run each filter agains the item
  					for (fi = 0, fl = filters.length; fi < fl; fi++) {
  						if (!filters[fi](item, i, l)) {
  							fi = fl + 1;
  							break;
  						}
  					}
  
  					// All filters matched the item
  					if (fi === fl) {
  						// Matched item is on the last expression like: panel toolbar [button]
  						if (index == selector.length - 1) {
  							matches.push(item);
  						} else {
  							// Collect next expression type
  							if (item.items) {
  								collect(item.items(), selector, index + 1);
  							}
  						}
  					} else if (filters.direct) {
  						return;
  					}
  
  					// Collect child items
  					if (item.items) {
  						collect(item.items(), selector, index);
  					}
  				}
  			}
  
  			if (container.items) {
  				for (i = 0, l = selectors.length; i < l; i++) {
  					collect(container.items(), selectors[i], 0);
  				}
  
  				// Unique the matches if needed
  				if (l > 1) {
  					matches = unique(matches);
  				}
  			}
  
  			// Fix for circular reference
  			if (!Collection) {
  				// TODO: Fix me!
  				Collection = Selector.Collection;
  			}
  
  			return new Collection(matches);
  		}
  	});
  
  	return Selector;
  });
  
  // Included from: js/maya/classes/ui/Collection.js
  
  /**
   * Collection.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Control collection, this class contains control instances and it enables you to
   * perform actions on all the contained items. This is very similar to how jQuery works.
   *
   * @example
   * someCollection.show().disabled(true);
   *
   * @class maya.ui.Collection
   */
  define("maya/ui/Collection", [
  	"maya/util/Tools",
  	"maya/ui/Selector",
  	"maya/util/Class"
  ], function(Tools, Selector, Class) {
  	"use strict";
  
  	var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
  
  	proto = {
  		/**
  		 * Current number of contained control instances.
  		 *
  		 * @field length
  		 * @type Number
  		 */
  		length: 0,
  
  		/**
  		 * Constructor for the collection.
  		 *
  		 * @constructor
  		 * @method init
  		 * @param {Array} items Optional array with items to add.
  		 */
  		init: function(items) {
  			if (items) {
  				this.add(items);
  			}
  		},
  
  		/**
  		 * Adds new items to the control collection.
  		 *
  		 * @method add
  		 * @param {Array} items Array if items to add to collection.
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		add: function(items) {
  			var self = this;
  
  			// Force single item into array
  			if (!Tools.isArray(items)) {
  				if (items instanceof Collection) {
  					self.add(items.toArray());
  				} else {
  					push.call(self, items);
  				}
  			} else {
  				push.apply(self, items);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Sets the contents of the collection. This will remove any existing items
  		 * and replace them with the ones specified in the input array.
  		 *
  		 * @method set
  		 * @param {Array} items Array with items to set into the Collection.
  		 * @return {maya.ui.Collection} Collection instance.
  		 */
  		set: function(items) {
  			var self = this, len = self.length, i;
  
  			self.length = 0;
  			self.add(items);
  
  			// Remove old entries
  			for (i = self.length; i < len; i++) {
  				delete self[i];
  			}
  
  			return self;
  		},
  
  		/**
  		 * Filters the collection item based on the specified selector expression or selector function.
  		 *
  		 * @method filter
  		 * @param {String} selector Selector expression to filter items by.
  		 * @return {maya.ui.Collection} Collection containing the filtered items.
  		 */
  		filter: function(selector) {
  			var self = this, i, l, matches = [], item, match;
  
  			// Compile string into selector expression
  			if (typeof selector === "string") {
  				selector = new Selector(selector);
  
  				match = function(item) {
  					return selector.match(item);
  				};
  			} else {
  				// Use selector as matching function
  				match = selector;
  			}
  
  			for (i = 0, l = self.length; i < l; i++) {
  				item = self[i];
  
  				if (match(item)) {
  					matches.push(item);
  				}
  			}
  
  			return new Collection(matches);
  		},
  
  		/**
  		 * Slices the items within the collection.
  		 *
  		 * @method slice
  		 * @param {Number} index Index to slice at.
  		 * @param {Number} len Optional length to slice.
  		 * @return {maya.ui.Collection} Current collection.
  		 */
  		slice: function() {
  			return new Collection(slice.apply(this, arguments));
  		},
  
  		/**
  		 * Makes the current collection equal to the specified index.
  		 *
  		 * @method eq
  		 * @param {Number} index Index of the item to set the collection to.
  		 * @return {maya.ui.Collection} Current collection.
  		 */
  		eq: function(index) {
  			return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  		},
  
  		/**
  		 * Executes the specified callback on each item in collection.
  		 *
  		 * @method each
  		 * @param {function} callback Callback to execute for each item in collection.
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		each: function(callback) {
  			Tools.each(this, callback);
  
  			return this;
  		},
  
  		/**
  		 * Returns an JavaScript array object of the contents inside the collection.
  		 *
  		 * @method toArray
  		 * @return {Array} Array with all items from collection.
  		 */
  		toArray: function() {
  			return Tools.toArray(this);
  		},
  
  		/**
  		 * Finds the index of the specified control or return -1 if it isn't in the collection.
  		 *
  		 * @method indexOf
  		 * @param {Control} ctrl Control instance to look for.
  		 * @return {Number} Index of the specified control or -1.
  		 */
  		indexOf: function(ctrl) {
  			var self = this, i = self.length;
  
  			while (i--) {
  				if (self[i] === ctrl) {
  					break;
  				}
  			}
  
  			return i;
  		},
  
  		/**
  		 * Returns a new collection of the contents in reverse order.
  		 *
  		 * @method reverse
  		 * @return {maya.ui.Collection} Collection instance with reversed items.
  		 */
  		reverse: function() {
  			return new Collection(Tools.toArray(this).reverse());
  		},
  
  		/**
  		 * Returns true/false if the class exists or not.
  		 *
  		 * @method hasClass
  		 * @param {String} cls Class to check for.
  		 * @return {Boolean} true/false state if the class exists or not.
  		 */
  		hasClass: function(cls) {
  			return this[0] ? this[0].hasClass(cls) : false;
  		},
  
  		/**
  		 * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
  		 *
  		 * @method prop
  		 * @param {String} name Property name to get/set.
  		 * @param {Object} value Optional object value to set.
  		 * @return {maya.ui.Collection} Current collection instance or value of the first item on a get operation.
  		 */
  		prop: function(name, value) {
  			var self = this, undef, item;
  
  			if (value !== undef) {
  				self.each(function(item) {
  					if (item[name]) {
  						item[name](value);
  					}
  				});
  
  				return self;
  			}
  
  			item = self[0];
  
  			if (item && item[name]) {
  				return item[name]();
  			}
  		},
  
  		/**
  		 * Executes the specific function name with optional arguments an all items in collection if it exists.
  		 *
  		 * @example collection.exec("myMethod", arg1, arg2, arg3);
  		 * @method exec
  		 * @param {String} name Name of the function to execute.
  		 * @param {Object} ... Multiple arguments to pass to each function.
  		 * @return {maya.ui.Collection} Current collection.
  		 */
  		exec: function(name) {
  			var self = this, args = Tools.toArray(arguments).slice(1);
  
  			self.each(function(item) {
  				if (item[name]) {
  					item[name].apply(item, args);
  				}
  			});
  
  			return self;
  		},
  
  		/**
  		 * Remove all items from collection and DOM.
  		 *
  		 * @method remove
  		 * @return {maya.ui.Collection} Current collection.
  		 */
  		remove: function() {
  			var i = this.length;
  
  			while (i--) {
  				this[i].remove();
  			}
  
  			return this;
  		}
  
  		/**
  		 * Fires the specified event by name and arguments on the control. This will execute all
  		 * bound event handlers.
  		 *
  		 * @method fire
  		 * @param {String} name Name of the event to fire.
  		 * @param {Object} args Optional arguments to pass to the event.
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		// fire: function(event, args) {}, -- Generated by code below
  
  		/**
  		 * Binds a callback to the specified event. This event can both be
  		 * native browser events like "click" or custom ones like PostRender.
  		 *
  		 * The callback function will have two parameters the first one being the control that received the event
  		 * the second one will be the event object either the browsers native event object or a custom JS object.
  		 *
  		 * @method on
  		 * @param {String} name Name of the event to bind. For example "click".
  		 * @param {String/function} callback Callback function to execute ones the event occurs.
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		// on: function(name, callback) {}, -- Generated by code below
  
  		/**
  		 * Unbinds the specified event and optionally a specific callback. If you omit the name
  		 * parameter all event handlers will be removed. If you omit the callback all event handles
  		 * by the specified name will be removed.
  		 *
  		 * @method off
  		 * @param {String} name Optional name for the event to unbind.
  		 * @param {function} callback Optional callback function to unbind.
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		// off: function(name, callback) {}, -- Generated by code below
  
  		/**
  		 * Shows the items in the current collection.
  		 *
  		 * @method show
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		// show: function() {}, -- Generated by code below
  
  		/**
  		 * Hides the items in the current collection.
  		 *
  		 * @method hide
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		// hide: function() {}, -- Generated by code below
  
  		/**
  		 * Sets/gets the text contents of the items in the current collection.
  		 *
  		 * @method text
  		 * @return {maya.ui.Collection} Current collection instance or text value of the first item on a get operation.
  		 */
  		// text: function(value) {}, -- Generated by code below
  
  		/**
  		 * Sets/gets the name contents of the items in the current collection.
  		 *
  		 * @method name
  		 * @return {maya.ui.Collection} Current collection instance or name value of the first item on a get operation.
  		 */
  		// name: function(value) {}, -- Generated by code below
  
  		/**
  		 * Sets/gets the disabled state on the items in the current collection.
  		 *
  		 * @method disabled
  		 * @return {maya.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
  		 */
  		// disabled: function(state) {}, -- Generated by code below
  
  		/**
  		 * Sets/gets the active state on the items in the current collection.
  		 *
  		 * @method active
  		 * @return {maya.ui.Collection} Current collection instance or active state of the first item on a get operation.
  		 */
  		// active: function(state) {}, -- Generated by code below
  
  		/**
  		 * Sets/gets the selected state on the items in the current collection.
  		 *
  		 * @method selected
  		 * @return {maya.ui.Collection} Current collection instance or selected state of the first item on a get operation.
  		 */
  		// selected: function(state) {}, -- Generated by code below
  
  		/**
  		 * Sets/gets the selected state on the items in the current collection.
  		 *
  		 * @method visible
  		 * @return {maya.ui.Collection} Current collection instance or visible state of the first item on a get operation.
  		 */
  		// visible: function(state) {}, -- Generated by code below
  
  		/**
  		 * Adds a class to all items in the collection.
  		 *
  		 * @method addClass
  		 * @param {String} cls Class to add to each item.
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		// addClass: function(cls) {}, -- Generated by code below
  
  		/**
  		 * Removes the specified class from all items in collection.
  		 *
  		 * @method removeClass
  		 * @param {String} cls Class to remove from each item.
  		 * @return {maya.ui.Collection} Current collection instance.
  		 */
  		// removeClass: function(cls) {}, -- Generated by code below
  	};
  
  	// Extend maya.ui.Collection prototype with some generated control specific methods
  	Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) {
  		proto[name] = function() {
  			var args = Tools.toArray(arguments);
  
  			this.each(function(ctrl) {
  				if (name in ctrl) {
  					ctrl[name].apply(ctrl, args);
  				}
  			});
  
  			return this;
  		};
  	});
  
  	// Extend maya.ui.Collection prototype with some property methods
  	Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
  		proto[name] = function(value) {
  			return this.prop(name, value);
  		};
  	});
  
  	// Create class based on the new prototype
  	Collection = Class.extend(proto);
  
  	// Stick Collection into Selector to prevent circual references
  	Selector.Collection = Collection;
  
  	return Collection;
  });
  
  // Included from: js/maya/classes/ui/DomUtils.js
  
  /**
   * DOMUtils.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  define("maya/ui/DomUtils", [
  	"maya/util/Tools",
  	"maya/dom/DOMUtils"
  ], function(Tools, DOMUtils) {
  	"use strict";
  
  	var count = 0;
  
  	return {
  		id: function() {
  			return 'mceu_' + (count++);
  		},
  
  		createFragment: function(html) {
  			return DOMUtils.DOM.createFragment(html);
  		},
  
  		getWindowSize: function() {
  			return DOMUtils.DOM.getViewPort();
  		},
  
  		getSize: function(elm) {
  			var width, height;
  
  			if (elm.getBoundingClientRect) {
  				var rect = elm.getBoundingClientRect();
  
  				width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
  				height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
  			} else {
  				width = elm.offsetWidth;
  				height = elm.offsetHeight;
  			}
  
  			return {width: width, height: height};
  		},
  
  		getPos: function(elm, root) {
  			return DOMUtils.DOM.getPos(elm, root);
  		},
  
  		getViewPort: function(win) {
  			return DOMUtils.DOM.getViewPort(win);
  		},
  
  		get: function(id) {
  			return document.getElementById(id);
  		},
  
  		addClass: function(elm, cls) {
  			return DOMUtils.DOM.addClass(elm, cls);
  		},
  
  		removeClass: function(elm, cls) {
  			return DOMUtils.DOM.removeClass(elm, cls);
  		},
  
  		hasClass: function(elm, cls) {
  			return DOMUtils.DOM.hasClass(elm, cls);
  		},
  
  		toggleClass: function(elm, cls, state) {
  			return DOMUtils.DOM.toggleClass(elm, cls, state);
  		},
  
  		css: function(elm, name, value) {
  			return DOMUtils.DOM.setStyle(elm, name, value);
  		},
  
  		getRuntimeStyle: function(elm, name) {
  			return DOMUtils.DOM.getStyle(elm, name, true);
  		},
  
  		on: function(target, name, callback, scope) {
  			return DOMUtils.DOM.bind(target, name, callback, scope);
  		},
  
  		off: function(target, name, callback) {
  			return DOMUtils.DOM.unbind(target, name, callback);
  		},
  
  		fire: function(target, name, args) {
  			return DOMUtils.DOM.fire(target, name, args);
  		},
  
  		innerHtml: function(elm, html) {
  			// Workaround for <div> in <p> bug on IE 8 #6178
  			DOMUtils.DOM.setHTML(elm, html);
  		}
  	};
  });
  
  // Included from: js/maya/classes/ui/Control.js
  
  /**
   * Control.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*eslint consistent-this:0 */
  
  /**
   * This is the base class for all controls and containers. All UI control instances inherit
   * from this one as it has the base logic needed by all of them.
   *
   * @class maya.ui.Control
   */
  define("maya/ui/Control", [
  	"maya/util/Class",
  	"maya/util/Tools",
  	"maya/util/EventDispatcher",
  	"maya/ui/Collection",
  	"maya/ui/DomUtils"
  ], function(Class, Tools, EventDispatcher, Collection, DomUtils) {
  	"use strict";
  
  	var hasMouseWheelEventSupport = "onmousewheel" in document;
  	var hasWheelEventSupport = false;
  	var classPrefix = "mce-";
  
  	function getEventDispatcher(obj) {
  		if (!obj._eventDispatcher) {
  			obj._eventDispatcher = new EventDispatcher({
  				scope: obj,
  				toggleEvent: function(name, state) {
  					if (state && EventDispatcher.isNative(name)) {
  						if (!obj._nativeEvents) {
  							obj._nativeEvents = {};
  						}
  
  						obj._nativeEvents[name] = true;
  
  						if (obj._rendered) {
  							obj.bindPendingEvents();
  						}
  					}
  				}
  			});
  		}
  
  		return obj._eventDispatcher;
  	}
  
  	var Control = Class.extend({
  		Statics: {
  			classPrefix: classPrefix
  		},
  
  		isRtl: function() {
  			return Control.rtl;
  		},
  
  		/**
  		 * Class/id prefix to use for all controls.
  		 *
  		 * @final
  		 * @field {String} classPrefix
  		 */
  		classPrefix: classPrefix,
  
  		/**
  		 * Constructs a new control instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {String} style Style CSS properties to add.
  		 * @setting {String} border Border box values example: 1 1 1 1
  		 * @setting {String} padding Padding box values example: 1 1 1 1
  		 * @setting {String} margin Margin box values example: 1 1 1 1
  		 * @setting {Number} minWidth Minimal width for the control.
  		 * @setting {Number} minHeight Minimal height for the control.
  		 * @setting {String} classes Space separated list of classes to add.
  		 * @setting {String} role WAI-ARIA role to use for control.
  		 * @setting {Boolean} hidden Is the control hidden by default.
  		 * @setting {Boolean} disabled Is the control disabled by default.
  		 * @setting {String} name Name of the control instance.
  		 */
  		init: function(settings) {
  			var self = this, classes, i;
  
  			self.settings = settings = Tools.extend({}, self.Defaults, settings);
  
  			// Initial states
  			self._id = settings.id || DomUtils.id();
  			self._text = self._name = '';
  			self._width = self._height = 0;
  			self._aria = {role: settings.role};
  			this._elmCache = {};
  
  			// Setup classes
  			classes = settings.classes;
  			if (classes) {
  				classes = classes.split(' ');
  				classes.map = {};
  				i = classes.length;
  				while (i--) {
  					classes.map[classes[i]] = true;
  				}
  			}
  
  			self._classes = classes || [];
  			self.visible(true);
  
  			// Set some properties
  			Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
  				var value = settings[name], undef;
  
  				if (value !== undef) {
  					self[name](value);
  				} else if (self['_' + name] === undef) {
  					self['_' + name] = false;
  				}
  			});
  
  			self.on('click', function() {
  				if (self.disabled()) {
  					return false;
  				}
  			});
  
  			// TODO: Is this needed duplicate code see above?
  			if (settings.classes) {
  				Tools.each(settings.classes.split(' '), function(cls) {
  					self.addClass(cls);
  				});
  			}
  
  			/**
  			 * Name/value object with settings for the current control.
  			 *
  			 * @field {Object} settings
  			 */
  			self.settings = settings;
  
  			self._borderBox = self.parseBox(settings.border);
  			self._paddingBox = self.parseBox(settings.padding);
  			self._marginBox = self.parseBox(settings.margin);
  
  			if (settings.hidden) {
  				self.hide();
  			}
  		},
  
  		// Will generate getter/setter methods for these properties
  		Properties: 'parent,title,text,width,height,disabled,active,name,value',
  
  		// Will generate empty dummy functions for these
  		Methods: 'renderHtml',
  
  		/**
  		 * Returns the root element to render controls into.
  		 *
  		 * @method getContainerElm
  		 * @return {Element} HTML DOM element to render into.
  		 */
  		getContainerElm: function() {
  			return document.body;
  		},
  
  		/**
  		 * Returns a control instance for the current DOM element.
  		 *
  		 * @method getParentCtrl
  		 * @param {Element} elm HTML dom element to get parent control from.
  		 * @return {maya.ui.Control} Control instance or undefined.
  		 */
  		getParentCtrl: function(elm) {
  			var ctrl, lookup = this.getRoot().controlIdLookup;
  
  			while (elm && lookup) {
  				ctrl = lookup[elm.id];
  				if (ctrl) {
  					break;
  				}
  
  				elm = elm.parentNode;
  			}
  
  			return ctrl;
  		},
  
  		/**
  		 * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
  		 *
  		 * @method parseBox
  		 * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
  		 * @return {Object} Object with top/right/bottom/left properties.
  		 * @private
  		 */
  		parseBox: function(value) {
  			var len, radix = 10;
  
  			if (!value) {
  				return;
  			}
  
  			if (typeof value === "number") {
  				value = value || 0;
  
  				return {
  					top: value,
  					left: value,
  					bottom: value,
  					right: value
  				};
  			}
  
  			value = value.split(' ');
  			len = value.length;
  
  			if (len === 1) {
  				value[1] = value[2] = value[3] = value[0];
  			} else if (len === 2) {
  				value[2] = value[0];
  				value[3] = value[1];
  			} else if (len === 3) {
  				value[3] = value[1];
  			}
  
  			return {
  				top: parseInt(value[0], radix) || 0,
  				right: parseInt(value[1], radix) || 0,
  				bottom: parseInt(value[2], radix) || 0,
  				left: parseInt(value[3], radix) || 0
  			};
  		},
  
  		borderBox: function() {
  			return this._borderBox;
  		},
  
  		paddingBox: function() {
  			return this._paddingBox;
  		},
  
  		marginBox: function() {
  			return this._marginBox;
  		},
  
  		measureBox: function(elm, prefix) {
  			function getStyle(name) {
  				var defaultView = document.defaultView;
  
  				if (defaultView) {
  					// Remove camelcase
  					name = name.replace(/[A-Z]/g, function(a) {
  						return '-' + a;
  					});
  
  					return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
  				}
  
  				return elm.currentStyle[name];
  			}
  
  			function getSide(name) {
  				var val = parseFloat(getStyle(name), 10);
  
  				return isNaN(val) ? 0 : val;
  			}
  
  			return {
  				top: getSide(prefix + "TopWidth"),
  				right: getSide(prefix + "RightWidth"),
  				bottom: getSide(prefix + "BottomWidth"),
  				left: getSide(prefix + "LeftWidth")
  			};
  		},
  
  		/**
  		 * Initializes the current controls layout rect.
  		 * This will be executed by the layout managers to determine the
  		 * default minWidth/minHeight etc.
  		 *
  		 * @method initLayoutRect
  		 * @return {Object} Layout rect instance.
  		 */
  		initLayoutRect: function() {
  			var self = this, settings = self.settings, borderBox, layoutRect;
  			var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
  			var startMinWidth, startMinHeight, initialSize;
  
  			// Measure the current element
  			borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
  			self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
  			self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
  			initialSize = DomUtils.getSize(elm);
  
  			// Setup minWidth/minHeight and width/height
  			startMinWidth = settings.minWidth;
  			startMinHeight = settings.minHeight;
  			minWidth = startMinWidth || initialSize.width;
  			minHeight = startMinHeight || initialSize.height;
  			width = settings.width;
  			height = settings.height;
  			autoResize = settings.autoResize;
  			autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height;
  
  			width = width || minWidth;
  			height = height || minHeight;
  
  			var deltaW = borderBox.left + borderBox.right;
  			var deltaH = borderBox.top + borderBox.bottom;
  
  			var maxW = settings.maxWidth || 0xFFFF;
  			var maxH = settings.maxHeight || 0xFFFF;
  
  			// Setup initial layout rect
  			self._layoutRect = layoutRect = {
  				x: settings.x || 0,
  				y: settings.y || 0,
  				w: width,
  				h: height,
  				deltaW: deltaW,
  				deltaH: deltaH,
  				contentW: width - deltaW,
  				contentH: height - deltaH,
  				innerW: width - deltaW,
  				innerH: height - deltaH,
  				startMinWidth: startMinWidth || 0,
  				startMinHeight: startMinHeight || 0,
  				minW: Math.min(minWidth, maxW),
  				minH: Math.min(minHeight, maxH),
  				maxW: maxW,
  				maxH: maxH,
  				autoResize: autoResize,
  				scrollW: 0
  			};
  
  			self._lastLayoutRect = {};
  
  			return layoutRect;
  		},
  
  		/**
  		 * Getter/setter for the current layout rect.
  		 *
  		 * @method layoutRect
  		 * @param {Object} [newRect] Optional new layout rect.
  		 * @return {maya.ui.Control/Object} Current control or rect object.
  		 */
  		layoutRect: function(newRect) {
  			var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
  
  			// Initialize default layout rect
  			if (!curRect) {
  				curRect = self.initLayoutRect();
  			}
  
  			// Set new rect values
  			if (newRect) {
  				// Calc deltas between inner and outer sizes
  				deltaWidth = curRect.deltaW;
  				deltaHeight = curRect.deltaH;
  
  				// Set x position
  				if (newRect.x !== undef) {
  					curRect.x = newRect.x;
  				}
  
  				// Set y position
  				if (newRect.y !== undef) {
  					curRect.y = newRect.y;
  				}
  
  				// Set minW
  				if (newRect.minW !== undef) {
  					curRect.minW = newRect.minW;
  				}
  
  				// Set minH
  				if (newRect.minH !== undef) {
  					curRect.minH = newRect.minH;
  				}
  
  				// Set new width and calculate inner width
  				size = newRect.w;
  				if (size !== undef) {
  					size = size < curRect.minW ? curRect.minW : size;
  					size = size > curRect.maxW ? curRect.maxW : size;
  					curRect.w = size;
  					curRect.innerW = size - deltaWidth;
  				}
  
  				// Set new height and calculate inner height
  				size = newRect.h;
  				if (size !== undef) {
  					size = size < curRect.minH ? curRect.minH : size;
  					size = size > curRect.maxH ? curRect.maxH : size;
  					curRect.h = size;
  					curRect.innerH = size - deltaHeight;
  				}
  
  				// Set new inner width and calculate width
  				size = newRect.innerW;
  				if (size !== undef) {
  					size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
  					size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
  					curRect.innerW = size;
  					curRect.w = size + deltaWidth;
  				}
  
  				// Set new height and calculate inner height
  				size = newRect.innerH;
  				if (size !== undef) {
  					size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
  					size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
  					curRect.innerH = size;
  					curRect.h = size + deltaHeight;
  				}
  
  				// Set new contentW
  				if (newRect.contentW !== undef) {
  					curRect.contentW = newRect.contentW;
  				}
  
  				// Set new contentH
  				if (newRect.contentH !== undef) {
  					curRect.contentH = newRect.contentH;
  				}
  
  				// Compare last layout rect with the current one to see if we need to repaint or not
  				lastLayoutRect = self._lastLayoutRect;
  				if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
  					lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
  					repaintControls = Control.repaintControls;
  
  					if (repaintControls) {
  						if (repaintControls.map && !repaintControls.map[self._id]) {
  							repaintControls.push(self);
  							repaintControls.map[self._id] = true;
  						}
  					}
  
  					lastLayoutRect.x = curRect.x;
  					lastLayoutRect.y = curRect.y;
  					lastLayoutRect.w = curRect.w;
  					lastLayoutRect.h = curRect.h;
  				}
  
  				return self;
  			}
  
  			return curRect;
  		},
  
  		/**
  		 * Repaints the control after a layout operation.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
  
  			// Use Math.round on all values on IE < 9
  			round = !document.createRange ? Math.round : function(value) {
  				return value;
  			};
  
  			style = self.getEl().style;
  			rect = self._layoutRect;
  			lastRepaintRect = self._lastRepaintRect || {};
  
  			borderBox = self._borderBox;
  			borderW = borderBox.left + borderBox.right;
  			borderH = borderBox.top + borderBox.bottom;
  
  			if (rect.x !== lastRepaintRect.x) {
  				style.left = round(rect.x) + 'px';
  				lastRepaintRect.x = rect.x;
  			}
  
  			if (rect.y !== lastRepaintRect.y) {
  				style.top = round(rect.y) + 'px';
  				lastRepaintRect.y = rect.y;
  			}
  
  			if (rect.w !== lastRepaintRect.w) {
  				style.width = round(rect.w - borderW) + 'px';
  				lastRepaintRect.w = rect.w;
  			}
  
  			if (rect.h !== lastRepaintRect.h) {
  				style.height = round(rect.h - borderH) + 'px';
  				lastRepaintRect.h = rect.h;
  			}
  
  			// Update body if needed
  			if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
  				bodyStyle = self.getEl('body').style;
  				bodyStyle.width = round(rect.innerW) + 'px';
  				lastRepaintRect.innerW = rect.innerW;
  			}
  
  			if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
  				bodyStyle = bodyStyle || self.getEl('body').style;
  				bodyStyle.height = round(rect.innerH) + 'px';
  				lastRepaintRect.innerH = rect.innerH;
  			}
  
  			self._lastRepaintRect = lastRepaintRect;
  			self.fire('repaint', {}, false);
  		},
  
  		/**
  		 * Binds a callback to the specified event. This event can both be
  		 * native browser events like "click" or custom ones like PostRender.
  		 *
  		 * The callback function will be passed a DOM event like object that enables yout do stop propagation.
  		 *
  		 * @method on
  		 * @param {String} name Name of the event to bind. For example "click".
  		 * @param {String/function} callback Callback function to execute ones the event occurs.
  		 * @return {maya.ui.Control} Current control object.
  		 */
  		on: function(name, callback) {
  			var self = this;
  
  			function resolveCallbackName(name) {
  				var callback, scope;
  
  				if (typeof name != 'string') {
  					return name;
  				}
  
  				return function(e) {
  					if (!callback) {
  						self.parentsAndSelf().each(function(ctrl) {
  							var callbacks = ctrl.settings.callbacks;
  
  							if (callbacks && (callback = callbacks[name])) {
  								scope = ctrl;
  								return false;
  							}
  						});
  					}
  
  					return callback.call(scope, e);
  				};
  			}
  
  			getEventDispatcher(self).on(name, resolveCallbackName(callback));
  
  			return self;
  		},
  
  		/**
  		 * Unbinds the specified event and optionally a specific callback. If you omit the name
  		 * parameter all event handlers will be removed. If you omit the callback all event handles
  		 * by the specified name will be removed.
  		 *
  		 * @method off
  		 * @param {String} [name] Name for the event to unbind.
  		 * @param {function} [callback] Callback function to unbind.
  		 * @return {mxex.ui.Control} Current control object.
  		 */
  		off: function(name, callback) {
  			getEventDispatcher(this).off(name, callback);
  			return this;
  		},
  
  		/**
  		 * Fires the specified event by name and arguments on the control. This will execute all
  		 * bound event handlers.
  		 *
  		 * @method fire
  		 * @param {String} name Name of the event to fire.
  		 * @param {Object} [args] Arguments to pass to the event.
  		 * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
  		 * @return {Object} Current arguments object.
  		 */
  		fire: function(name, args, bubble) {
  			var self = this;
  
  			args = args || {};
  
  			if (!args.control) {
  				args.control = self;
  			}
  
  			args = getEventDispatcher(self).fire(name, args);
  
  			// Bubble event up to parents
  			if (bubble !== false && self.parent) {
  				var parent = self.parent();
  				while (parent && !args.isPropagationStopped()) {
  					parent.fire(name, args, false);
  					parent = parent.parent();
  				}
  			}
  
  			return args;
  		},
  
  		/**
  		 * Returns true/false if the specified event has any listeners.
  		 *
  		 * @method hasEventListeners
  		 * @param {String} name Name of the event to check for.
  		 * @return {Boolean} True/false state if the event has listeners.
  		 */
  		hasEventListeners: function(name) {
  			return getEventDispatcher(this).has(name);
  		},
  
  		/**
  		 * Returns a control collection with all parent controls.
  		 *
  		 * @method parents
  		 * @param {String} selector Optional selector expression to find parents.
  		 * @return {maya.ui.Collection} Collection with all parent controls.
  		 */
  		parents: function(selector) {
  			var self = this, ctrl, parents = new Collection();
  
  			// Add each parent to collection
  			for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
  				parents.add(ctrl);
  			}
  
  			// Filter away everything that doesn't match the selector
  			if (selector) {
  				parents = parents.filter(selector);
  			}
  
  			return parents;
  		},
  
  		/**
  		 * Returns the current control and it's parents.
  		 *
  		 * @method parentsAndSelf
  		 * @param {String} selector Optional selector expression to find parents.
  		 * @return {maya.ui.Collection} Collection with all parent controls.
  		 */
  		parentsAndSelf: function(selector) {
  			return new Collection(this).add(this.parents(selector));
  		},
  
  		/**
  		 * Returns the control next to the current control.
  		 *
  		 * @method next
  		 * @return {maya.ui.Control} Next control instance.
  		 */
  		next: function() {
  			var parentControls = this.parent().items();
  
  			return parentControls[parentControls.indexOf(this) + 1];
  		},
  
  		/**
  		 * Returns the control previous to the current control.
  		 *
  		 * @method prev
  		 * @return {maya.ui.Control} Previous control instance.
  		 */
  		prev: function() {
  			var parentControls = this.parent().items();
  
  			return parentControls[parentControls.indexOf(this) - 1];
  		},
  
  		/**
  		 * Find the common ancestor for two control instances.
  		 *
  		 * @method findCommonAncestor
  		 * @param {maya.ui.Control} ctrl1 First control.
  		 * @param {maya.ui.Control} ctrl2 Second control.
  		 * @return {maya.ui.Control} Ancestor control instance.
  		 */
  		findCommonAncestor: function(ctrl1, ctrl2) {
  			var parentCtrl;
  
  			while (ctrl1) {
  				parentCtrl = ctrl2;
  
  				while (parentCtrl && ctrl1 != parentCtrl) {
  					parentCtrl = parentCtrl.parent();
  				}
  
  				if (ctrl1 == parentCtrl) {
  					break;
  				}
  
  				ctrl1 = ctrl1.parent();
  			}
  
  			return ctrl1;
  		},
  
  		/**
  		 * Returns true/false if the specific control has the specific class.
  		 *
  		 * @method hasClass
  		 * @param {String} cls Class to check for.
  		 * @param {String} [group] Sub element group name.
  		 * @return {Boolean} True/false if the control has the specified class.
  		 */
  		hasClass: function(cls, group) {
  			var classes = this._classes[group || 'control'];
  
  			cls = this.classPrefix + cls;
  
  			return classes && !!classes.map[cls];
  		},
  
  		/**
  		 * Adds the specified class to the control
  		 *
  		 * @method addClass
  		 * @param {String} cls Class to check for.
  		 * @param {String} [group] Sub element group name.
  		 * @return {maya.ui.Control} Current control object.
  		 */
  		addClass: function(cls, group) {
  			var self = this, classes, elm;
  
  			cls = this.classPrefix + cls;
  			classes = self._classes[group || 'control'];
  
  			if (!classes) {
  				classes = [];
  				classes.map = {};
  				self._classes[group || 'control'] = classes;
  			}
  
  			if (!classes.map[cls]) {
  				classes.map[cls] = cls;
  				classes.push(cls);
  
  				if (self._rendered) {
  					elm = self.getEl(group);
  
  					if (elm) {
  						elm.className = classes.join(' ');
  					}
  				}
  			}
  
  			return self;
  		},
  
  		/**
  		 * Removes the specified class from the control.
  		 *
  		 * @method removeClass
  		 * @param {String} cls Class to remove.
  		 * @param {String} [group] Sub element group name.
  		 * @return {maya.ui.Control} Current control object.
  		 */
  		removeClass: function(cls, group) {
  			var self = this, classes, i, elm;
  
  			cls = this.classPrefix + cls;
  			classes = self._classes[group || 'control'];
  			if (classes && classes.map[cls]) {
  				delete classes.map[cls];
  
  				i = classes.length;
  				while (i--) {
  					if (classes[i] === cls) {
  						classes.splice(i, 1);
  					}
  				}
  			}
  
  			if (self._rendered) {
  				elm = self.getEl(group);
  
  				if (elm) {
  					elm.className = classes.join(' ');
  				}
  			}
  
  			return self;
  		},
  
  		/**
  		 * Toggles the specified class on the control.
  		 *
  		 * @method toggleClass
  		 * @param {String} cls Class to remove.
  		 * @param {Boolean} state True/false state to add/remove class.
  		 * @param {String} [group] Sub element group name.
  		 * @return {maya.ui.Control} Current control object.
  		 */
  		toggleClass: function(cls, state, group) {
  			var self = this;
  
  			if (state) {
  				self.addClass(cls, group);
  			} else {
  				self.removeClass(cls, group);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Returns the class string for the specified group name.
  		 *
  		 * @method classes
  		 * @param {String} [group] Group to get clases by.
  		 * @return {String} Classes for the specified group.
  		 */
  		classes: function(group) {
  			var classes = this._classes[group || 'control'];
  
  			return classes ? classes.join(' ') : '';
  		},
  
  		/**
  		 * Sets the inner HTML of the control element.
  		 *
  		 * @method innerHtml
  		 * @param {String} html Html string to set as inner html.
  		 * @return {maya.ui.Control} Current control object.
  		 */
  		innerHtml: function(html) {
  			DomUtils.innerHtml(this.getEl(), html);
  			return this;
  		},
  
  		/**
  		 * Returns the control DOM element or sub element.
  		 *
  		 * @method getEl
  		 * @param {String} [suffix] Suffix to get element by.
  		 * @return {Element} HTML DOM element for the current control or it's children.
  		 */
  		getEl: function(suffix) {
  			var id = suffix ? this._id + '-' + suffix : this._id;
  
  			if (!this._elmCache[id]) {
  				this._elmCache[id] = DomUtils.get(id);
  			}
  
  			return this._elmCache[id];
  		},
  
  		/**
  		 * Sets/gets the visible for the control.
  		 *
  		 * @method visible
  		 * @param {Boolean} state Value to set to control.
  		 * @return {Boolean/maya.ui.Control} Current control on a set operation or current state on a get.
  		 */
  		visible: function(state) {
  			var self = this, parentCtrl;
  
  			if (typeof state !== "undefined") {
  				if (self._visible !== state) {
  					if (self._rendered) {
  						self.getEl().style.display = state ? '' : 'none';
  					}
  
  					self._visible = state;
  
  					// Parent container needs to reflow
  					parentCtrl = self.parent();
  					if (parentCtrl) {
  						parentCtrl._lastRect = null;
  					}
  
  					self.fire(state ? 'show' : 'hide');
  				}
  
  				return self;
  			}
  
  			return self._visible;
  		},
  
  		/**
  		 * Sets the visible state to true.
  		 *
  		 * @method show
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		show: function() {
  			return this.visible(true);
  		},
  
  		/**
  		 * Sets the visible state to false.
  		 *
  		 * @method hide
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		hide: function() {
  			return this.visible(false);
  		},
  
  		/**
  		 * Focuses the current control.
  		 *
  		 * @method focus
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		focus: function() {
  			try {
  				this.getEl().focus();
  			} catch (ex) {
  				// Ignore IE error
  			}
  
  			return this;
  		},
  
  		/**
  		 * Blurs the current control.
  		 *
  		 * @method blur
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		blur: function() {
  			this.getEl().blur();
  
  			return this;
  		},
  
  		/**
  		 * Sets the specified aria property.
  		 *
  		 * @method aria
  		 * @param {String} name Name of the aria property to set.
  		 * @param {String} value Value of the aria property.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		aria: function(name, value) {
  			var self = this, elm = self.getEl(self.ariaTarget);
  
  			if (typeof value === "undefined") {
  				return self._aria[name];
  			} else {
  				self._aria[name] = value;
  			}
  
  			if (self._rendered) {
  				elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Encodes the specified string with HTML entities. It will also
  		 * translate the string to different languages.
  		 *
  		 * @method encode
  		 * @param {String/Object/Array} text Text to entity encode.
  		 * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
  		 * @return {String} Encoded and possible traslated string.
  		 */
  		encode: function(text, translate) {
  			if (translate !== false) {
  				text = this.translate(text);
  			}
  
  			return (text || '').replace(/[&<>"]/g, function(match) {
  				return '&#' + match.charCodeAt(0) + ';';
  			});
  		},
  
  		/**
  		 * Returns the translated string.
  		 *
  		 * @method translate
  		 * @param {String} text Text to translate.
  		 * @return {String} Translated string or the same as the input.
  		 */
  		translate: function(text) {
  			return Control.translate ? Control.translate(text) : text;
  		},
  
  		/**
  		 * Adds items before the current control.
  		 *
  		 * @method before
  		 * @param {Array/maya.ui.Collection} items Array of items to prepend before this control.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		before: function(items) {
  			var self = this, parent = self.parent();
  
  			if (parent) {
  				parent.insert(items, parent.items().indexOf(self), true);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Adds items after the current control.
  		 *
  		 * @method after
  		 * @param {Array/maya.ui.Collection} items Array of items to append after this control.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		after: function(items) {
  			var self = this, parent = self.parent();
  
  			if (parent) {
  				parent.insert(items, parent.items().indexOf(self));
  			}
  
  			return self;
  		},
  
  		/**
  		 * Removes the current control from DOM and from UI collections.
  		 *
  		 * @method remove
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		remove: function() {
  			var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
  
  			if (self.items) {
  				var controls = self.items().toArray();
  				i = controls.length;
  				while (i--) {
  					controls[i].remove();
  				}
  			}
  
  			if (parent && parent.items) {
  				newItems = [];
  
  				parent.items().each(function(item) {
  					if (item !== self) {
  						newItems.push(item);
  					}
  				});
  
  				parent.items().set(newItems);
  				parent._lastRect = null;
  			}
  
  			if (self._eventsRoot && self._eventsRoot == self) {
  				DomUtils.off(elm);
  			}
  
  			var lookup = self.getRoot().controlIdLookup;
  			if (lookup) {
  				delete lookup[self._id];
  			}
  
  			if (elm && elm.parentNode) {
  				elm.parentNode.removeChild(elm);
  			}
  
  			self._rendered = false;
  
  			return self;
  		},
  
  		/**
  		 * Renders the control before the specified element.
  		 *
  		 * @method renderBefore
  		 * @param {Element} elm Element to render before.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		renderBefore: function(elm) {
  			var self = this;
  
  			elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
  			self.postRender();
  
  			return self;
  		},
  
  		/**
  		 * Renders the control to the specified element.
  		 *
  		 * @method renderBefore
  		 * @param {Element} elm Element to render to.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		renderTo: function(elm) {
  			var self = this;
  
  			elm = elm || self.getContainerElm();
  			elm.appendChild(DomUtils.createFragment(self.renderHtml()));
  			self.postRender();
  
  			return self;
  		},
  
  		/**
  		 * Post render method. Called after the control has been rendered to the target.
  		 *
  		 * @method postRender
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		postRender: function() {
  			var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
  
  			// Bind on<event> settings
  			for (name in settings) {
  				if (name.indexOf("on") === 0) {
  					self.on(name.substr(2), settings[name]);
  				}
  			}
  
  			if (self._eventsRoot) {
  				for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
  					parentEventsRoot = parent._eventsRoot;
  				}
  
  				if (parentEventsRoot) {
  					for (name in parentEventsRoot._nativeEvents) {
  						self._nativeEvents[name] = true;
  					}
  				}
  			}
  
  			self.bindPendingEvents();
  
  			if (settings.style) {
  				elm = self.getEl();
  				if (elm) {
  					elm.setAttribute('style', settings.style);
  					elm.style.cssText = settings.style;
  				}
  			}
  
  			if (!self._visible) {
  				DomUtils.css(self.getEl(), 'display', 'none');
  			}
  
  			if (self.settings.border) {
  				box = self.borderBox();
  				DomUtils.css(self.getEl(), {
  					'border-top-width': box.top,
  					'border-right-width': box.right,
  					'border-bottom-width': box.bottom,
  					'border-left-width': box.left
  				});
  			}
  
  			// Add instance to lookup
  			var root = self.getRoot();
  			if (!root.controlIdLookup) {
  				root.controlIdLookup = {};
  			}
  
  			root.controlIdLookup[self._id] = self;
  
  			for (var key in self._aria) {
  				self.aria(key, self._aria[key]);
  			}
  
  			self.fire('postrender', {}, false);
  		},
  
  		/**
  		 * Scrolls the current control into view.
  		 *
  		 * @method scrollIntoView
  		 * @param {String} align Alignment in view top|center|bottom.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		scrollIntoView: function(align) {
  			function getOffset(elm, rootElm) {
  				var x, y, parent = elm;
  
  				x = y = 0;
  				while (parent && parent != rootElm && parent.nodeType) {
  					x += parent.offsetLeft || 0;
  					y += parent.offsetTop || 0;
  					parent = parent.offsetParent;
  				}
  
  				return {x: x, y: y};
  			}
  
  			var elm = this.getEl(), parentElm = elm.parentNode;
  			var x, y, width, height, parentWidth, parentHeight;
  			var pos = getOffset(elm, parentElm);
  
  			x = pos.x;
  			y = pos.y;
  			width = elm.offsetWidth;
  			height = elm.offsetHeight;
  			parentWidth = parentElm.clientWidth;
  			parentHeight = parentElm.clientHeight;
  
  			if (align == "end") {
  				x -= parentWidth - width;
  				y -= parentHeight - height;
  			} else if (align == "center") {
  				x -= (parentWidth / 2) - (width / 2);
  				y -= (parentHeight / 2) - (height / 2);
  			}
  
  			parentElm.scrollLeft = x;
  			parentElm.scrollTop = y;
  
  			return this;
  		},
  
  		/**
  		 * Binds pending DOM events.
  		 *
  		 * @private
  		 */
  		bindPendingEvents: function() {
  			var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;
  
  			function delegate(e) {
  				var control = self.getParentCtrl(e.target);
  
  				if (control) {
  					control.fire(e.type, e);
  				}
  			}
  
  			function mouseLeaveHandler() {
  				var ctrl = eventRootCtrl._lastHoverCtrl;
  
  				if (ctrl) {
  					ctrl.fire("mouseleave", {target: ctrl.getEl()});
  
  					ctrl.parents().each(function(ctrl) {
  						ctrl.fire("mouseleave", {target: ctrl.getEl()});
  					});
  
  					eventRootCtrl._lastHoverCtrl = null;
  				}
  			}
  
  			function mouseEnterHandler(e) {
  				var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
  
  				// Over on a new control
  				if (ctrl !== lastCtrl) {
  					eventRootCtrl._lastHoverCtrl = ctrl;
  
  					parents = ctrl.parents().toArray().reverse();
  					parents.push(ctrl);
  
  					if (lastCtrl) {
  						lastParents = lastCtrl.parents().toArray().reverse();
  						lastParents.push(lastCtrl);
  
  						for (idx = 0; idx < lastParents.length; idx++) {
  							if (parents[idx] !== lastParents[idx]) {
  								break;
  							}
  						}
  
  						for (i = lastParents.length - 1; i >= idx; i--) {
  							lastCtrl = lastParents[i];
  							lastCtrl.fire("mouseleave", {
  								target: lastCtrl.getEl()
  							});
  						}
  					}
  
  					for (i = idx; i < parents.length; i++) {
  						ctrl = parents[i];
  						ctrl.fire("mouseenter", {
  							target: ctrl.getEl()
  						});
  					}
  				}
  			}
  
  			function fixWheelEvent(e) {
  				e.preventDefault();
  
  				if (e.type == "mousewheel") {
  					e.deltaY = -1 / 40 * e.wheelDelta;
  
  					if (e.wheelDeltaX) {
  						e.deltaX = -1 / 40 * e.wheelDeltaX;
  					}
  				} else {
  					e.deltaX = 0;
  					e.deltaY = e.detail;
  				}
  
  				e = self.fire("wheel", e);
  			}
  
  			self._rendered = true;
  
  			nativeEvents = self._nativeEvents;
  			if (nativeEvents) {
  				// Find event root element if it exists
  				parents = self.parents().toArray();
  				parents.unshift(self);
  				for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
  					eventRootCtrl = parents[i]._eventsRoot;
  				}
  
  				// Event root wasn't found the use the root control
  				if (!eventRootCtrl) {
  					eventRootCtrl = parents[parents.length - 1] || self;
  				}
  
  				// Set the eventsRoot property on children that didn't have it
  				self._eventsRoot = eventRootCtrl;
  				for (l = i, i = 0; i < l; i++) {
  					parents[i]._eventsRoot = eventRootCtrl;
  				}
  
  				var eventRootDelegates = eventRootCtrl._delegates;
  				if (!eventRootDelegates) {
  					eventRootDelegates = eventRootCtrl._delegates = {};
  				}
  
  				// Bind native event delegates
  				for (name in nativeEvents) {
  					if (!nativeEvents) {
  						return false;
  					}
  
  					if (name === "wheel" && !hasWheelEventSupport) {
  						if (hasMouseWheelEventSupport) {
  							DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
  						} else {
  							DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
  						}
  
  						continue;
  					}
  
  					// Special treatment for mousenter/mouseleave since these doesn't bubble
  					if (name === "mouseenter" || name === "mouseleave") {
  						// Fake mousenter/mouseleave
  						if (!eventRootCtrl._hasMouseEnter) {
  							DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
  							DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
  							eventRootCtrl._hasMouseEnter = 1;
  						}
  					} else if (!eventRootDelegates[name]) {
  						DomUtils.on(eventRootCtrl.getEl(), name, delegate);
  						eventRootDelegates[name] = true;
  					}
  
  					// Remove the event once it's bound
  					nativeEvents[name] = false;
  				}
  			}
  		},
  
  		getRoot: function() {
  			var ctrl = this, rootControl, parents = [];
  
  			while (ctrl) {
  				if (ctrl.rootControl) {
  					rootControl = ctrl.rootControl;
  					break;
  				}
  
  				parents.push(ctrl);
  				rootControl = ctrl;
  				ctrl = ctrl.parent();
  			}
  
  			if (!rootControl) {
  				rootControl = this;
  			}
  
  			var i = parents.length;
  			while (i--) {
  				parents[i].rootControl = rootControl;
  			}
  
  			return rootControl;
  		},
  
  		/**
  		 * Reflows the current control and it's parents.
  		 * This should be used after you for example append children to the current control so
  		 * that the layout managers know that they need to reposition everything.
  		 *
  		 * @example
  		 * container.append({type: 'button', text: 'My button'}).reflow();
  		 *
  		 * @method reflow
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		reflow: function() {
  			this.repaint();
  
  			return this;
  		}
  
  		/**
  		 * Sets/gets the parent container for the control.
  		 *
  		 * @method parent
  		 * @param {maya.ui.Container} parent Optional parent to set.
  		 * @return {maya.ui.Control} Parent control or the current control on a set action.
  		 */
  		// parent: function(parent) {} -- Generated
  
  		/**
  		 * Sets/gets the text for the control.
  		 *
  		 * @method text
  		 * @param {String} value Value to set to control.
  		 * @return {String/maya.ui.Control} Current control on a set operation or current value on a get.
  		 */
  		// text: function(value) {} -- Generated
  
  		/**
  		 * Sets/gets the width for the control.
  		 *
  		 * @method width
  		 * @param {Number} value Value to set to control.
  		 * @return {Number/maya.ui.Control} Current control on a set operation or current value on a get.
  		 */
  		// width: function(value) {} -- Generated
  
  		/**
  		 * Sets/gets the height for the control.
  		 *
  		 * @method height
  		 * @param {Number} value Value to set to control.
  		 * @return {Number/maya.ui.Control} Current control on a set operation or current value on a get.
  		 */
  		// height: function(value) {} -- Generated
  
  		/**
  		 * Sets/gets the disabled state on the control.
  		 *
  		 * @method disabled
  		 * @param {Boolean} state Value to set to control.
  		 * @return {Boolean/maya.ui.Control} Current control on a set operation or current state on a get.
  		 */
  		// disabled: function(state) {} -- Generated
  
  		/**
  		 * Sets/gets the active for the control.
  		 *
  		 * @method active
  		 * @param {Boolean} state Value to set to control.
  		 * @return {Boolean/maya.ui.Control} Current control on a set operation or current state on a get.
  		 */
  		// active: function(state) {} -- Generated
  
  		/**
  		 * Sets/gets the name for the control.
  		 *
  		 * @method name
  		 * @param {String} value Value to set to control.
  		 * @return {String/maya.ui.Control} Current control on a set operation or current value on a get.
  		 */
  		// name: function(value) {} -- Generated
  
  		/**
  		 * Sets/gets the title for the control.
  		 *
  		 * @method title
  		 * @param {String} value Value to set to control.
  		 * @return {String/maya.ui.Control} Current control on a set operation or current value on a get.
  		 */
  		// title: function(value) {} -- Generated
  	});
  
  	return Control;
  });
  
  // Included from: js/maya/classes/ui/Factory.js
  
  /**
   * Factory.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*global maya:true */
  
  /**
   * This class is a factory for control instances. This enables you
   * to create instances of controls without having to require the UI controls directly.
   *
   * It also allow you to override or add new control types.
   *
   * @class maya.ui.Factory
   */
  define("maya/ui/Factory", [], function() {
  	"use strict";
  
  	var types = {}, namespaceInit;
  
  	return {
  		/**
  		 * Adds a new control instance type to the factory.
  		 *
  		 * @method add
  		 * @param {String} type Type name for example "button".
  		 * @param {function} typeClass Class type function.
  		 */
  		add: function(type, typeClass) {
  			types[type.toLowerCase()] = typeClass;
  		},
  
  		/**
  		 * Returns true/false if the specified type exists or not.
  		 *
  		 * @method has
  		 * @param {String} type Type to look for.
  		 * @return {Boolean} true/false if the control by name exists.
  		 */
  		has: function(type) {
  			return !!types[type.toLowerCase()];
  		},
  
  		/**
  		 * Creates a new control instance based on the settings provided. The instance created will be
  		 * based on the specified type property it can also create whole structures of components out of
  		 * the specified JSON object.
  		 *
  		 * @example
  		 * maya.ui.Factory.create({
  		 *     type: 'button',
  		 *     text: 'Hello world!'
  		 * });
  		 *
  		 * @method create
  		 * @param {Object/String} settings Name/Value object with items used to create the type.
  		 * @return {maya.ui.Control} Control instance based on the specified type.
  		 */
  		create: function(type, settings) {
  			var ControlType, name, namespace;
  
  			// Build type lookup
  			if (!namespaceInit) {
  				namespace = maya.ui;
  
  				for (name in namespace) {
  					types[name.toLowerCase()] = namespace[name];
  				}
  
  				namespaceInit = true;
  			}
  
  			// If string is specified then use it as the type
  			if (typeof type == 'string') {
  				settings = settings || {};
  				settings.type = type;
  			} else {
  				settings = type;
  				type = settings.type;
  			}
  
  			// Find control type
  			type = type.toLowerCase();
  			ControlType = types[type];
  
  			// #if debug
  
  			if (!ControlType) {
  				throw new Error("Could not find control by type: " + type);
  			}
  
  			// #endif
  
  			ControlType = new ControlType(settings);
  			ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
  
  			return ControlType;
  		}
  	};
  });
  
  // Included from: js/maya/classes/ui/KeyboardNavigation.js
  
  /**
   * KeyboardNavigation.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles keyboard navigation of controls and elements.
   *
   * @class maya.ui.KeyboardNavigation
   */
  define("maya/ui/KeyboardNavigation", [
  ], function() {
  	"use strict";
  
  	/**
  	 * This class handles all keyboard navigation for WAI-ARIA support. Each root container
  	 * gets an instance of this class.
  	 *
  	 * @constructor
  	 */
  	return function(settings) {
  		var root = settings.root, focusedElement, focusedControl;
  
  		try {
  			focusedElement = document.activeElement;
  		} catch (ex) {
  			// IE sometimes fails to return a proper element
  			focusedElement = document.body;
  		}
  
  		focusedControl = root.getParentCtrl(focusedElement);
  
  		/**
  		 * Returns the currently focused elements wai aria role of the currently
  		 * focused element or specified element.
  		 *
  		 * @private
  		 * @param {Element} elm Optional element to get role from.
  		 * @return {String} Role of specified element.
  		 */
  		function getRole(elm) {
  			elm = elm || focusedElement;
  
  			return elm && elm.getAttribute('role');
  		}
  
  		/**
  		 * Returns the wai role of the parent element of the currently
  		 * focused element or specified element.
  		 *
  		 * @private
  		 * @param {Element} elm Optional element to get parent role from.
  		 * @return {String} Role of the first parent that has a role.
  		 */
  		function getParentRole(elm) {
  			var role, parent = elm || focusedElement;
  
  			while ((parent = parent.parentNode)) {
  				if ((role = getRole(parent))) {
  					return role;
  				}
  			}
  		}
  
  		/**
  		 * Returns a wai aria property by name for example aria-selected.
  		 *
  		 * @private
  		 * @param {String} name Name of the aria property to get for example "disabled".
  		 * @return {String} Aria property value.
  		 */
  		function getAriaProp(name) {
  			var elm = focusedElement;
  
  			if (elm) {
  				return elm.getAttribute('aria-' + name);
  			}
  		}
  
  		/**
  		 * Is the element a text input element or not.
  		 *
  		 * @private
  		 * @param {Element} elm Element to check if it's an text input element or not.
  		 * @return {Boolean} True/false if the element is a text element or not.
  		 */
  		function isTextInputElement(elm) {
  			var tagName = elm.tagName.toUpperCase();
  
  			// Notice: since type can be "email" etc we don't check the type
  			// So all input elements gets treated as text input elements
  			return tagName == "INPUT" || tagName == "TEXTAREA";
  		}
  
  		/**
  		 * Returns true/false if the specified element can be focused or not.
  		 *
  		 * @private
  		 * @param {Element} elm DOM element to check if it can be focused or not.
  		 * @return {Boolean} True/false if the element can have focus.
  		 */
  		function canFocus(elm) {
  			if (isTextInputElement(elm) && !elm.hidden) {
  				return true;
  			}
  
  			if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
  				return true;
  			}
  
  			return false;
  		}
  
  		/**
  		 * Returns an array of focusable visible elements within the specified container element.
  		 *
  		 * @private
  		 * @param {Element} elm DOM element to find focusable elements within.
  		 * @return {Array} Array of focusable elements.
  		 */
  		function getFocusElements(elm) {
  			var elements = [];
  
  			function collect(elm) {
  				if (elm.nodeType != 1 || elm.style.display == 'none') {
  					return;
  				}
  
  				if (canFocus(elm)) {
  					elements.push(elm);
  				}
  
  				for (var i = 0; i < elm.childNodes.length; i++) {
  					collect(elm.childNodes[i]);
  				}
  			}
  
  			collect(elm || root.getEl());
  
  			return elements;
  		}
  
  		/**
  		 * Returns the navigation root control for the specified control. The navigation root
  		 * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
  		 * It will look for parents of the specified target control or the currently focused control if this option is omitted.
  		 *
  		 * @private
  		 * @param {maya.ui.Control} targetControl Optional target control to find root of.
  		 * @return {maya.ui.Control} Navigation root control.
  		 */
  		function getNavigationRoot(targetControl) {
  			var navigationRoot, controls;
  
  			targetControl = targetControl || focusedControl;
  			controls = targetControl.parents().toArray();
  			controls.unshift(targetControl);
  
  			for (var i = 0; i < controls.length; i++) {
  				navigationRoot = controls[i];
  
  				if (navigationRoot.settings.ariaRoot) {
  					break;
  				}
  			}
  
  			return navigationRoot;
  		}
  
  		/**
  		 * Focuses the first item in the specified targetControl element or the last aria index if the
  		 * navigation root has the ariaRemember option enabled.
  		 *
  		 * @private
  		 * @param {maya.ui.Control} targetControl Target control to focus the first item in.
  		 */
  		function focusFirst(targetControl) {
  			var navigationRoot = getNavigationRoot(targetControl);
  			var focusElements = getFocusElements(navigationRoot.getEl());
  
  			if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
  				moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
  			} else {
  				moveFocusToIndex(0, focusElements);
  			}
  		}
  
  		/**
  		 * Moves the focus to the specified index within the elements list.
  		 * This will scope the index to the size of the element list if it changed.
  		 *
  		 * @private
  		 * @param {Number} idx Specified index to move to.
  		 * @param {Array} elements Array with dom elements to move focus within.
  		 * @return {Number} Input index or a changed index if it was out of range.
  		 */
  		function moveFocusToIndex(idx, elements) {
  			if (idx < 0) {
  				idx = elements.length - 1;
  			} else if (idx >= elements.length) {
  				idx = 0;
  			}
  
  			if (elements[idx]) {
  				elements[idx].focus();
  			}
  
  			return idx;
  		}
  
  		/**
  		 * Moves the focus forwards or backwards.
  		 *
  		 * @private
  		 * @param {Number} dir Direction to move in positive means forward, negative means backwards.
  		 * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
  		 */
  		function moveFocus(dir, elements) {
  			var idx = -1, navigationRoot = getNavigationRoot();
  
  			elements = elements || getFocusElements(navigationRoot.getEl());
  
  			for (var i = 0; i < elements.length; i++) {
  				if (elements[i] === focusedElement) {
  					idx = i;
  				}
  			}
  
  			idx += dir;
  			navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
  		}
  
  		/**
  		 * Moves the focus to the left this is called by the left key.
  		 *
  		 * @private
  		 */
  		function left() {
  			var parentRole = getParentRole();
  
  			if (parentRole == "tablist") {
  				moveFocus(-1, getFocusElements(focusedElement.parentNode));
  			} else if (focusedControl.parent().submenu) {
  				cancel();
  			} else {
  				moveFocus(-1);
  			}
  		}
  
  		/**
  		 * Moves the focus to the right this is called by the right key.
  		 *
  		 * @private
  		 */
  		function right() {
  			var role = getRole(), parentRole = getParentRole();
  
  			if (parentRole == "tablist") {
  				moveFocus(1, getFocusElements(focusedElement.parentNode));
  			} else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
  				enter();
  			} else {
  				moveFocus(1);
  			}
  		}
  
  		/**
  		 * Moves the focus to the up this is called by the up key.
  		 *
  		 * @private
  		 */
  		function up() {
  			moveFocus(-1);
  		}
  
  		/**
  		 * Moves the focus to the up this is called by the down key.
  		 *
  		 * @private
  		 */
  		function down() {
  			var role = getRole(), parentRole = getParentRole();
  
  			if (role == "menuitem" && parentRole == "menubar") {
  				enter();
  			} else if (role == "button" && getAriaProp('haspopup')) {
  				enter({key: 'down'});
  			} else {
  				moveFocus(1);
  			}
  		}
  
  		/**
  		 * Moves the focus to the next item or previous item depending on shift key.
  		 *
  		 * @private
  		 * @param {DOMEvent} e DOM event object.
  		 */
  		function tab(e) {
  			var parentRole = getParentRole();
  
  			if (parentRole == "tablist") {
  				var elm = getFocusElements(focusedControl.getEl('body'))[0];
  
  				if (elm) {
  					elm.focus();
  				}
  			} else {
  				moveFocus(e.shiftKey ? -1 : 1);
  			}
  		}
  
  		/**
  		 * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
  		 *
  		 * @private
  		 */
  		function cancel() {
  			focusedControl.fire('cancel');
  		}
  
  		/**
  		 * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
  		 *
  		 * @private
  		 * @param {Object} aria Optional aria data to pass along with the enter event.
  		 */
  		function enter(aria) {
  			aria = aria || {};
  			focusedControl.fire('click', {target: focusedElement, aria: aria});
  		}
  
  		root.on('keydown', function(e) {
  			function handleNonTabOrEscEvent(e, handler) {
  				// Ignore non tab keys for text elements
  				if (isTextInputElement(focusedElement)) {
  					return;
  				}
  
  				if (handler(e) !== false) {
  					e.preventDefault();
  				}
  			}
  
  			if (e.isDefaultPrevented()) {
  				return;
  			}
  
  			switch (e.keyCode) {
  				case 37: // DOM_VK_LEFT
  					handleNonTabOrEscEvent(e, left);
  					break;
  
  				case 39: // DOM_VK_RIGHT
  					handleNonTabOrEscEvent(e, right);
  					break;
  
  				case 38: // DOM_VK_UP
  					handleNonTabOrEscEvent(e, up);
  					break;
  
  				case 40: // DOM_VK_DOWN
  					handleNonTabOrEscEvent(e, down);
  					break;
  
  				case 27: // DOM_VK_ESCAPE
  					cancel();
  					break;
  
  				case 14: // DOM_VK_ENTER
  				case 13: // DOM_VK_RETURN
  				case 32: // DOM_VK_SPACE
  					handleNonTabOrEscEvent(e, enter);
  					break;
  
  				case 9: // DOM_VK_TAB
  					if (tab(e) !== false) {
  						e.preventDefault();
  					}
  					break;
  			}
  		});
  
  		root.on('focusin', function(e) {
  			focusedElement = e.target;
  			focusedControl = e.control;
  		});
  
  		return {
  			focusFirst: focusFirst
  		};
  	};
  });
  
  // Included from: js/maya/classes/ui/Container.js
  
  /**
   * Container.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Container control. This is extended by all controls that can have
   * children such as panels etc. You can also use this class directly as an
   * generic container instance. The container doesn't have any specific role or style.
   *
   * @-x-less Container.less
   * @class maya.ui.Container
   * @extends maya.ui.Control
   */
  define("maya/ui/Container", [
  	"maya/ui/Control",
  	"maya/ui/Collection",
  	"maya/ui/Selector",
  	"maya/ui/Factory",
  	"maya/ui/KeyboardNavigation",
  	"maya/util/Tools",
  	"maya/ui/DomUtils"
  ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
  	"use strict";
  
  	var selectorCache = {};
  
  	return Control.extend({
  		layout: '',
  		innerClass: 'container-inner',
  
  		/**
  		 * Constructs a new control instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {Array} items Items to add to container in JSON format or control instances.
  		 * @setting {String} layout Layout manager by name to use.
  		 * @setting {Object} defaults Default settings to apply to all items.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  			settings = self.settings;
  			self._fixed = settings.fixed;
  			self._items = new Collection();
  
  			if (self.isRtl()) {
  				self.addClass('rtl');
  			}
  
  			self.addClass('container');
  			self.addClass('container-body', 'body');
  
  			if (settings.containerCls) {
  				self.addClass(settings.containerCls);
  			}
  
  			self._layout = Factory.create((settings.layout || self.layout) + 'layout');
  
  			if (self.settings.items) {
  				self.add(self.settings.items);
  			}
  
  			// TODO: Fix this!
  			self._hasBody = true;
  		},
  
  		/**
  		 * Returns a collection of child items that the container currently have.
  		 *
  		 * @method items
  		 * @return {maya.ui.Collection} Control collection direct child controls.
  		 */
  		items: function() {
  			return this._items;
  		},
  
  		/**
  		 * Find child controls by selector.
  		 *
  		 * @method find
  		 * @param {String} selector Selector CSS pattern to find children by.
  		 * @return {maya.ui.Collection} Control collection with child controls.
  		 */
  		find: function(selector) {
  			selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
  
  			return selector.find(this);
  		},
  
  		/**
  		 * Adds one or many items to the current container. This will create instances of
  		 * the object representations if needed.
  		 *
  		 * @method add
  		 * @param {Array/Object/maya.ui.Control} items Array or item that will be added to the container.
  		 * @return {maya.ui.Collection} Current collection control.
  		 */
  		add: function(items) {
  			var self = this;
  
  			self.items().add(self.create(items)).parent(self);
  
  			return self;
  		},
  
  		/**
  		 * Focuses the current container instance. This will look
  		 * for the first control in the container and focus that.
  		 *
  		 * @method focus
  		 * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
  		 * @return {maya.ui.Collection} Current instance.
  		 */
  		focus: function(keyboard) {
  			var self = this, focusCtrl, keyboardNav, items;
  
  			if (keyboard) {
  				keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
  
  				if (keyboardNav) {
  					keyboardNav.focusFirst(self);
  					return;
  				}
  			}
  
  			items = self.find('*');
  
  			// TODO: Figure out a better way to auto focus alert dialog buttons
  			if (self.statusbar) {
  				items.add(self.statusbar.items());
  			}
  
  			items.each(function(ctrl) {
  				if (ctrl.settings.autofocus) {
  					focusCtrl = null;
  					return false;
  				}
  
  				if (ctrl.canFocus) {
  					focusCtrl = focusCtrl || ctrl;
  				}
  			});
  
  			if (focusCtrl) {
  				focusCtrl.focus();
  			}
  
  			return self;
  		},
  
  		/**
  		 * Replaces the specified child control with a new control.
  		 *
  		 * @method replace
  		 * @param {maya.ui.Control} oldItem Old item to be replaced.
  		 * @param {maya.ui.Control} newItem New item to be inserted.
  		 */
  		replace: function(oldItem, newItem) {
  			var ctrlElm, items = this.items(), i = items.length;
  
  			// Replace the item in collection
  			while (i--) {
  				if (items[i] === oldItem) {
  					items[i] = newItem;
  					break;
  				}
  			}
  
  			if (i >= 0) {
  				// Remove new item from DOM
  				ctrlElm = newItem.getEl();
  				if (ctrlElm) {
  					ctrlElm.parentNode.removeChild(ctrlElm);
  				}
  
  				// Remove old item from DOM
  				ctrlElm = oldItem.getEl();
  				if (ctrlElm) {
  					ctrlElm.parentNode.removeChild(ctrlElm);
  				}
  			}
  
  			// Adopt the item
  			newItem.parent(this);
  		},
  
  		/**
  		 * Creates the specified items. If any of the items is plain JSON style objects
  		 * it will convert these into real maya.ui.Control instances.
  		 *
  		 * @method create
  		 * @param {Array} items Array of items to convert into control instances.
  		 * @return {Array} Array with control instances.
  		 */
  		create: function(items) {
  			var self = this, settings, ctrlItems = [];
  
  			// Non array structure, then force it into an array
  			if (!Tools.isArray(items)) {
  				items = [items];
  			}
  
  			// Add default type to each child control
  			Tools.each(items, function(item) {
  				if (item) {
  					// Construct item if needed
  					if (!(item instanceof Control)) {
  						// Name only then convert it to an object
  						if (typeof item == "string") {
  							item = {type: item};
  						}
  
  						// Create control instance based on input settings and default settings
  						settings = Tools.extend({}, self.settings.defaults, item);
  						item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
  							(settings.defaults ? settings.defaults.type : null);
  						item = Factory.create(settings);
  					}
  
  					ctrlItems.push(item);
  				}
  			});
  
  			return ctrlItems;
  		},
  
  		/**
  		 * Renders new control instances.
  		 *
  		 * @private
  		 */
  		renderNew: function() {
  			var self = this;
  
  			// Render any new items
  			self.items().each(function(ctrl, index) {
  				var containerElm, fragment;
  
  				ctrl.parent(self);
  
  				if (!ctrl._rendered) {
  					containerElm = self.getEl('body');
  					fragment = DomUtils.createFragment(ctrl.renderHtml());
  
  					// Insert or append the item
  					if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
  						containerElm.insertBefore(fragment, containerElm.childNodes[index]);
  					} else {
  						containerElm.appendChild(fragment);
  					}
  
  					ctrl.postRender();
  				}
  			});
  
  			self._layout.applyClasses(self);
  			self._lastRect = null;
  
  			return self;
  		},
  
  		/**
  		 * Appends new instances to the current container.
  		 *
  		 * @method append
  		 * @param {Array/maya.ui.Collection} items Array if controls to append.
  		 * @return {maya.ui.Container} Current container instance.
  		 */
  		append: function(items) {
  			return this.add(items).renderNew();
  		},
  
  		/**
  		 * Prepends new instances to the current container.
  		 *
  		 * @method prepend
  		 * @param {Array/maya.ui.Collection} items Array if controls to prepend.
  		 * @return {maya.ui.Container} Current container instance.
  		 */
  		prepend: function(items) {
  			var self = this;
  
  			self.items().set(self.create(items).concat(self.items().toArray()));
  
  			return self.renderNew();
  		},
  
  		/**
  		 * Inserts an control at a specific index.
  		 *
  		 * @method insert
  		 * @param {Array/maya.ui.Collection} items Array if controls to insert.
  		 * @param {Number} index Index to insert controls at.
  		 * @param {Boolean} [before=false] Inserts controls before the index.
  		 */
  		insert: function(items, index, before) {
  			var self = this, curItems, beforeItems, afterItems;
  
  			items = self.create(items);
  			curItems = self.items();
  
  			if (!before && index < curItems.length - 1) {
  				index += 1;
  			}
  
  			if (index >= 0 && index < curItems.length) {
  				beforeItems = curItems.slice(0, index).toArray();
  				afterItems = curItems.slice(index).toArray();
  				curItems.set(beforeItems.concat(items, afterItems));
  			}
  
  			return self.renderNew();
  		},
  
  		/**
  		 * Populates the form fields from the specified JSON data object.
  		 *
  		 * Control items in the form that matches the data will have it's value set.
  		 *
  		 * @method fromJSON
  		 * @param {Object} data JSON data object to set control values by.
  		 * @return {maya.ui.Container} Current form instance.
  		 */
  		fromJSON: function(data) {
  			var self = this;
  
  			for (var name in data) {
  				self.find('#' + name).value(data[name]);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Serializes the form into a JSON object by getting all items
  		 * that has a name and a value.
  		 *
  		 * @method toJSON
  		 * @return {Object} JSON object with form data.
  		 */
  		toJSON: function() {
  			var self = this, data = {};
  
  			self.find('*').each(function(ctrl) {
  				var name = ctrl.name(), value = ctrl.value();
  
  				if (name && typeof value != "undefined") {
  					data[name] = value;
  				}
  			});
  
  			return data;
  		},
  
  		preRender: function() {
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, layout = self._layout, role = this.settings.role;
  
  			self.preRender();
  			layout.preRender(self);
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
  					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  						(self.settings.html || '') + layout.renderHtml(self) +
  					'</div>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Post render method. Called after the control has been rendered to the target.
  		 *
  		 * @method postRender
  		 * @return {maya.ui.Container} Current combobox instance.
  		 */
  		postRender: function() {
  			var self = this, box;
  
  			self.items().exec('postRender');
  			self._super();
  
  			self._layout.postRender(self);
  			self._rendered = true;
  
  			if (self.settings.style) {
  				DomUtils.css(self.getEl(), self.settings.style);
  			}
  
  			if (self.settings.border) {
  				box = self.borderBox();
  				DomUtils.css(self.getEl(), {
  					'border-top-width': box.top,
  					'border-right-width': box.right,
  					'border-bottom-width': box.bottom,
  					'border-left-width': box.left
  				});
  			}
  
  			if (!self.parent()) {
  				self.keyboardNav = new KeyboardNavigation({
  					root: self
  				});
  			}
  
  			return self;
  		},
  
  		/**
  		 * Initializes the current controls layout rect.
  		 * This will be executed by the layout managers to determine the
  		 * default minWidth/minHeight etc.
  		 *
  		 * @method initLayoutRect
  		 * @return {Object} Layout rect instance.
  		 */
  		initLayoutRect: function() {
  			var self = this, layoutRect = self._super();
  
  			// Recalc container size by asking layout manager
  			self._layout.recalc(self);
  
  			return layoutRect;
  		},
  
  		/**
  		 * Recalculates the positions of the controls in the current container.
  		 * This is invoked by the reflow method and shouldn't be called directly.
  		 *
  		 * @method recalc
  		 */
  		recalc: function() {
  			var self = this, rect = self._layoutRect, lastRect = self._lastRect;
  
  			if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
  				self._layout.recalc(self);
  				rect = self.layoutRect();
  				self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
  				return true;
  			}
  		},
  
  		/**
  		 * Reflows the current container and it's children and possible parents.
  		 * This should be used after you for example append children to the current control so
  		 * that the layout managers know that they need to reposition everything.
  		 *
  		 * @example
  		 * container.append({type: 'button', text: 'My button'}).reflow();
  		 *
  		 * @method reflow
  		 * @return {maya.ui.Container} Current container instance.
  		 */
  		reflow: function() {
  			var i;
  
  			if (this.visible()) {
  				Control.repaintControls = [];
  				Control.repaintControls.map = {};
  
  				this.recalc();
  				i = Control.repaintControls.length;
  
  				while (i--) {
  					Control.repaintControls[i].repaint();
  				}
  
  				// TODO: Fix me!
  				if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
  					this.repaint();
  				}
  
  				Control.repaintControls = [];
  			}
  
  			return this;
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/DragHelper.js
  
  /**
   * DragHelper.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Drag/drop helper class.
   *
   * @example
   * var dragHelper = new maya.ui.DragHelper('mydiv', {
   *     start: function(evt) {
   *     },
   *
   *     drag: function(evt) {
   *     },
   *
   *     end: function(evt) {
   *     }
   * });
   *
   * @class maya.ui.DragHelper
   */
  define("maya/ui/DragHelper", [
  	"maya/ui/DomUtils"
  ], function(DomUtils) {
  	"use strict";
  
  	function getDocumentSize() {
  		var doc = document, documentElement, body, scrollWidth, clientWidth;
  		var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
  
  		documentElement = doc.documentElement;
  		body = doc.body;
  
  		scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
  		clientWidth = max(documentElement.clientWidth, body.clientWidth);
  		offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
  
  		scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
  		clientHeight = max(documentElement.clientHeight, body.clientHeight);
  		offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
  
  		return {
  			width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
  			height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
  		};
  	}
  
  	return function(id, settings) {
  		var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY;
  
  		settings = settings || {};
  
  		function getHandleElm() {
  			return doc.getElementById(settings.handle || id);
  		}
  
  		start = function(e) {
  			var docSize = getDocumentSize(), handleElm, cursor;
  
  			e.preventDefault();
  			downButton = e.button;
  			handleElm = getHandleElm();
  			startX = e.screenX;
  			startY = e.screenY;
  
  			// Grab cursor from handle
  			if (window.getComputedStyle) {
  				cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
  			} else {
  				cursor = handleElm.runtimeStyle.cursor;
  			}
  
  			// Create event overlay and add it to document
  			eventOverlayElm = doc.createElement('div');
  			DomUtils.css(eventOverlayElm, {
  				position: "absolute",
  				top: 0, left: 0,
  				width: docSize.width,
  				height: docSize.height,
  				zIndex: 0x7FFFFFFF,
  				opacity: 0.0001,
  				cursor: cursor
  			});
  
  			doc.body.appendChild(eventOverlayElm);
  
  			// Bind mouse events
  			DomUtils.on(doc, 'mousemove', drag);
  			DomUtils.on(doc, 'mouseup', stop);
  
  			// Begin drag
  			settings.start(e);
  		};
  
  		drag = function(e) {
  			if (e.button !== downButton) {
  				return stop(e);
  			}
  
  			e.deltaX = e.screenX - startX;
  			e.deltaY = e.screenY - startY;
  
  			e.preventDefault();
  			settings.drag(e);
  		};
  
  		stop = function(e) {
  			DomUtils.off(doc, 'mousemove', drag);
  			DomUtils.off(doc, 'mouseup', stop);
  
  			eventOverlayElm.parentNode.removeChild(eventOverlayElm);
  
  			if (settings.stop) {
  				settings.stop(e);
  			}
  		};
  
  		/**
  		 * Destroys the drag/drop helper instance.
  		 *
  		 * @method destroy
  		 */
  		this.destroy = function() {
  			DomUtils.off(getHandleElm());
  		};
  
  		DomUtils.on(getHandleElm(), 'mousedown', start);
  	};
  });
  
  // Included from: js/maya/classes/ui/Scrollable.js
  
  /**
   * Scrollable.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This mixin makes controls scrollable using custom scrollbars.
   *
   * @-x-less Scrollable.less
   * @mixin maya.ui.Scrollable
   */
  define("maya/ui/Scrollable", [
  	"maya/ui/DomUtils",
  	"maya/ui/DragHelper"
  ], function(DomUtils, DragHelper) {
  	"use strict";
  
  	return {
  		init: function() {
  			var self = this;
  			self.on('repaint', self.renderScroll);
  		},
  
  		renderScroll: function() {
  			var self = this, margin = 2;
  
  			function repaintScroll() {
  				var hasScrollH, hasScrollV, bodyElm;
  
  				function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
  					var containerElm, scrollBarElm, scrollThumbElm;
  					var containerSize, scrollSize, ratio, rect;
  					var posNameLower, sizeNameLower;
  
  					scrollBarElm = self.getEl('scroll' + axisName);
  					if (scrollBarElm) {
  						posNameLower = posName.toLowerCase();
  						sizeNameLower = sizeName.toLowerCase();
  
  						if (self.getEl('absend')) {
  							DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1);
  						}
  
  						if (!hasScroll) {
  							DomUtils.css(scrollBarElm, 'display', 'none');
  							return;
  						}
  
  						DomUtils.css(scrollBarElm, 'display', 'block');
  						containerElm = self.getEl('body');
  						scrollThumbElm = self.getEl('scroll' + axisName + "t");
  						containerSize = containerElm["client" + sizeName] - (margin * 2);
  						containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
  						scrollSize = containerElm["scroll" + sizeName];
  						ratio = containerSize / scrollSize;
  
  						rect = {};
  						rect[posNameLower] = containerElm["offset" + posName] + margin;
  						rect[sizeNameLower] = containerSize;
  						DomUtils.css(scrollBarElm, rect);
  
  						rect = {};
  						rect[posNameLower] = containerElm["scroll" + posName] * ratio;
  						rect[sizeNameLower] = containerSize * ratio;
  						DomUtils.css(scrollThumbElm, rect);
  					}
  				}
  
  				bodyElm = self.getEl('body');
  				hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
  				hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
  
  				repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
  				repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
  			}
  
  			function addScroll() {
  				function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
  					var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
  
  					self.getEl().appendChild(DomUtils.createFragment(
  						'<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
  							'<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
  						'</div>'
  					));
  
  					self.draghelper = new DragHelper(axisId + 't', {
  						start: function() {
  							scrollStart = self.getEl('body')["scroll" + posName];
  							DomUtils.addClass(DomUtils.get(axisId), prefix + 'active');
  						},
  
  						drag: function(e) {
  							var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
  
  							hasScrollH = layoutRect.contentW > layoutRect.innerW;
  							hasScrollV = layoutRect.contentH > layoutRect.innerH;
  							containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
  							containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
  
  							ratio = containerSize / self.getEl('body')["scroll" + sizeName];
  							self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
  						},
  
  						stop: function() {
  							DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active');
  						}
  					});
  /*
  					self.on('click', function(e) {
  						if (e.target.id == self._id + '-scrollv') {
  
  						}
  					});*/
  				}
  
  				self.addClass('scroll');
  
  				addScrollAxis("v", "Top", "Height", "Y", "Width");
  				addScrollAxis("h", "Left", "Width", "X", "Height");
  			}
  
  			if (self.settings.autoScroll) {
  				if (!self._hasScroll) {
  					self._hasScroll = true;
  					addScroll();
  
  					self.on('wheel', function(e) {
  						var bodyEl = self.getEl('body');
  
  						bodyEl.scrollLeft += (e.deltaX || 0) * 10;
  						bodyEl.scrollTop += e.deltaY * 10;
  
  						repaintScroll();
  					});
  
  					DomUtils.on(self.getEl('body'), "scroll", repaintScroll);
  				}
  
  				repaintScroll();
  			}
  		}
  	};
  });
  
  // Included from: js/maya/classes/ui/Panel.js
  
  /**
   * Panel.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new panel.
   *
   * @-x-less Panel.less
   * @class maya.ui.Panel
   * @extends maya.ui.Container
   * @mixes maya.ui.Scrollable
   */
  define("maya/ui/Panel", [
  	"maya/ui/Container",
  	"maya/ui/Scrollable"
  ], function(Container, Scrollable) {
  	"use strict";
  
  	return Container.extend({
  		Defaults: {
  			layout: 'fit',
  			containerCls: 'panel'
  		},
  
  		Mixins: [Scrollable],
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, layout = self._layout, innerHtml = self.settings.html;
  
  			self.preRender();
  			layout.preRender(self);
  
  			if (typeof innerHtml == "undefined") {
  				innerHtml = (
  					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  						layout.renderHtml(self) +
  					'</div>'
  				);
  			} else {
  				if (typeof innerHtml == 'function') {
  					innerHtml = innerHtml.call(self);
  				}
  
  				self._hasBody = false;
  			}
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
  					(self._preBodyHtml || '') +
  					innerHtml +
  				'</div>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Movable.js
  
  /**
   * Movable.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Movable mixin. Makes controls movable absolute and relative to other elements.
   *
   * @mixin maya.ui.Movable
   */
  define("maya/ui/Movable", [
  	"maya/ui/DomUtils"
  ], function(DomUtils) {
  	"use strict";
  
  	function calculateRelativePosition(ctrl, targetElm, rel) {
  		var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;
  
  		viewport = DomUtils.getViewPort();
  
  		// Get pos of target
  		pos = DomUtils.getPos(targetElm);
  		x = pos.x;
  		y = pos.y;
  
  		if (ctrl._fixed && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') {
  			x -= viewport.x;
  			y -= viewport.y;
  		}
  
  		// Get size of self
  		ctrlElm = ctrl.getEl();
  		size = DomUtils.getSize(ctrlElm);
  		selfW = size.width;
  		selfH = size.height;
  
  		// Get size of target
  		size = DomUtils.getSize(targetElm);
  		targetW = size.width;
  		targetH = size.height;
  
  		// Parse align string
  		rel = (rel || '').split('');
  
  		// Target corners
  		if (rel[0] === 'b') {
  			y += targetH;
  		}
  
  		if (rel[1] === 'r') {
  			x += targetW;
  		}
  
  		if (rel[0] === 'c') {
  			y += Math.round(targetH / 2);
  		}
  
  		if (rel[1] === 'c') {
  			x += Math.round(targetW / 2);
  		}
  
  		// Self corners
  		if (rel[3] === 'b') {
  			y -= selfH;
  		}
  
  		if (rel[4] === 'r') {
  			x -= selfW;
  		}
  
  		if (rel[3] === 'c') {
  			y -= Math.round(selfH / 2);
  		}
  
  		if (rel[4] === 'c') {
  			x -= Math.round(selfW / 2);
  		}
  
  		return {
  			x: x,
  			y: y,
  			w: selfW,
  			h: selfH
  		};
  	}
  
  	return {
  		/**
  		 * Tests various positions to get the most suitable one.
  		 *
  		 * @method testMoveRel
  		 * @param {DOMElement} elm Element to position against.
  		 * @param {Array} rels Array with relative positions.
  		 * @return {String} Best suitable relative position.
  		 */
  		testMoveRel: function(elm, rels) {
  			var viewPortRect = DomUtils.getViewPort();
  
  			for (var i = 0; i < rels.length; i++) {
  				var pos = calculateRelativePosition(this, elm, rels[i]);
  
  				if (this._fixed) {
  					if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
  						return rels[i];
  					}
  				} else {
  					if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
  						pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
  						return rels[i];
  					}
  				}
  			}
  
  			return rels[0];
  		},
  
  		/**
  		 * Move relative to the specified element.
  		 *
  		 * @method moveRel
  		 * @param {Element} elm Element to move relative to.
  		 * @param {String} rel Relative mode. For example: br-tl.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		moveRel: function(elm, rel) {
  			if (typeof rel != 'string') {
  				rel = this.testMoveRel(elm, rel);
  			}
  
  			var pos = calculateRelativePosition(this, elm, rel);
  			return this.moveTo(pos.x, pos.y);
  		},
  
  		/**
  		 * Move by a relative x, y values.
  		 *
  		 * @method moveBy
  		 * @param {Number} dx Relative x position.
  		 * @param {Number} dy Relative y position.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		moveBy: function(dx, dy) {
  			var self = this, rect = self.layoutRect();
  
  			self.moveTo(rect.x + dx, rect.y + dy);
  
  			return self;
  		},
  
  		/**
  		 * Move to absolute position.
  		 *
  		 * @method moveTo
  		 * @param {Number} x Absolute x position.
  		 * @param {Number} y Absolute y position.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		moveTo: function(x, y) {
  			var self = this;
  
  			// TODO: Move this to some global class
  			function contrain(value, max, size) {
  				if (value < 0) {
  					return 0;
  				}
  
  				if (value + size > max) {
  					value = max - size;
  					return value < 0 ? 0 : value;
  				}
  
  				return value;
  			}
  
  			if (self.settings.constrainToViewport) {
  				var viewPortRect = DomUtils.getViewPort(window);
  				var layoutRect = self.layoutRect();
  
  				x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
  				y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
  			}
  
  			if (self._rendered) {
  				self.layoutRect({x: x, y: y}).repaint();
  			} else {
  				self.settings.x = x;
  				self.settings.y = y;
  			}
  
  			self.fire('move', {x: x, y: y});
  
  			return self;
  		}
  	};
  });
  
  // Included from: js/maya/classes/ui/Resizable.js
  
  /**
   * Resizable.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Resizable mixin. Enables controls to be resized.
   *
   * @mixin maya.ui.Resizable
   */
  define("maya/ui/Resizable", [
  	"maya/ui/DomUtils"
  ], function(DomUtils) {
  	"use strict";
  
  	return {
  		/**
  		 * Resizes the control to contents.
  		 *
  		 * @method resizeToContent
  		 */
  		resizeToContent: function() {
  			this._layoutRect.autoResize = true;
  			this._lastRect = null;
  			this.reflow();
  		},
  
  		/**
  		 * Resizes the control to a specific width/height.
  		 *
  		 * @method resizeTo
  		 * @param {Number} w Control width.
  		 * @param {Number} h Control height.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		resizeTo: function(w, h) {
  			// TODO: Fix hack
  			if (w <= 1 || h <= 1) {
  				var rect = DomUtils.getWindowSize();
  
  				w = w <= 1 ? w * rect.w : w;
  				h = h <= 1 ? h * rect.h : h;
  			}
  
  			this._layoutRect.autoResize = false;
  			return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
  		},
  
  		/**
  		 * Resizes the control to a specific relative width/height.
  		 *
  		 * @method resizeBy
  		 * @param {Number} dw Relative control width.
  		 * @param {Number} dh Relative control height.
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		resizeBy: function(dw, dh) {
  			var self = this, rect = self.layoutRect();
  
  			return self.resizeTo(rect.w + dw, rect.h + dh);
  		}
  	};
  });
  
  // Included from: js/maya/classes/ui/FloatPanel.js
  
  /**
   * FloatPanel.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class creates a floating panel.
   *
   * @-x-less FloatPanel.less
   * @class maya.ui.FloatPanel
   * @extends maya.ui.Panel
   * @mixes maya.ui.Movable
   * @mixes maya.ui.Resizable
   */
  define("maya/ui/FloatPanel", [
  	"maya/ui/Panel",
  	"maya/ui/Movable",
  	"maya/ui/Resizable",
  	"maya/ui/DomUtils"
  ], function(Panel, Movable, Resizable, DomUtils) {
  	"use strict";
  
  	var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
  	var zOrder = [], hasModal;
  
  	function bindDocumentClickHandler() {
  		function isChildOf(ctrl, parent) {
  			while (ctrl) {
  				if (ctrl == parent) {
  					return true;
  				}
  
  				ctrl = ctrl.parent();
  			}
  		}
  
  		if (!documentClickHandler) {
  			documentClickHandler = function(e) {
  				// Gecko fires click event and in the wrong order on Mac so lets normalize
  				if (e.button == 2) {
  					return;
  				}
  
  				// Hide any float panel when a click is out side that float panel and the
  				// float panels direct parent for example a click on a menu button
  				var i = visiblePanels.length;
  				while (i--) {
  					var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
  
  					if (panel.settings.autohide) {
  						if (clickCtrl) {
  							if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
  								continue;
  							}
  						}
  
  						e = panel.fire('autohide', {target: e.target});
  						if (!e.isDefaultPrevented()) {
  							panel.hide();
  						}
  					}
  				}
  			};
  
  			DomUtils.on(document, 'click', documentClickHandler);
  		}
  	}
  
  	function bindDocumentScrollHandler() {
  		if (!documentScrollHandler) {
  			documentScrollHandler = function() {
  				var i;
  
  				i = visiblePanels.length;
  				while (i--) {
  					repositionPanel(visiblePanels[i]);
  				}
  			};
  
  			DomUtils.on(window, 'scroll', documentScrollHandler);
  		}
  	}
  
  	function bindWindowResizeHandler() {
  		if (!windowResizeHandler) {
  			var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight;
  
  			windowResizeHandler = function() {
  				// Workaround for #7065 IE 7 fires resize events event though the window wasn't resized
  				if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) {
  					clientWidth = docElm.clientWidth;
  					clientHeight = docElm.clientHeight;
  					FloatPanel.hideAll();
  				}
  			};
  
  			DomUtils.on(window, 'resize', windowResizeHandler);
  		}
  	}
  
  	/**
  	 * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
  	 * also reposition all child panels of the current panel.
  	 */
  	function repositionPanel(panel) {
  		var scrollY = DomUtils.getViewPort().y;
  
  		function toggleFixedChildPanels(fixed, deltaY) {
  			var parent;
  
  			for (var i = 0; i < visiblePanels.length; i++) {
  				if (visiblePanels[i] != panel) {
  					parent = visiblePanels[i].parent();
  
  					while (parent && (parent = parent.parent())) {
  						if (parent == panel) {
  							visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
  						}
  					}
  				}
  			}
  		}
  
  		if (panel.settings.autofix) {
  			if (!panel._fixed) {
  				panel._autoFixY = panel.layoutRect().y;
  
  				if (panel._autoFixY < scrollY) {
  					panel.fixed(true).layoutRect({y: 0}).repaint();
  					toggleFixedChildPanels(true, scrollY - panel._autoFixY);
  				}
  			} else {
  				if (panel._autoFixY > scrollY) {
  					panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
  					toggleFixedChildPanels(false, panel._autoFixY - scrollY);
  				}
  			}
  		}
  	}
  
  	function addRemove(add, ctrl) {
  		var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
  
  		if (add) {
  			zOrder.push(ctrl);
  		} else {
  			i = zOrder.length;
  
  			while (i--) {
  				if (zOrder[i] === ctrl) {
  					zOrder.splice(i, 1);
  				}
  			}
  		}
  
  		if (zOrder.length) {
  			for (i = 0; i < zOrder.length; i++) {
  				if (zOrder[i].modal) {
  					zIndex++;
  					topModal = zOrder[i];
  				}
  
  				zOrder[i].getEl().style.zIndex = zIndex;
  				zOrder[i].zIndex = zIndex;
  				zIndex++;
  			}
  		}
  
  		var modalBlockEl = document.getElementById(ctrl.classPrefix + 'modal-block');
  
  		if (topModal) {
  			DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
  		} else if (modalBlockEl) {
  			modalBlockEl.parentNode.removeChild(modalBlockEl);
  			hasModal = false;
  		}
  
  		FloatPanel.currentZIndex = zIndex;
  	}
  
  	var FloatPanel = Panel.extend({
  		Mixins: [Movable, Resizable],
  
  		/**
  		 * Constructs a new control instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {Boolean} autohide Automatically hide the panel.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  			self._eventsRoot = self;
  
  			self.addClass('floatpanel');
  
  			// Hide floatpanes on click out side the root button
  			if (settings.autohide) {
  				bindDocumentClickHandler();
  				bindWindowResizeHandler();
  				visiblePanels.push(self);
  			}
  
  			if (settings.autofix) {
  				bindDocumentScrollHandler();
  
  				self.on('move', function() {
  					repositionPanel(this);
  				});
  			}
  
  			self.on('postrender show', function(e) {
  				if (e.control == self) {
  					var modalBlockEl, prefix = self.classPrefix;
  
  					if (self.modal && !hasModal) {
  						modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' +
  							prefix + 'reset ' + prefix + 'fade"></div>');
  						modalBlockEl = modalBlockEl.firstChild;
  
  						self.getContainerElm().appendChild(modalBlockEl);
  
  						setTimeout(function() {
  							DomUtils.addClass(modalBlockEl, prefix + 'in');
  							DomUtils.addClass(self.getEl(), prefix + 'in');
  						}, 0);
  
  						hasModal = true;
  					}
  
  					addRemove(true, self);
  				}
  			});
  
  			self.on('show', function() {
  				self.parents().each(function(ctrl) {
  					if (ctrl._fixed) {
  						self.fixed(true);
  						return false;
  					}
  				});
  			});
  
  			if (settings.popover) {
  				self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
  				self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start');
  			}
  		},
  
  		fixed: function(state) {
  			var self = this;
  
  			if (self._fixed != state) {
  				if (self._rendered) {
  					var viewport = DomUtils.getViewPort();
  
  					if (state) {
  						self.layoutRect().y -= viewport.y;
  					} else {
  						self.layoutRect().y += viewport.y;
  					}
  				}
  
  				self.toggleClass('fixed', state);
  				self._fixed = state;
  			}
  
  			return self;
  		},
  
  		/**
  		 * Shows the current float panel.
  		 *
  		 * @method show
  		 * @return {maya.ui.FloatPanel} Current floatpanel instance.
  		 */
  		show: function() {
  			var self = this, i, state = self._super();
  
  			i = visiblePanels.length;
  			while (i--) {
  				if (visiblePanels[i] === self) {
  					break;
  				}
  			}
  
  			if (i === -1) {
  				visiblePanels.push(self);
  			}
  
  			return state;
  		},
  
  		/**
  		 * Hides the current float panel.
  		 *
  		 * @method hide
  		 * @return {maya.ui.FloatPanel} Current floatpanel instance.
  		 */
  		hide: function() {
  			removeVisiblePanel(this);
  			addRemove(false, this);
  
  			return this._super();
  		},
  
  		/**
  		 * Hide all visible float panels with he autohide setting enabled. This is for
  		 * manually hiding floating menus or panels.
  		 *
  		 * @method hideAll
  		 */
  		hideAll: function() {
  			FloatPanel.hideAll();
  		},
  
  		/**
  		 * Closes the float panel. This will remove the float panel from page and fire the close event.
  		 *
  		 * @method close
  		 */
  		close: function() {
  			var self = this;
  
  			if (!self.fire('close').isDefaultPrevented()) {
  				self.remove();
  				addRemove(false, self);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Removes the float panel from page.
  		 *
  		 * @method remove
  		 */
  		remove: function() {
  			removeVisiblePanel(this);
  			this._super();
  		},
  
  		postRender: function() {
  			var self = this;
  
  			if (self.settings.bodyRole) {
  				this.getEl('body').setAttribute('role', self.settings.bodyRole);
  			}
  
  			return self._super();
  		}
  	});
  
  	/**
  	 * Hide all visible float panels with he autohide setting enabled. This is for
  	 * manually hiding floating menus or panels.
  	 *
  	 * @static
  	 * @method hideAll
  	 */
  	FloatPanel.hideAll = function() {
  		var i = visiblePanels.length;
  
  		while (i--) {
  			var panel = visiblePanels[i];
  
  			if (panel && panel.settings.autohide) {
  				panel.hide();
  				visiblePanels.splice(i, 1);
  			}
  		}
  	};
  
  	function removeVisiblePanel(panel) {
  		var i;
  
  		i = visiblePanels.length;
  		while (i--) {
  			if (visiblePanels[i] === panel) {
  				visiblePanels.splice(i, 1);
  			}
  		}
  
  		i = zOrder.length;
  		while (i--) {
  			if (zOrder[i] === panel) {
  				zOrder.splice(i, 1);
  			}
  		}
  	}
  
  	return FloatPanel;
  });
  
  // Included from: js/maya/classes/ui/Window.js
  
  /**
   * Window.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new window.
   *
   * @-x-less Window.less
   * @class maya.ui.Window
   * @extends maya.ui.FloatPanel
   */
  define("maya/ui/Window", [
  	"maya/ui/FloatPanel",
  	"maya/ui/Panel",
  	"maya/ui/DomUtils",
  	"maya/ui/DragHelper"
  ], function(FloatPanel, Panel, DomUtils, DragHelper) {
  	"use strict";
  
  	var Window = FloatPanel.extend({
  		modal: true,
  
  		Defaults: {
  			border: 1,
  			layout: 'flex',
  			containerCls: 'panel',
  			role: 'dialog',
  			callbacks: {
  				submit: function() {
  					this.fire('submit', {data: this.toJSON()});
  				},
  
  				close: function() {
  					this.close();
  				}
  			}
  		},
  
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  
  			if (self.isRtl()) {
  				self.addClass('rtl');
  			}
  
  			self.addClass('window');
  			self._fixed = true;
  
  			// Create statusbar
  			if (settings.buttons) {
  				self.statusbar = new Panel({
  					layout: 'flex',
  					border: '1 0 0 0',
  					spacing: 3,
  					padding: 10,
  					align: 'center',
  					pack: self.isRtl() ? 'start' : 'end',
  					defaults: {
  						type: 'button'
  					},
  					items: settings.buttons
  				});
  
  				self.statusbar.addClass('foot');
  				self.statusbar.parent(self);
  			}
  
  			self.on('click', function(e) {
  				if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
  					self.close();
  				}
  			});
  
  			self.on('cancel', function() {
  				self.close();
  			});
  
  			self.aria('describedby', self.describedBy || self._id + '-none');
  			self.aria('label', settings.title);
  			self._fullscreen = false;
  		},
  
  		/**
  		 * Recalculates the positions of the controls in the current container.
  		 * This is invoked by the reflow method and shouldn't be called directly.
  		 *
  		 * @method recalc
  		 */
  		recalc: function() {
  			var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;
  
  			if (self._fullscreen) {
  				self.layoutRect(DomUtils.getWindowSize());
  				self.layoutRect().contentH = self.layoutRect().innerH;
  			}
  
  			self._super();
  
  			layoutRect = self.layoutRect();
  
  			// Resize window based on title width
  			if (self.settings.title && !self._fullscreen) {
  				width = layoutRect.headerW;
  				if (width > layoutRect.w) {
  					x = layoutRect.x - Math.max(0, width / 2);
  					self.layoutRect({w: width, x: x});
  					needsRecalc = true;
  				}
  			}
  
  			// Resize window based on statusbar width
  			if (statusbar) {
  				statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
  
  				width = statusbar.layoutRect().minW + layoutRect.deltaW;
  				if (width > layoutRect.w) {
  					x = layoutRect.x - Math.max(0, width - layoutRect.w);
  					self.layoutRect({w: width, x: x});
  					needsRecalc = true;
  				}
  			}
  
  			// Recalc body and disable auto resize
  			if (needsRecalc) {
  				self.recalc();
  			}
  		},
  
  		/**
  		 * Initializes the current controls layout rect.
  		 * This will be executed by the layout managers to determine the
  		 * default minWidth/minHeight etc.
  		 *
  		 * @method initLayoutRect
  		 * @return {Object} Layout rect instance.
  		 */
  		initLayoutRect: function() {
  			var self = this, layoutRect = self._super(), deltaH = 0, headEl;
  
  			// Reserve vertical space for title
  			if (self.settings.title && !self._fullscreen) {
  				headEl = self.getEl('head');
  
  				var size = DomUtils.getSize(headEl);
  
  				layoutRect.headerW = size.width;
  				layoutRect.headerH = size.height;
  
  				deltaH += layoutRect.headerH;
  			}
  
  			// Reserve vertical space for statusbar
  			if (self.statusbar) {
  				deltaH += self.statusbar.layoutRect().h;
  			}
  
  			layoutRect.deltaH += deltaH;
  			layoutRect.minH += deltaH;
  			//layoutRect.innerH -= deltaH;
  			layoutRect.h += deltaH;
  
  			var rect = DomUtils.getWindowSize();
  
  			layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2);
  			layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2);
  
  			return layoutRect;
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
  			var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
  
  			self.preRender();
  			layout.preRender(self);
  
  			if (settings.title) {
  				headerHtml = (
  					'<div id="' + id + '-head" class="' + prefix + 'window-head">' +
  						'<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
  						'<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
  						'<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
  					'</div>'
  				);
  			}
  
  			if (settings.url) {
  				html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
  			}
  
  			if (typeof html == "undefined") {
  				html = layout.renderHtml(self);
  			}
  
  			if (self.statusbar) {
  				footerHtml = self.statusbar.renderHtml();
  			}
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
  					'<div class="' + self.classPrefix + 'reset" role="application">' +
  						headerHtml +
  						'<div id="' + id + '-body" class="' + self.classes('body') + '">' +
  							html +
  						'</div>' +
  						footerHtml +
  					'</div>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Switches the window fullscreen mode.
  		 *
  		 * @method fullscreen
  		 * @param {Boolean} state True/false state.
  		 * @return {maya.ui.Window} Current window instance.
  		 */
  		fullscreen: function(state) {
  			var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
  
  			if (state != self._fullscreen) {
  				DomUtils.on(window, 'resize', function() {
  					var time;
  
  					if (self._fullscreen) {
  						// Time the layout time if it's to slow use a timeout to not hog the CPU
  						if (!slowRendering) {
  							time = new Date().getTime();
  
  							var rect = DomUtils.getWindowSize();
  							self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  
  							if ((new Date().getTime()) - time > 50) {
  								slowRendering = true;
  							}
  						} else {
  							if (!self._timer) {
  								self._timer = setTimeout(function() {
  									var rect = DomUtils.getWindowSize();
  									self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  
  									self._timer = 0;
  								}, 50);
  							}
  						}
  					}
  				});
  
  				layoutRect = self.layoutRect();
  				self._fullscreen = state;
  
  				if (!state) {
  					self._borderBox = self.parseBox(self.settings.border);
  					self.getEl('head').style.display = '';
  					layoutRect.deltaH += layoutRect.headerH;
  					DomUtils.removeClass(documentElement, prefix + 'fullscreen');
  					DomUtils.removeClass(document.body, prefix + 'fullscreen');
  					self.removeClass('fullscreen');
  					self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
  				} else {
  					self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
  
  					self._borderBox = self.parseBox('0');
  					self.getEl('head').style.display = 'none';
  					layoutRect.deltaH -= layoutRect.headerH + 2;
  					DomUtils.addClass(documentElement, prefix + 'fullscreen');
  					DomUtils.addClass(document.body, prefix + 'fullscreen');
  					self.addClass('fullscreen');
  
  					var rect = DomUtils.getWindowSize();
  					self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  				}
  			}
  
  			return self.reflow();
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this, startPos;
  
  			setTimeout(function() {
  				self.addClass('in');
  			}, 0);
  
  			self._super();
  
  			if (self.statusbar) {
  				self.statusbar.postRender();
  			}
  
  			self.focus();
  
  			this.dragHelper = new DragHelper(self._id + '-dragh', {
  				start: function() {
  					startPos = {
  						x: self.layoutRect().x,
  						y: self.layoutRect().y
  					};
  				},
  
  				drag: function(e) {
  					self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
  				}
  			});
  
  			self.on('submit', function(e) {
  				if (!e.isDefaultPrevented()) {
  					self.close();
  				}
  			});
  		},
  
  		/**
  		 * Fires a submit event with the serialized form.
  		 *
  		 * @method submit
  		 * @return {Object} Event arguments object.
  		 */
  		submit: function() {
  			return this.fire('submit', {data: this.toJSON()});
  		},
  
  		/**
  		 * Removes the current control from DOM and from UI collections.
  		 *
  		 * @method remove
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		remove: function() {
  			var self = this, prefix = self.classPrefix;
  
  			self.dragHelper.destroy();
  			self._super();
  
  			if (self.statusbar) {
  				this.statusbar.remove();
  			}
  
  			if (self._fullscreen) {
  				DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
  				DomUtils.removeClass(document.body, prefix + 'fullscreen');
  			}
  		},
  
  		/**
  		 * Returns the contentWindow object of the iframe if it exists.
  		 *
  		 * @method getContentWindow
  		 * @return {Window} window object or null.
  		 */
  		getContentWindow: function() {
  			var ifr = this.getEl().getElementsByTagName('iframe')[0];
  			return ifr ? ifr.contentWindow : null;
  		}
  	});
  
  	return Window;
  });
  
  // Included from: js/maya/classes/ui/MessageBox.js
  
  /**
   * MessageBox.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is used to create MessageBoxes like alerts/confirms etc.
   *
   * @class maya.ui.MessageBox
   * @extends maya.ui.Window
   */
  define("maya/ui/MessageBox", [
  	"maya/ui/Window"
  ], function(Window) {
  	"use strict";
  
  	var MessageBox = Window.extend({
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			settings = {
  				border: 1,
  				padding: 20,
  				layout: 'flex',
  				pack: "center",
  				align: "center",
  				containerCls: 'panel',
  				autoScroll: true,
  				buttons: {type: "button", text: "Ok", action: "ok"},
  				items: {
  					type: "label",
  					multiline: true,
  					maxWidth: 500,
  					maxHeight: 200
  				}
  			};
  
  			this._super(settings);
  		},
  
  		Statics: {
  			/**
  			 * Ok buttons constant.
  			 *
  			 * @static
  			 * @final
  			 * @field {Number} OK
  			 */
  			OK: 1,
  
  			/**
  			 * Ok/cancel buttons constant.
  			 *
  			 * @static
  			 * @final
  			 * @field {Number} OK_CANCEL
  			 */
  			OK_CANCEL: 2,
  
  			/**
  			 * yes/no buttons constant.
  			 *
  			 * @static
  			 * @final
  			 * @field {Number} YES_NO
  			 */
  			YES_NO: 3,
  
  			/**
  			 * yes/no/cancel buttons constant.
  			 *
  			 * @static
  			 * @final
  			 * @field {Number} YES_NO_CANCEL
  			 */
  			YES_NO_CANCEL: 4,
  
  			/**
  			 * Constructs a new message box and renders it to the body element.
  			 *
  			 * @static
  			 * @method msgBox
  			 * @param {Object} settings Name/value object with settings.
  			 */
  			msgBox: function(settings) {
  				var buttons, callback = settings.callback || function() {};
  
  				function createButton(text, status, primary) {
  					return {
  						type: "button",
  						text: text,
  						subtype: primary ? 'primary' : '',
  						onClick: function(e) {
  							e.control.parents()[1].close();
  							callback(status);
  						}
  					};
  				}
  
  				switch (settings.buttons) {
  					case MessageBox.OK_CANCEL:
  						buttons = [
  							createButton('Ok', true, true),
  							createButton('Cancel', false)
  						];
  						break;
  
  					case MessageBox.YES_NO:
  					case MessageBox.YES_NO_CANCEL:
  						buttons = [
  							createButton('Yes', 1, true),
  							createButton('No', 0)
  						];
  
  						if (settings.buttons == MessageBox.YES_NO_CANCEL) {
  							buttons.push(createButton('Cancel', -1));
  						}
  						break;
  
  					default:
  						buttons = [
  							createButton('Ok', true, true)
  						];
  						break;
  				}
  
  				return new Window({
  					padding: 20,
  					x: settings.x,
  					y: settings.y,
  					minWidth: 300,
  					minHeight: 100,
  					layout: "flex",
  					pack: "center",
  					align: "center",
  					buttons: buttons,
  					title: settings.title,
  					role: 'alertdialog',
  					items: {
  						type: "label",
  						multiline: true,
  						maxWidth: 500,
  						maxHeight: 200,
  						text: settings.text
  					},
  					onPostRender: function() {
  						this.aria('describedby', this.items()[0]._id);
  					},
  					onClose: settings.onClose,
  					onCancel: function() {
  						callback(false);
  					}
  				}).renderTo(document.body).reflow();
  			},
  
  			/**
  			 * Creates a new alert dialog.
  			 *
  			 * @method alert
  			 * @param {Object} settings Settings for the alert dialog.
  			 * @param {function} [callback] Callback to execute when the user makes a choice.
  			 */
  			alert: function(settings, callback) {
  				if (typeof settings == "string") {
  					settings = {text: settings};
  				}
  
  				settings.callback = callback;
  				return MessageBox.msgBox(settings);
  			},
  
  			/**
  			 * Creates a new confirm dialog.
  			 *
  			 * @method confirm
  			 * @param {Object} settings Settings for the confirm dialog.
  			 * @param {function} [callback] Callback to execute when the user makes a choice.
  			 */
  			confirm: function(settings, callback) {
  				if (typeof settings == "string") {
  					settings = {text: settings};
  				}
  
  				settings.callback = callback;
  				settings.buttons = MessageBox.OK_CANCEL;
  
  				return MessageBox.msgBox(settings);
  			}
  		}
  	});
  
  	return MessageBox;
  });
  
  // Included from: js/maya/classes/WindowManager.js
  
  /**
   * WindowManager.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
   *
   * @class maya.WindowManager
   * @example
   * // Opens a new dialog with the file.htm file and the size 320x240
   * // It also adds a custom parameter this can be retrieved by using mayaPopup.getWindowArg inside the dialog.
   * maya.activeEditor.windowManager.open({
   *    url: 'file.htm',
   *    width: 320,
   *    height: 240
   * }, {
   *    custom_param: 1
   * });
   *
   * // Displays an alert box using the active editors window manager instance
   * maya.activeEditor.windowManager.alert('Hello world!');
   *
   * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
   * maya.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
   *    if (s)
   *       maya.activeEditor.windowManager.alert("Ok");
   *    else
   *       maya.activeEditor.windowManager.alert("Cancel");
   * });
   */
  define("maya/WindowManager", [
  	"maya/ui/Window",
  	"maya/ui/MessageBox"
  ], function(Window, MessageBox) {
  	return function(editor) {
  		var self = this, windows = [];
  
  		function getTopMostWindow() {
  			if (windows.length) {
  				return windows[windows.length - 1];
  			}
  		}
  
  		self.windows = windows;
  
  		editor.on('remove', function() {
  			var i = windows.length;
  
  			while (i--) {
  				windows[i].close();
  			}
  		});
  
  		/**
  		 * Opens a new window.
  		 *
  		 * @method open
  		 * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
  		 * @option {String} title Window title.
  		 * @option {String} file URL of the file to open in the window.
  		 * @option {Number} width Width in pixels.
  		 * @option {Number} height Height in pixels.
  		 * @option {Boolean} autoScroll Specifies whether the popup window can have scrollbars if required (i.e. content
  		 * larger than the popup size specified).
  		 */
  		self.open = function(args, params) {
  			var win;
  
  			editor.editorManager.setActive(editor);
  
  			args.title = args.title || ' ';
  
  			// Handle URL
  			args.url = args.url || args.file; // Legacy
  			if (args.url) {
  				args.width = parseInt(args.width || 320, 10);
  				args.height = parseInt(args.height || 240, 10);
  			}
  
  			// Handle body
  			if (args.body) {
  				args.items = {
  					defaults: args.defaults,
  					type: args.bodyType || 'form',
  					items: args.body
  				};
  			}
  
  			if (!args.url && !args.buttons) {
  				args.buttons = [
  					{text: 'Ok', subtype: 'primary', onclick: function() {
  						win.find('form')[0].submit();
  					}},
  
  					{text: 'Cancel', onclick: function() {
  						win.close();
  					}}
  				];
  			}
  
  			win = new Window(args);
  			windows.push(win);
  
  			win.on('close', function() {
  				var i = windows.length;
  
  				while (i--) {
  					if (windows[i] === win) {
  						windows.splice(i, 1);
  					}
  				}
  
  				if (!windows.length) {
  					editor.focus();
  				}
  			});
  
  			// Handle data
  			if (args.data) {
  				win.on('postRender', function() {
  					this.find('*').each(function(ctrl) {
  						var name = ctrl.name();
  
  						if (name in args.data) {
  							ctrl.value(args.data[name]);
  						}
  					});
  				});
  			}
  
  			// store args and parameters
  			win.features = args || {};
  			win.params = params || {};
  
  			// Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
  			if (windows.length === 1) {
  				editor.nodeChanged();
  			}
  
  			return win.renderTo().reflow();
  		};
  
  		/**
  		 * Creates a alert dialog. Please don't use the blocking behavior of this
  		 * native version use the callback method instead then it can be extended.
  		 *
  		 * @method alert
  		 * @param {String} message Text to display in the new alert dialog.
  		 * @param {function} callback Callback function to be executed after the user has selected ok.
  		 * @param {Object} scope Optional scope to execute the callback in.
  		 * @example
  		 * // Displays an alert box using the active editors window manager instance
  		 * maya.activeEditor.windowManager.alert('Hello world!');
  		 */
  		self.alert = function(message, callback, scope) {
  			MessageBox.alert(message, function() {
  				if (callback) {
  					callback.call(scope || this);
  				} else {
  					editor.focus();
  				}
  			});
  		};
  
  		/**
  		 * Creates a confirm dialog. Please don't use the blocking behavior of this
  		 * native version use the callback method instead then it can be extended.
  		 *
  		 * @method confirm
  		 * @param {String} messageText to display in the new confirm dialog.
  		 * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
  		 * @param {Object} scope Optional scope to execute the callback in.
  		 * @example
  		 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
  		 * maya.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
  		 *    if (s)
  		 *       maya.activeEditor.windowManager.alert("Ok");
  		 *    else
  		 *       maya.activeEditor.windowManager.alert("Cancel");
  		 * });
  		 */
  		self.confirm = function(message, callback, scope) {
  			MessageBox.confirm(message, function(state) {
  				callback.call(scope || this, state);
  			});
  		};
  
  		/**
  		 * Closes the top most window.
  		 *
  		 * @method close
  		 */
  		self.close = function() {
  			if (getTopMostWindow()) {
  				getTopMostWindow().close();
  			}
  		};
  
  		/**
  		 * Returns the params of the last window open call. This can be used in iframe based
  		 * dialog to get params passed from the maya plugin.
  		 *
  		 * @example
  		 * var dialogArguments = top.maya.activeEditor.windowManager.getParams();
  		 *
  		 * @method getParams
  		 * @return {Object} Name/value object with parameters passed from windowManager.open call.
  		 */
  		self.getParams = function() {
  			return getTopMostWindow() ? getTopMostWindow().params : null;
  		};
  
  		/**
  		 * Sets the params of the last opened window.
  		 *
  		 * @method setParams
  		 * @param {Object} params Params object to set for the last opened window.
  		 */
  		self.setParams = function(params) {
  			if (getTopMostWindow()) {
  				getTopMostWindow().params = params;
  			}
  		};
  
  		/**
  		 * Returns the currently opened window objects.
  		 *
  		 * @method getWindows
  		 * @return {Array} Array of the currently opened windows.
  		 */
  		self.getWindows = function() {
  			return windows;
  		};
  	};
  });
  
  // Included from: js/maya/classes/util/Quirks.js
  
  /**
   * Quirks.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   *
   * @ignore-file
   */
  
  /**
   * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
   *
   * @class maya.util.Quirks
   */
  define("maya/util/Quirks", [
  	"maya/util/VK",
  	"maya/dom/RangeUtils",
  	"maya/dom/TreeWalker",
  	"maya/html/Node",
  	"maya/html/Entities",
  	"maya/Env",
  	"maya/util/Tools"
  ], function(VK, RangeUtils, TreeWalker, Node, Entities, Env, Tools) {
  	return function(editor) {
  		var each = Tools.each, $ = editor.$;
  		var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
  			settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
  		var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
  		var mceInternalUrlPrefix = 'data:text/mce-internal,';
  		var mceInternalDataType = isIE ? 'Text' : 'URL';
  
  		/**
  		 * Executes a command with a specific state this can be to enable/disable browser editing features.
  		 */
  		function setEditorCommandState(cmd, state) {
  			try {
  				editor.getDoc().execCommand(cmd, false, state);
  			} catch (ex) {
  				// Ignore
  			}
  		}
  
  		/**
  		 * Returns current IE document mode.
  		 */
  		function getDocumentMode() {
  			var documentMode = editor.getDoc().documentMode;
  
  			return documentMode ? documentMode : 6;
  		}
  
  		/**
  		 * Returns true/false if the event is prevented or not.
  		 *
  		 * @private
  		 * @param {Event} e Event object.
  		 * @return {Boolean} true/false if the event is prevented or not.
  		 */
  		function isDefaultPrevented(e) {
  			return e.isDefaultPrevented();
  		}
  
  		/**
  		 * Sets Text/URL data on the event's dataTransfer object to a special data:text/mce-internal url.
  		 * This is to workaround the inability to set custom contentType on IE and Safari.
  		 * The editor's selected content is encoded into this url so drag and drop between editors will work.
  		 *
  		 * @private
  		 * @param {DragEvent} e Event object
  		 */
  		function setMceInteralContent(e) {
  			var selectionHtml;
  
  			if (e.dataTransfer) {
  				if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
  					selection.select(e.target);
  				}
  
  				selectionHtml = editor.selection.getContent();
  
  				// Safari/IE doesn't support custom dataTransfer items so we can only use URL and Text
  				if (selectionHtml.length > 0) {
  					e.dataTransfer.setData(mceInternalDataType, mceInternalUrlPrefix + escape(selectionHtml));
  				}
  			}
  		}
  
  		/**
  		 * Gets content of special data:text/mce-internal url on the event's dataTransfer object.
  		 * This is to workaround the inability to set custom contentType on IE and Safari.
  		 * The editor's selected content is encoded into this url so drag and drop between editors will work.
  		 *
  		 * @private
  		 * @param {DragEvent} e Event object
  		 * @returns {String} mce-internal content
  		 */
  		function getMceInternalContent(e) {
  			var internalContent, content;
  
  			if (e.dataTransfer) {
  				internalContent = e.dataTransfer.getData(mceInternalDataType);
  
  				if (internalContent && internalContent.indexOf(mceInternalUrlPrefix) >= 0) {
  					content = unescape(internalContent.substr(mceInternalUrlPrefix.length));
  				}
  			}
  
  			return content;
  		}
  
  		/**
  		 * Inserts contents using the paste clipboard command if it's available if it isn't it will fallback
  		 * to the core command.
  		 *
  		 * @private
  		 * @param {String} content Content to insert at selection.
  		 */
  		function insertClipboardContents(content) {
  			if (editor.queryCommandSupported('mceInsertClipboardContent')) {
  				editor.execCommand('mceInsertClipboardContent', false, {content: content});
  			} else {
  				editor.execCommand('mceInsertContent', false, content);
  			}
  		}
  
  		/**
  		 * Fixes a WebKit bug when deleting contents using backspace or delete key.
  		 * WebKit will produce a span element if you delete across two block elements.
  		 *
  		 * Example:
  		 * <h1>a</h1><p>|b</p>
  		 *
  		 * Will produce this on backspace:
  		 * <h1>a<span style="<all runtime styles>">b</span></p>
  		 *
  		 * This fixes the backspace to produce:
  		 * <h1>a|b</p>
  		 *
  		 * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
  		 *
  		 * This fixes the following delete scenarios:
  		 *  1. Delete by pressing backspace key.
  		 *  2. Delete by pressing delete key.
  		 *  3. Delete by pressing backspace key with ctrl/cmd (Word delete).
  		 *  4. Delete by pressing delete key with ctrl/cmd (Word delete).
  		 *  5. Delete by drag/dropping contents inside the editor.
  		 *  6. Delete by using Cut Ctrl+X/Cmd+X.
  		 *  7. Delete by selecting contents and writing a character.
  		 *
  		 * This code is a ugly hack since writing full custom delete logic for just this bug
  		 * fix seemed like a huge task. I hope we can remove this before the year 2030.
  		 */
  		function cleanupStylesWhenDeleting() {
  			var doc = editor.getDoc(), dom = editor.dom, selection = editor.selection;
  			var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
  
  			// Add mini polyfill for older WebKits
  			// TODO: Remove this when old Safari versions gets updated
  			if (!MutationObserver) {
  				olderWebKit = true;
  
  				MutationObserver = function() {
  					var records = [], target;
  
  					function nodeInsert(e) {
  						var target = e.relatedNode || e.target;
  						records.push({target: target, addedNodes: [target]});
  					}
  
  					function attrModified(e) {
  						var target = e.relatedNode || e.target;
  						records.push({target: target, attributeName: e.attrName});
  					}
  
  					this.observe = function(node) {
  						target = node;
  						target.addEventListener('DOMSubtreeModified', nodeInsert, false);
  						target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
  						target.addEventListener('DOMNodeInserted', nodeInsert, false);
  						target.addEventListener('DOMAttrModified', attrModified, false);
  					};
  
  					this.disconnect = function() {
  						target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
  						target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
  						target.removeEventListener('DOMNodeInserted', nodeInsert, false);
  						target.removeEventListener('DOMAttrModified', attrModified, false);
  					};
  
  					this.takeRecords = function() {
  						return records;
  					};
  				};
  			}
  
  			function isTrailingBr(node) {
  				var blockElements = dom.schema.getBlockElements(), rootNode = editor.getBody();
  
  				if (node.nodeName != 'BR') {
  					return false;
  				}
  
  				for (node = node; node != rootNode && !blockElements[node.nodeName]; node = node.parentNode) {
  					if (node.nextSibling) {
  						return false;
  					}
  				}
  
  				return true;
  			}
  
  			function findCaretNode(node, forward, startNode) {
  				var walker, current, nonEmptyElements;
  
  				nonEmptyElements = dom.schema.getNonEmptyElements();
  
  				walker = new TreeWalker(startNode || node, node);
  
  				while ((current = walker[forward ? 'next' : 'prev']())) {
  					if (nonEmptyElements[current.nodeName] && !isTrailingBr(current)) {
  						return current;
  					}
  
  					if (current.nodeType == 3 && current.data.length > 0) {
  						return current;
  					}
  				}
  			}
  
  			function deleteRangeBetweenTextBlocks(rng) {
  				var startBlock, endBlock, caretNodeBefore, caretNodeAfter, textBlockElements;
  
  				if (rng.collapsed) {
  					return;
  				}
  
  				startBlock = dom.getParent(RangeUtils.getNode(rng.startContainer, rng.startOffset), dom.isBlock);
  				endBlock = dom.getParent(RangeUtils.getNode(rng.endContainer, rng.endOffset), dom.isBlock);
  				textBlockElements = editor.schema.getTextBlockElements();
  
  				if (startBlock == endBlock) {
  					return;
  				}
  
  				if (!textBlockElements[startBlock.nodeName] || !textBlockElements[endBlock.nodeName]) {
  					return;
  				}
  
  				if (dom.getContentEditable(startBlock) === "false" || dom.getContentEditable(endBlock) === "false") {
  					return;
  				}
  
  				rng.deleteContents();
  
  				caretNodeBefore = findCaretNode(startBlock, false);
  				caretNodeAfter = findCaretNode(endBlock, true);
  
  				if (!dom.isEmpty(endBlock)) {
  					$(startBlock).append(endBlock.childNodes);
  				}
  
  				$(endBlock).remove();
  
  				if (caretNodeBefore) {
  					if (caretNodeBefore.nodeType == 1) {
  						if (caretNodeBefore.nodeName == "BR") {
  							rng.setStartBefore(caretNodeBefore);
  							rng.setEndBefore(caretNodeBefore);
  						} else {
  							rng.setStartAfter(caretNodeBefore);
  							rng.setEndAfter(caretNodeBefore);
  						}
  					} else {
  						rng.setStart(caretNodeBefore, caretNodeBefore.data.length);
  						rng.setEnd(caretNodeBefore, caretNodeBefore.data.length);
  					}
  				} else if (caretNodeAfter) {
  					if (caretNodeAfter.nodeType == 1) {
  						rng.setStartBefore(caretNodeAfter);
  						rng.setEndBefore(caretNodeAfter);
  					} else {
  						rng.setStart(caretNodeAfter, 0);
  						rng.setEnd(caretNodeAfter, 0);
  					}
  				}
  
  				selection.setRng(rng);
  
  				return true;
  			}
  
  			function expandBetweenBlocks(rng, isForward) {
  				var caretNode, targetCaretNode, textBlock, targetTextBlock, container, offset;
  
  				if (!rng.collapsed) {
  					return rng;
  				}
  
  				container = rng.startContainer;
  				offset = rng.startOffset;
  
  				if (container.nodeType == 3) {
  					if (isForward) {
  						if (offset < container.data.length) {
  							return rng;
  						}
  					} else {
  						if (offset > 0) {
  							return rng;
  						}
  					}
  				}
  
  				caretNode = RangeUtils.getNode(rng.startContainer, rng.startOffset);
  				textBlock = dom.getParent(caretNode, dom.isBlock);
  				targetCaretNode = findCaretNode(editor.getBody(), isForward, caretNode);
  				targetTextBlock = dom.getParent(targetCaretNode, dom.isBlock);
  
  				if (!caretNode || !targetCaretNode) {
  					return rng;
  				}
  
  				if (textBlock != targetTextBlock) {
  					if (!isForward) {
  						if (targetCaretNode.nodeType == 1) {
  							if (targetCaretNode.nodeName == "BR") {
  								rng.setStartBefore(targetCaretNode);
  							} else {
  								rng.setStartAfter(targetCaretNode);
  							}
  						} else {
  							rng.setStart(targetCaretNode, targetCaretNode.data.length);
  						}
  
  						if (caretNode.nodeType == 1) {
  							rng.setEnd(caretNode, 0);
  						} else {
  							rng.setEndBefore(caretNode);
  						}
  					} else {
  						if (caretNode.nodeType == 1) {
  							if (caretNode.nodeName == "BR") {
  								rng.setStartBefore(caretNode);
  							} else {
  								rng.setStartAfter(caretNode);
  							}
  						} else {
  							rng.setStart(caretNode, caretNode.data.length);
  						}
  
  						if (targetCaretNode.nodeType == 1) {
  							rng.setEnd(targetCaretNode, 0);
  						} else {
  							rng.setEndBefore(targetCaretNode);
  						}
  					}
  				}
  
  				return rng;
  			}
  
  			function handleTextBlockMergeDelete(isForward) {
  				var rng = selection.getRng();
  
  				rng = expandBetweenBlocks(rng, isForward);
  
  				if (deleteRangeBetweenTextBlocks(rng)) {
  					return true;
  				}
  			}
  
  			function customDelete(isForward) {
  				var mutationObserver, rng, caretElement;
  
  				if (handleTextBlockMergeDelete(isForward)) {
  					return;
  				}
  
  				Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
  					// Mark existing spans
  					if (elm.tagName == 'SPAN') {
  						elm.setAttribute('mce-data-marked', 1);
  					}
  
  					// Make sure all elements has a data-mce-style attribute
  					if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
  						editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style'));
  					}
  				});
  
  				// Observe added nodes and style attribute changes
  				mutationObserver = new MutationObserver(function() {});
  				mutationObserver.observe(editor.getDoc(), {
  					childList: true,
  					attributes: true,
  					subtree: true,
  					attributeFilter: ['style']
  				});
  
  				editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
  
  				rng = editor.selection.getRng();
  				caretElement = rng.startContainer.parentNode;
  
  				Tools.each(mutationObserver.takeRecords(), function(record) {
  					if (!dom.isChildOf(record.target, editor.getBody())) {
  						return;
  					}
  
  					// Restore style attribute to previous value
  					if (record.attributeName == "style") {
  						var oldValue = record.target.getAttribute('data-mce-style');
  
  						if (oldValue) {
  							record.target.setAttribute("style", oldValue);
  						} else {
  							record.target.removeAttribute("style");
  						}
  					}
  
  					// Remove all spans that isn't maked and retain selection
  					Tools.each(record.addedNodes, function(node) {
  						if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
  							var offset, container;
  
  							if (node == caretElement) {
  								offset = rng.startOffset;
  								container = node.firstChild;
  							}
  
  							dom.remove(node, true);
  
  							if (container) {
  								rng.setStart(container, offset);
  								rng.setEnd(container, offset);
  								editor.selection.setRng(rng);
  							}
  						}
  					});
  				});
  
  				mutationObserver.disconnect();
  
  				// Remove any left over marks
  				Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
  					span.removeAttribute('mce-data-marked');
  				});
  			}
  
  			editor.on('keydown', function(e) {
  				var isForward = e.keyCode == DELETE, isMetaOrCtrl = e.ctrlKey || e.metaKey;
  
  				if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
  					var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
  
  					// Ignore non meta delete in the where there is text before/after the caret
  					if (!isMetaOrCtrl && rng.collapsed && container.nodeType == 3) {
  						if (isForward ? offset < container.data.length : offset > 0) {
  							return;
  						}
  					}
  
  					e.preventDefault();
  
  					if (isMetaOrCtrl) {
  						editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", e.metaKey ? "lineboundary" : "word");
  					}
  
  					customDelete(isForward);
  				}
  			});
  
  			// Handle case where text is deleted by typing over
  			editor.on('keypress', function(e) {
  				if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) {
  					var rng, currentFormatNodes, fragmentNode, blockParent, caretNode, charText;
  
  					rng = editor.selection.getRng();
  					charText = String.fromCharCode(e.charCode);
  					e.preventDefault();
  
  					// Keep track of current format nodes
  					currentFormatNodes = $(rng.startContainer).parents().filter(function(idx, node) {
  						return !!editor.schema.getTextInlineElements()[node.nodeName];
  					});
  
  					customDelete(true);
  
  					// Check if the browser removed them
  					currentFormatNodes = currentFormatNodes.filter(function(idx, node) {
  						return !$.contains(editor.getBody(), node);
  					});
  
  					// Then re-add them
  					if (currentFormatNodes.length) {
  						fragmentNode = dom.createFragment();
  
  						currentFormatNodes.each(function(idx, formatNode) {
  							formatNode = formatNode.cloneNode(false);
  
  							if (fragmentNode.hasChildNodes()) {
  								formatNode.appendChild(fragmentNode.firstChild);
  								fragmentNode.appendChild(formatNode);
  							} else {
  								caretNode = formatNode;
  								fragmentNode.appendChild(formatNode);
  							}
  
  							fragmentNode.appendChild(formatNode);
  						});
  
  						caretNode.appendChild(editor.getDoc().createTextNode(charText));
  
  						// Prevent edge case where older WebKit would add an extra BR element
  						blockParent = dom.getParent(rng.startContainer, dom.isBlock);
  						if (dom.isEmpty(blockParent)) {
  							$(blockParent).empty().append(fragmentNode);
  						} else {
  							rng.insertNode(fragmentNode);
  						}
  
  						rng.setStart(caretNode.firstChild, 1);
  						rng.setEnd(caretNode.firstChild, 1);
  						editor.selection.setRng(rng);
  					} else {
  						editor.selection.setContent(charText);
  					}
  				}
  			});
  
  			editor.addCommand('Delete', function() {
  				customDelete();
  			});
  
  			editor.addCommand('ForwardDelete', function() {
  				customDelete(true);
  			});
  
  			// Older WebKits doesn't properly handle the clipboard so we can't add the rest
  			if (olderWebKit) {
  				return;
  			}
  
  			editor.on('dragstart', function(e) {
  				dragStartRng = selection.getRng();
  				setMceInteralContent(e);
  			});
  
  			editor.on('drop', function(e) {
  				if (!isDefaultPrevented(e)) {
  					var internalContent = getMceInternalContent(e);
  					if (internalContent) {
  						e.preventDefault();
  
  						// Safari has a weird issue where drag/dropping images sometimes
  						// produces a green plus icon. When this happens the caretRangeFromPoint
  						// will return "null" even though the x, y coordinate is correct.
  						// But if we detach the insert from the drop event we will get a proper range
  						window.setTimeout(function() {
  							var pointRng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, doc);
  
  							if (dragStartRng) {
  								selection.setRng(dragStartRng);
  								dragStartRng = null;
  							}
  
  							customDelete();
  							selection.setRng(pointRng);
  							insertClipboardContents(internalContent);
  						}, 0);
  					}
  				}
  			});
  
  			editor.on('cut', function(e) {
  				if (!isDefaultPrevented(e) && e.clipboardData) {
  					e.preventDefault();
  					e.clipboardData.clearData();
  					e.clipboardData.setData('text/html', editor.selection.getContent());
  					e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
  					customDelete(true);
  				}
  			});
  		}
  
  		/**
  		 * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
  		 *
  		 * For example:
  		 * <p><b>|</b></p>
  		 *
  		 * Or:
  		 * <h1>|</h1>
  		 *
  		 * Or:
  		 * [<h1></h1>]
  		 */
  		function emptyEditorWhenDeleting() {
  			function serializeRng(rng) {
  				var body = dom.create("body");
  				var contents = rng.cloneContents();
  				body.appendChild(contents);
  				return selection.serializer.serialize(body, {format: 'html'});
  			}
  
  			function allContentsSelected(rng) {
  				if (!rng.setStart) {
  					if (rng.item) {
  						return false;
  					}
  
  					var bodyRng = rng.duplicate();
  					bodyRng.moveToElementText(editor.getBody());
  					return RangeUtils.compareRanges(rng, bodyRng);
  				}
  
  				var selection = serializeRng(rng);
  
  				var allRng = dom.createRng();
  				allRng.selectNode(editor.getBody());
  
  				var allSelection = serializeRng(allRng);
  				return selection === allSelection;
  			}
  
  			editor.on('keydown', function(e) {
  				var keyCode = e.keyCode, isCollapsed, body;
  
  				// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
  				if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
  					isCollapsed = editor.selection.isCollapsed();
  					body = editor.getBody();
  
  					// Selection is collapsed but the editor isn't empty
  					if (isCollapsed && !dom.isEmpty(body)) {
  						return;
  					}
  
  					// Selection isn't collapsed but not all the contents is selected
  					if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
  						return;
  					}
  
  					// Manually empty the editor
  					e.preventDefault();
  					editor.setContent('');
  
  					if (body.firstChild && dom.isBlock(body.firstChild)) {
  						editor.selection.setCursorLocation(body.firstChild, 0);
  					} else {
  						editor.selection.setCursorLocation(body, 0);
  					}
  
  					editor.nodeChanged();
  				}
  			});
  		}
  
  		/**
  		 * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
  		 * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
  		 * This selects the whole body so that backspace/delete logic will delete everything
  		 */
  		function selectAll() {
  			editor.shortcuts.add('meta+a', null, 'SelectAll');
  		}
  
  		/**
  		 * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
  		 * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
  		 *
  		 * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
  		 * you enter a character into the editor.
  		 *
  		 * It also happens when the first focus in made to the body.
  		 *
  		 * See: https://bugs.webkit.org/show_bug.cgi?id=83566
  		 */
  		function inputMethodFocus() {
  			if (!editor.settings.content_editable) {
  				// Case 1 IME doesn't initialize if you focus the document
  				dom.bind(editor.getDoc(), 'focusin', function() {
  					selection.setRng(selection.getRng());
  				});
  
  				// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
  				// Needs to be both down/up due to weird rendering bug on Chrome Windows
  				dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) {
  					if (e.target == editor.getDoc().documentElement) {
  						editor.getBody().focus();
  
  						if (e.type == 'mousedown') {
  							// Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret
  							selection.placeCaretAt(e.clientX, e.clientY);
  						} else {
  							selection.setRng(selection.getRng());
  						}
  					}
  				});
  			}
  		}
  
  		/**
  		 * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
  		 * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
  		 * left there. maya sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
  		 * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
  		 * browsers.
  		 *
  		 * It also fixes a bug on Firefox where it's impossible to delete HR elements.
  		 */
  		function removeHrOnBackspace() {
  			editor.on('keydown', function(e) {
  				if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  					// Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow
  					if (!editor.getBody().getElementsByTagName('hr').length) {
  						return;
  					}
  
  					if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  						var node = selection.getNode();
  						var previousSibling = node.previousSibling;
  
  						if (node.nodeName == 'HR') {
  							dom.remove(node);
  							e.preventDefault();
  							return;
  						}
  
  						if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
  							dom.remove(previousSibling);
  							e.preventDefault();
  						}
  					}
  				}
  			});
  		}
  
  		/**
  		 * Firefox 3.x has an issue where the body element won't get proper focus if you click out
  		 * side it's rectangle.
  		 */
  		function focusBody() {
  			// Fix for a focus bug in FF 3.x where the body element
  			// wouldn't get proper focus if the user clicked on the HTML element
  			if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
  				editor.on('mousedown', function(e) {
  					if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
  						var body = editor.getBody();
  
  						// Blur the body it's focused but not correctly focused
  						body.blur();
  
  						// Refocus the body after a little while
  						setTimeout(function() {
  							body.focus();
  						}, 0);
  					}
  				});
  			}
  		}
  
  		/**
  		 * WebKit has a bug where it isn't possible to select image, hr or anchor elements
  		 * by clicking on them so we need to fake that.
  		 */
  		function selectControlElements() {
  			editor.on('click', function(e) {
  				var target = e.target;
  
  				// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
  				// WebKit can't even do simple things like selecting an image
  				// Needs to be the setBaseAndExtend or it will fail to select floated images
  				if (/^(IMG|HR)$/.test(target.nodeName)) {
  					e.preventDefault();
  					selection.getSel().setBaseAndExtent(target, 0, target, 1);
  					editor.nodeChanged();
  				}
  
  				if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) {
  					e.preventDefault();
  					selection.select(target);
  				}
  			});
  		}
  
  		/**
  		 * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
  		 *
  		 * Fixes do backspace/delete on this:
  		 * <p>bla[ck</p><p style="color:red">r]ed</p>
  		 *
  		 * Would become:
  		 * <p>bla|ed</p>
  		 *
  		 * Instead of:
  		 * <p style="color:red">bla|ed</p>
  		 */
  		function removeStylesWhenDeletingAcrossBlockElements() {
  			function getAttributeApplyFunction() {
  				var template = dom.getAttribs(selection.getStart().cloneNode(false));
  
  				return function() {
  					var target = selection.getStart();
  
  					if (target !== editor.getBody()) {
  						dom.setAttrib(target, "style", null);
  
  						each(template, function(attr) {
  							target.setAttributeNode(attr.cloneNode(true));
  						});
  					}
  				};
  			}
  
  			function isSelectionAcrossElements() {
  				return !selection.isCollapsed() &&
  					dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
  			}
  
  			editor.on('keypress', function(e) {
  				var applyAttributes;
  
  				if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
  					applyAttributes = getAttributeApplyFunction();
  					editor.getDoc().execCommand('delete', false, null);
  					applyAttributes();
  					e.preventDefault();
  					return false;
  				}
  			});
  
  			dom.bind(editor.getDoc(), 'cut', function(e) {
  				var applyAttributes;
  
  				if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
  					applyAttributes = getAttributeApplyFunction();
  
  					setTimeout(function() {
  						applyAttributes();
  					}, 0);
  				}
  			});
  		}
  
  		/**
  		 * Screen readers on IE needs to have the role application set on the body.
  		 */
  		function ensureBodyHasRoleApplication() {
  			document.body.setAttribute("role", "application");
  		}
  
  		/**
  		 * Backspacing into a table behaves differently depending upon browser type.
  		 * Therefore, disable Backspace when cursor immediately follows a table.
  		 */
  		function disableBackspaceIntoATable() {
  			editor.on('keydown', function(e) {
  				if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  					if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  						var previousSibling = selection.getNode().previousSibling;
  						if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
  							e.preventDefault();
  							return false;
  						}
  					}
  				}
  			});
  		}
  
  		/**
  		 * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
  		 * logic adds a \n before the BR so that it will get rendered.
  		 */
  		function addNewLinesBeforeBrInPre() {
  			// IE8+ rendering mode does the right thing with BR in PRE
  			if (getDocumentMode() > 7) {
  				return;
  			}
  
  			// Enable display: none in area and add a specific class that hides all BR elements in PRE to
  			// avoid the caret from getting stuck at the BR elements while pressing the right arrow key
  			setEditorCommandState('RespectVisibilityInDesign', true);
  			editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
  			dom.addClass(editor.getBody(), 'mceHideBrInPre');
  
  			// Adds a \n before all BR elements in PRE to get them visual
  			parser.addNodeFilter('pre', function(nodes) {
  				var i = nodes.length, brNodes, j, brElm, sibling;
  
  				while (i--) {
  					brNodes = nodes[i].getAll('br');
  					j = brNodes.length;
  					while (j--) {
  						brElm = brNodes[j];
  
  						// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
  						sibling = brElm.prev;
  						if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
  							sibling.value += '\n';
  						} else {
  							brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
  						}
  					}
  				}
  			});
  
  			// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
  			serializer.addNodeFilter('pre', function(nodes) {
  				var i = nodes.length, brNodes, j, brElm, sibling;
  
  				while (i--) {
  					brNodes = nodes[i].getAll('br');
  					j = brNodes.length;
  					while (j--) {
  						brElm = brNodes[j];
  						sibling = brElm.prev;
  						if (sibling && sibling.type == 3) {
  							sibling.value = sibling.value.replace(/\r?\n$/, '');
  						}
  					}
  				}
  			});
  		}
  
  		/**
  		 * Moves style width/height to attribute width/height when the user resizes an image on IE.
  		 */
  		function removePreSerializedStylesWhenSelectingControls() {
  			dom.bind(editor.getBody(), 'mouseup', function() {
  				var value, node = selection.getNode();
  
  				// Moved styles to attributes on IMG eements
  				if (node.nodeName == 'IMG') {
  					// Convert style width to width attribute
  					if ((value = dom.getStyle(node, 'width'))) {
  						dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
  						dom.setStyle(node, 'width', '');
  					}
  
  					// Convert style height to height attribute
  					if ((value = dom.getStyle(node, 'height'))) {
  						dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
  						dom.setStyle(node, 'height', '');
  					}
  				}
  			});
  		}
  
  		/**
  		 * Removes a blockquote when backspace is pressed at the beginning of it.
  		 *
  		 * For example:
  		 * <blockquote><p>|x</p></blockquote>
  		 *
  		 * Becomes:
  		 * <p>|x</p>
  		 */
  		function removeBlockQuoteOnBackSpace() {
  			// Add block quote deletion handler
  			editor.on('keydown', function(e) {
  				var rng, container, offset, root, parent;
  
  				if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
  					return;
  				}
  
  				rng = selection.getRng();
  				container = rng.startContainer;
  				offset = rng.startOffset;
  				root = dom.getRoot();
  				parent = container;
  
  				if (!rng.collapsed || offset !== 0) {
  					return;
  				}
  
  				while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
  					parent = parent.parentNode;
  				}
  
  				// Is the cursor at the beginning of a blockquote?
  				if (parent.tagName === 'BLOCKQUOTE') {
  					// Remove the blockquote
  					editor.formatter.toggle('blockquote', null, parent);
  
  					// Move the caret to the beginning of container
  					rng = dom.createRng();
  					rng.setStart(container, 0);
  					rng.setEnd(container, 0);
  					selection.setRng(rng);
  				}
  			});
  		}
  
  		/**
  		 * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
  		 */
  		function setGeckoEditingOptions() {
  			function setOpts() {
  				editor._refreshContentEditable();
  
  				setEditorCommandState("StyleWithCSS", false);
  				setEditorCommandState("enableInlineTableEditing", false);
  
  				if (!settings.object_resizing) {
  					setEditorCommandState("enableObjectResizing", false);
  				}
  			}
  
  			if (!settings.readonly) {
  				editor.on('BeforeExecCommand MouseDown', setOpts);
  			}
  		}
  
  		/**
  		 * Fixes a gecko link bug, when a link is placed at the end of block elements there is
  		 * no way to move the caret behind the link. This fix adds a bogus br element after the link.
  		 *
  		 * For example this:
  		 * <p><b><a href="#">x</a></b></p>
  		 *
  		 * Becomes this:
  		 * <p><b><a href="#">x</a></b><br></p>
  		 */
  		function addBrAfterLastLinks() {
  			function fixLinks() {
  				each(dom.select('a'), function(node) {
  					var parentNode = node.parentNode, root = dom.getRoot();
  
  					if (parentNode.lastChild === node) {
  						while (parentNode && !dom.isBlock(parentNode)) {
  							if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
  								return;
  							}
  
  							parentNode = parentNode.parentNode;
  						}
  
  						dom.add(parentNode, 'br', {'data-mce-bogus': 1});
  					}
  				});
  			}
  
  			editor.on('SetContent ExecCommand', function(e) {
  				if (e.type == "setcontent" || e.command === 'mceInsertLink') {
  					fixLinks();
  				}
  			});
  		}
  
  		/**
  		 * WebKit will produce DIV elements here and there by default. But since maya uses paragraphs by
  		 * default we want to change that behavior.
  		 */
  		function setDefaultBlockType() {
  			if (settings.forced_root_block) {
  				editor.on('init', function() {
  					setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
  				});
  			}
  		}
  
  		/**
  		 * Removes ghost selections from images/tables on Gecko.
  		 */
  		function removeGhostSelection() {
  			editor.on('Undo Redo SetContent', function(e) {
  				if (!e.initial) {
  					editor.execCommand('mceRepaint');
  				}
  			});
  		}
  
  		/**
  		 * Deletes the selected image on IE instead of navigating to previous page.
  		 */
  		function deleteControlItemOnBackSpace() {
  			editor.on('keydown', function(e) {
  				var rng;
  
  				if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
  					rng = editor.getDoc().selection.createRange();
  					if (rng && rng.item) {
  						e.preventDefault();
  						editor.undoManager.beforeChange();
  						dom.remove(rng.item(0));
  						editor.undoManager.add();
  					}
  				}
  			});
  		}
  
  		/**
  		 * IE10 doesn't properly render block elements with the right height until you add contents to them.
  		 * This fixes that by adding a padding-right to all empty text block elements.
  		 * See: https://connect.microsoft.com/IE/feedback/details/743881
  		 */
  		function renderEmptyBlocksFix() {
  			var emptyBlocksCSS;
  
  			// IE10+
  			if (getDocumentMode() >= 10) {
  				emptyBlocksCSS = '';
  				each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
  					emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
  				});
  
  				editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
  			}
  		}
  
  		/**
  		 * Old IE versions can't retain contents within noscript elements so this logic will store the contents
  		 * as a attribute and the insert that value as it's raw text when the DOM is serialized.
  		 */
  		function keepNoScriptContents() {
  			if (getDocumentMode() < 9) {
  				parser.addNodeFilter('noscript', function(nodes) {
  					var i = nodes.length, node, textNode;
  
  					while (i--) {
  						node = nodes[i];
  						textNode = node.firstChild;
  
  						if (textNode) {
  							node.attr('data-mce-innertext', textNode.value);
  						}
  					}
  				});
  
  				serializer.addNodeFilter('noscript', function(nodes) {
  					var i = nodes.length, node, textNode, value;
  
  					while (i--) {
  						node = nodes[i];
  						textNode = nodes[i].firstChild;
  
  						if (textNode) {
  							textNode.value = Entities.decode(textNode.value);
  						} else {
  							// Old IE can't retain noscript value so an attribute is used to store it
  							value = node.attributes.map['data-mce-innertext'];
  							if (value) {
  								node.attr('data-mce-innertext', null);
  								textNode = new Node('#text', 3);
  								textNode.value = value;
  								textNode.raw = true;
  								node.append(textNode);
  							}
  						}
  					}
  				});
  			}
  		}
  
  		/**
  		 * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
  		 */
  		function fixCaretSelectionOfDocumentElementOnIe() {
  			var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
  
  			// Return range from point or null if it failed
  			function rngFromPoint(x, y) {
  				var rng = body.createTextRange();
  
  				try {
  					rng.moveToPoint(x, y);
  				} catch (ex) {
  					// IE sometimes throws and exception, so lets just ignore it
  					rng = null;
  				}
  
  				return rng;
  			}
  
  			// Fires while the selection is changing
  			function selectionChange(e) {
  				var pointRng;
  
  				// Check if the button is down or not
  				if (e.button) {
  					// Create range from mouse position
  					pointRng = rngFromPoint(e.x, e.y);
  
  					if (pointRng) {
  						// Check if pointRange is before/after selection then change the endPoint
  						if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
  							pointRng.setEndPoint('StartToStart', startRng);
  						} else {
  							pointRng.setEndPoint('EndToEnd', startRng);
  						}
  
  						pointRng.select();
  					}
  				} else {
  					endSelection();
  				}
  			}
  
  			// Removes listeners
  			function endSelection() {
  				var rng = doc.selection.createRange();
  
  				// If the range is collapsed then use the last start range
  				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
  					startRng.select();
  				}
  
  				dom.unbind(doc, 'mouseup', endSelection);
  				dom.unbind(doc, 'mousemove', selectionChange);
  				startRng = started = 0;
  			}
  
  			// Make HTML element unselectable since we are going to handle selection by hand
  			doc.documentElement.unselectable = true;
  
  			// Detect when user selects outside BODY
  			dom.bind(doc, 'mousedown contextmenu', function(e) {
  				if (e.target.nodeName === 'HTML') {
  					if (started) {
  						endSelection();
  					}
  
  					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
  					htmlElm = doc.documentElement;
  					if (htmlElm.scrollHeight > htmlElm.clientHeight) {
  						return;
  					}
  
  					started = 1;
  					// Setup start position
  					startRng = rngFromPoint(e.x, e.y);
  					if (startRng) {
  						// Listen for selection change events
  						dom.bind(doc, 'mouseup', endSelection);
  						dom.bind(doc, 'mousemove', selectionChange);
  
  						dom.getRoot().focus();
  						startRng.select();
  					}
  				}
  			});
  		}
  
  		/**
  		 * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
  		 * this fix will lean the caret right into the closest inline element.
  		 */
  		function normalizeSelection() {
  			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
  			editor.on('keyup focusin mouseup', function(e) {
  				if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
  					selection.normalize();
  				}
  			}, true);
  		}
  
  		/**
  		 * Forces Gecko to render a broken image icon if it fails to load an image.
  		 */
  		function showBrokenImageIcon() {
  			editor.contentStyles.push(
  				'img:-moz-broken {' +
  					'-moz-force-broken-image-icon:1;' +
  					'min-width:24px;' +
  					'min-height:24px' +
  				'}'
  			);
  		}
  
  		/**
  		 * iOS has a bug where it's impossible to type if the document has a touchstart event
  		 * bound and the user touches the document while having the on screen keyboard visible.
  		 *
  		 * The touch event moves the focus to the parent document while having the caret inside the iframe
  		 * this fix moves the focus back into the iframe document.
  		 */
  		function restoreFocusOnKeyDown() {
  			if (!editor.inline) {
  				editor.on('keydown', function() {
  					if (document.activeElement == document.body) {
  						editor.getWin().focus();
  					}
  				});
  			}
  		}
  
  		/**
  		 * IE 11 has an annoying issue where you can't move focus into the editor
  		 * by clicking on the white area HTML element. We used to be able to to fix this with
  		 * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
  		 * object it's not possible anymore. So we need to hack in a ungly CSS to force the
  		 * body to be at least 150px. If the user clicks the HTML element out side this 150px region
  		 * we simply move the focus into the first paragraph. Not ideal since you loose the
  		 * positioning of the caret but goot enough for most cases.
  		 */
  		function bodyHeight() {
  			if (!editor.inline) {
  				editor.contentStyles.push('body {min-height: 150px}');
  				editor.on('click', function(e) {
  					if (e.target.nodeName == 'HTML') {
  						var rng;
  
  						// Need to store away non collapsed ranges since the focus call will mess that up see #7382
  						rng = editor.selection.getRng();
  						editor.getBody().focus();
  						editor.selection.setRng(rng);
  						editor.selection.normalize();
  						editor.nodeChanged();
  					}
  				});
  			}
  		}
  
  		/**
  		 * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
  		 * You might then loose all your work so we need to block that behavior and replace it with our own.
  		 */
  		function blockCmdArrowNavigation() {
  			if (Env.mac) {
  				editor.on('keydown', function(e) {
  					if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) {
  						e.preventDefault();
  						editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word');
  					}
  				});
  			}
  		}
  
  		/**
  		 * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
  		 */
  		function disableAutoUrlDetect() {
  			setEditorCommandState("AutoUrlDetect", false);
  		}
  
  		/**
  		 * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
  		 * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
  		 * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
  		 * but not as the lastChild of the body. However is we add a BR element to the body then remove it
  		 * it doesn't seem to add these BR elements makes sence right?!
  		 *
  		 * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
  		 */
  		function doubleTrailingBrElements() {
  			if (!editor.inline) {
  				editor.on('focus blur beforegetcontent', function() {
  					var br = editor.dom.create('br');
  					editor.getBody().appendChild(br);
  					br.parentNode.removeChild(br);
  				}, true);
  			}
  		}
  
  		/**
  		 * iOS 7.1 introduced two new bugs:
  		 * 1) It's possible to open links within a contentEditable area by clicking on them.
  		 * 2) If you hold down the finger it will display the link/image touch callout menu.
  		 */
  		function tapLinksAndImages() {
  			editor.on('click', function(e) {
  				var elm = e.target;
  
  				do {
  					if (elm.tagName === 'A') {
  						e.preventDefault();
  						return;
  					}
  				} while ((elm = elm.parentNode));
  			});
  
  			editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
  		}
  
  		/**
  		 * iOS Safari and possible other browsers have a bug where it won't fire
  		 * a click event when a contentEditable is focused. This function fakes click events
  		 * by using touchstart/touchend and measuring the time and distance travelled.
  		 */
  		function touchClickEvent() {
  			editor.on('touchstart', function(e) {
  				var elm, time, startTouch, changedTouches;
  
  				elm = e.target;
  				time = new Date().getTime();
  				changedTouches = e.changedTouches;
  
  				if (!changedTouches || changedTouches.length > 1) {
  					return;
  				}
  
  				startTouch = changedTouches[0];
  
  				editor.once('touchend', function(e) {
  					var endTouch = e.changedTouches[0], args;
  
  					if (new Date().getTime() - time > 500) {
  						return;
  					}
  
  					if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) {
  						return;
  					}
  
  					if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) {
  						return;
  					}
  
  					args = {
  						target: elm
  					};
  
  					each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) {
  						args[key] = endTouch[key];
  					});
  
  					args = editor.fire('click', args);
  
  					if (!args.isDefaultPrevented()) {
  						// iOS WebKit can't place the caret properly once
  						// you bind touch events so we need to do this manually
  						// TODO: Expand to the closest word? Touble tap still works.
  						editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY);
  						editor.nodeChanged();
  					}
  				});
  			});
  		}
  
  		/**
  		 * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
  		 * For example this: <form><button></form>
  		 */
  		function blockFormSubmitInsideEditor() {
  			editor.on('init', function() {
  				editor.dom.bind(editor.getBody(), 'submit', function(e) {
  					e.preventDefault();
  				});
  			});
  		}
  
  		/**
  		 * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
  		 *
  		 * Scenario:
  		 *  1) Create a table 2x2.
  		 *  2) Select and copy cells A2-B2.
  		 *  3) Paste and it will add BR element to table cell.
  		 */
  		function removeAppleInterchangeBrs() {
  			parser.addNodeFilter('br', function(nodes) {
  				var i = nodes.length;
  
  				while (i--) {
  					if (nodes[i].attr('class') == 'Apple-interchange-newline') {
  						nodes[i].remove();
  					}
  				}
  			});
  		}
  
  		/**
  		 * IE cannot set custom contentType's on drag events, and also does not properly drag/drop between
  		 * editors. This uses a special data:text/mce-internal URL to pass data when drag/drop between editors.
  		 */
  		function ieInternalDragAndDrop() {
  			editor.on('dragstart', function(e) {
  				setMceInteralContent(e);
  			});
  
  			editor.on('drop', function(e) {
  				if (!isDefaultPrevented(e)) {
  					var internalContent = getMceInternalContent(e);
  					if (internalContent) {
  						e.preventDefault();
  
  						var rng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, editor.getDoc());
  						selection.setRng(rng);
  						insertClipboardContents(internalContent);
  					}
  				}
  			});
  		}
  
  		// All browsers
  		removeBlockQuoteOnBackSpace();
  		emptyEditorWhenDeleting();
  		normalizeSelection();
  
  		// WebKit
  		if (isWebKit) {
  			cleanupStylesWhenDeleting();
  			inputMethodFocus();
  			selectControlElements();
  			setDefaultBlockType();
  			blockFormSubmitInsideEditor();
  			disableBackspaceIntoATable();
  			removeAppleInterchangeBrs();
  			touchClickEvent();
  
  			// iOS
  			if (Env.iOS) {
  				restoreFocusOnKeyDown();
  				bodyHeight();
  				tapLinksAndImages();
  			} else {
  				selectAll();
  			}
  		}
  
  		// IE
  		if (isIE && Env.ie < 11) {
  			removeHrOnBackspace();
  			ensureBodyHasRoleApplication();
  			addNewLinesBeforeBrInPre();
  			removePreSerializedStylesWhenSelectingControls();
  			deleteControlItemOnBackSpace();
  			renderEmptyBlocksFix();
  			keepNoScriptContents();
  			fixCaretSelectionOfDocumentElementOnIe();
  		}
  
  		if (Env.ie >= 11) {
  			bodyHeight();
  			doubleTrailingBrElements();
  			disableBackspaceIntoATable();
  		}
  
  		if (Env.ie) {
  			selectAll();
  			disableAutoUrlDetect();
  			ieInternalDragAndDrop();
  		}
  
  		// Gecko
  		if (isGecko) {
  			removeHrOnBackspace();
  			focusBody();
  			removeStylesWhenDeletingAcrossBlockElements();
  			setGeckoEditingOptions();
  			addBrAfterLastLinks();
  			removeGhostSelection();
  			showBrokenImageIcon();
  			blockCmdArrowNavigation();
  			disableBackspaceIntoATable();
  		}
  	};
  });
  
  // Included from: js/maya/classes/util/Observable.js
  
  /**
   * Observable.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This mixin will add event binding logic to classes.
   *
   * @mixin maya.util.Observable
   */
  define("maya/util/Observable", [
  	"maya/util/EventDispatcher"
  ], function(EventDispatcher) {
  	function getEventDispatcher(obj) {
  		if (!obj._eventDispatcher) {
  			obj._eventDispatcher = new EventDispatcher({
  				scope: obj,
  				toggleEvent: function(name, state) {
  					if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
  						obj.toggleNativeEvent(name, state);
  					}
  				}
  			});
  		}
  
  		return obj._eventDispatcher;
  	}
  
  	return {
  		/**
  		 * Fires the specified event by name.
  		 *
  		 * @method fire
  		 * @param {String} name Name of the event to fire.
  		 * @param {Object?} args Event arguments.
  		 * @param {Boolean?} bubble True/false if the event is to be bubbled.
  		 * @return {Object} Event args instance passed in.
  		 * @example
  		 * instance.fire('event', {...});
  		 */
  		fire: function(name, args, bubble) {
  			var self = this;
  
  			// Prevent all events except the remove event after the instance has been removed
  			if (self.removed && name !== "remove") {
  				return args;
  			}
  
  			args = getEventDispatcher(self).fire(name, args, bubble);
  
  			// Bubble event up to parents
  			if (bubble !== false && self.parent) {
  				var parent = self.parent();
  				while (parent && !args.isPropagationStopped()) {
  					parent.fire(name, args, false);
  					parent = parent.parent();
  				}
  			}
  
  			return args;
  		},
  
  		/**
  		 * Binds an event listener to a specific event by name.
  		 *
  		 * @method on
  		 * @param {String} name Event name or space separated list of events to bind.
  		 * @param {callback} callback Callback to be executed when the event occurs.
  		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  		 * @return {Object} Current class instance.
  		 * @example
  		 * instance.on('event', function(e) {
  		 *     // Callback logic
  		 * });
  		 */
  		on: function(name, callback, prepend) {
  			return getEventDispatcher(this).on(name, callback, prepend);
  		},
  
  		/**
  		 * Unbinds an event listener to a specific event by name.
  		 *
  		 * @method off
  		 * @param {String?} name Name of the event to unbind.
  		 * @param {callback?} callback Callback to unbind.
  		 * @return {Object} Current class instance.
  		 * @example
  		 * // Unbind specific callback
  		 * instance.off('event', handler);
  		 *
  		 * // Unbind all listeners by name
  		 * instance.off('event');
  		 *
  		 * // Unbind all events
  		 * instance.off();
  		 */
  		off: function(name, callback) {
  			return getEventDispatcher(this).off(name, callback);
  		},
  
  		/**
  		 * Bind the event callback and once it fires the callback is removed.
  		 *
  		 * @method once
  		 * @param {String} name Name of the event to bind.
  		 * @param {callback} callback Callback to bind only once.
  		 * @return {Object} Current class instance.
  		 */
  		once: function(name, callback) {
  			return getEventDispatcher(this).once(name, callback);
  		},
  
  		/**
  		 * Returns true/false if the object has a event of the specified name.
  		 *
  		 * @method hasEventListeners
  		 * @param {String} name Name of the event to check for.
  		 * @return {Boolean} true/false if the event exists or not.
  		 */
  		hasEventListeners: function(name) {
  			return getEventDispatcher(this).has(name);
  		}
  	};
  });
  
  // Included from: js/maya/classes/EditorObservable.js
  
  /**
   * EditorObservable.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This mixin contains the event logic for the maya.Editor class.
   *
   * @mixin maya.EditorObservable
   * @extends maya.util.Observable
   */
  define("maya/EditorObservable", [
  	"maya/util/Observable",
  	"maya/dom/DOMUtils",
  	"maya/util/Tools"
  ], function(Observable, DOMUtils, Tools) {
  	var DOM = DOMUtils.DOM, customEventRootDelegates;
  
  	/**
  	 * Returns the event target so for the specified event. Some events fire
  	 * only on document, some fire on documentElement etc. This also handles the
  	 * custom event root setting where it returns that element instead of the body.
  	 *
  	 * @private
  	 * @param {maya.Editor} editor Editor instance to get event target from.
  	 * @param {String} eventName Name of the event for example "click".
  	 * @return {Element/Document} HTML Element or document target to bind on.
  	 */
  	function getEventTarget(editor, eventName) {
  		if (eventName == 'selectionchange') {
  			return editor.getDoc();
  		}
  
  		// Need to bind mousedown/mouseup etc to document not body in iframe mode
  		// Since the user might click on the HTML element not the BODY
  		if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
  			return editor.getDoc().documentElement;
  		}
  
  		// Bind to event root instead of body if it's defined
  		if (editor.settings.event_root) {
  			if (!editor.eventRoot) {
  				editor.eventRoot = DOM.select(editor.settings.event_root)[0];
  			}
  
  			return editor.eventRoot;
  		}
  
  		return editor.getBody();
  	}
  
  	/**
  	 * Binds a event delegate for the specified name this delegate will fire
  	 * the event to the editor dispatcher.
  	 *
  	 * @private
  	 * @param {maya.Editor} editor Editor instance to get event target from.
  	 * @param {String} eventName Name of the event for example "click".
  	 */
  	function bindEventDelegate(editor, eventName) {
  		var eventRootElm = getEventTarget(editor, eventName), delegate;
  
  		if (!editor.delegates) {
  			editor.delegates = {};
  		}
  
  		if (editor.delegates[eventName]) {
  			return;
  		}
  
  		if (editor.settings.event_root) {
  			if (!customEventRootDelegates) {
  				customEventRootDelegates = {};
  				editor.editorManager.on('removeEditor', function() {
  					var name;
  
  					if (!editor.editorManager.activeEditor) {
  						if (customEventRootDelegates) {
  							for (name in customEventRootDelegates) {
  								editor.dom.unbind(getEventTarget(editor, name));
  							}
  
  							customEventRootDelegates = null;
  						}
  					}
  				});
  			}
  
  			if (customEventRootDelegates[eventName]) {
  				return;
  			}
  
  			delegate = function(e) {
  				var target = e.target, editors = editor.editorManager.editors, i = editors.length;
  
  				while (i--) {
  					var body = editors[i].getBody();
  
  					if (body === target || DOM.isChildOf(target, body)) {
  						if (!editors[i].hidden) {
  							editors[i].fire(eventName, e);
  						}
  					}
  				}
  			};
  
  			customEventRootDelegates[eventName] = delegate;
  			DOM.bind(eventRootElm, eventName, delegate);
  		} else {
  			delegate = function(e) {
  				if (!editor.hidden) {
  					editor.fire(eventName, e);
  				}
  			};
  
  			DOM.bind(eventRootElm, eventName, delegate);
  			editor.delegates[eventName] = delegate;
  		}
  	}
  
  	var EditorObservable = {
  		/**
  		 * Bind any pending event delegates. This gets executed after the target body/document is created.
  		 *
  		 * @private
  		 */
  		bindPendingEventDelegates: function() {
  			var self = this;
  
  			Tools.each(self._pendingNativeEvents, function(name) {
  				bindEventDelegate(self, name);
  			});
  		},
  
  		/**
  		 * Toggles a native event on/off this is called by the EventDispatcher when
  		 * the first native event handler is added and when the last native event handler is removed.
  		 *
  		 * @private
  		 */
  		toggleNativeEvent: function(name, state) {
  			var self = this;
  
  			if (self.settings.readonly) {
  				return;
  			}
  
  			// Never bind focus/blur since the FocusManager fakes those
  			if (name == "focus" || name == "blur") {
  				return;
  			}
  
  			if (state) {
  				if (self.initialized) {
  					bindEventDelegate(self, name);
  				} else {
  					if (!self._pendingNativeEvents) {
  						self._pendingNativeEvents = [name];
  					} else {
  						self._pendingNativeEvents.push(name);
  					}
  				}
  			} else if (self.initialized) {
  				self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
  				delete self.delegates[name];
  			}
  		},
  
  		/**
  		 * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
  		 *
  		 * @private
  		 */
  		unbindAllNativeEvents: function() {
  			var self = this, name;
  
  			if (self.delegates) {
  				for (name in self.delegates) {
  					self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
  				}
  
  				delete self.delegates;
  			}
  
  			if (!self.inline) {
  				self.getBody().onload = null;
  				self.dom.unbind(self.getWin());
  				self.dom.unbind(self.getDoc());
  			}
  
  			self.dom.unbind(self.getBody());
  			self.dom.unbind(self.getContainer());
  		}
  	};
  
  	EditorObservable = Tools.extend({}, Observable, EditorObservable);
  
  	return EditorObservable;
  });
  
  // Included from: js/maya/classes/Shortcuts.js
  
  /**
   * Shortcuts.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Contains all logic for handling of keyboard shortcuts.
   *
   * @example
   * editor.shortcuts.add('ctrl+a', function() {});
   * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
   * editor.shortcuts.add('ctrl+alt+a', function() {});
   * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
   */
  define("maya/Shortcuts", [
  	"maya/util/Tools",
  	"maya/Env"
  ], function(Tools, Env) {
  	var each = Tools.each, explode = Tools.explode;
  
  	var keyCodeLookup = {
  		"f9": 120,
  		"f10": 121,
  		"f11": 122
  	};
  
  	var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
  
  	return function(editor) {
  		var self = this, shortcuts = {};
  
  		function createShortcut(pattern, desc, cmdFunc, scope) {
  			var id, key, shortcut;
  
  			shortcut = {
  				func: cmdFunc,
  				scope: scope || editor,
  				desc: editor.translate(desc)
  			};
  
  			// Parse modifiers and keys ctrl+alt+b for example
  			each(explode(pattern, '+'), function(value) {
  				if (value in modifierNames) {
  					shortcut[value] = true;
  				} else {
  					// Allow numeric keycodes like ctrl+219 for ctrl+[
  					if (/^[0-9]{2,}$/.test(value)) {
  						shortcut.keyCode = parseInt(value, 10);
  					} else {
  						shortcut.charCode = value.charCodeAt(0);
  						shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
  					}
  				}
  			});
  
  			// Generate unique id for modifier combination and set default state for unused modifiers
  			id = [shortcut.keyCode];
  			for (key in modifierNames) {
  				if (shortcut[key]) {
  					id.push(key);
  				} else {
  					shortcut[key] = false;
  				}
  			}
  			shortcut.id = id.join(',');
  
  			// Handle special access modifier differently depending on Mac/Win
  			if (shortcut.access) {
  				shortcut.alt = true;
  
  				if (Env.mac) {
  					shortcut.ctrl = true;
  				} else {
  					shortcut.shift = true;
  				}
  			}
  
  			// Handle special meta modifier differently depending on Mac/Win
  			if (shortcut.meta) {
  				if (Env.mac) {
  					shortcut.meta = true;
  				} else {
  					shortcut.ctrl = true;
  					shortcut.meta = false;
  				}
  			}
  
  			return shortcut;
  		}
  
  		editor.on('keyup keypress keydown', function(e) {
  			if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) {
  				each(shortcuts, function(shortcut) {
  					if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
  						return;
  					}
  
  					if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
  						return;
  					}
  
  					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
  						e.preventDefault();
  
  						if (e.type == "keydown") {
  							shortcut.func.call(shortcut.scope);
  						}
  
  						return true;
  					}
  				});
  			}
  		});
  
  		/**
  		 * Adds a keyboard shortcut for some command or function.
  		 *
  		 * @method addShortcut
  		 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  		 * @param {String} desc Text description for the command.
  		 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  		 * @param {Object} sc Optional scope to execute the function in.
  		 * @return {Boolean} true/false state if the shortcut was added or not.
  		 */
  		self.add = function(pattern, desc, cmdFunc, scope) {
  			var cmd;
  
  			cmd = cmdFunc;
  
  			if (typeof cmdFunc === 'string') {
  				cmdFunc = function() {
  					editor.execCommand(cmd, false, null);
  				};
  			} else if (Tools.isArray(cmd)) {
  				cmdFunc = function() {
  					editor.execCommand(cmd[0], cmd[1], cmd[2]);
  				};
  			}
  
  			each(explode(pattern.toLowerCase()), function(pattern) {
  				var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
  				shortcuts[shortcut.id] = shortcut;
  			});
  
  			return true;
  		};
  
  		/**
  		 * Remove a keyboard shortcut by pattern.
  		 *
  		 * @method remove
  		 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  		 * @return {Boolean} true/false state if the shortcut was removed or not.
  		 */
  		self.remove = function(pattern) {
  			var shortcut = createShortcut(pattern);
  
  			if (shortcuts[shortcut.id]) {
  				delete shortcuts[shortcut.id];
  				return true;
  			}
  
  			return false;
  		};
  	};
  });
  
  // Included from: js/maya/classes/Editor.js
  
  /**
   * Editor.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*jshint scripturl:true */
  
  /**
   * Include the base event class documentation.
   *
   * @include ../../../tools/docs/maya.Event.js
   */
  
  /**
   * This class contains the core logic for a maya editor.
   *
   * @class maya.Editor
   * @mixes maya.util.Observable
   * @example
   * // Add a class to all paragraphs in the editor.
   * maya.activeEditor.dom.addClass(maya.activeEditor.dom.select('p'), 'someclass');
   *
   * // Gets the current editors selection as text
   * maya.activeEditor.selection.getContent({format: 'text'});
   *
   * // Creates a new editor instance
   * var ed = new maya.Editor('textareaid', {
   *     some_setting: 1
   * }, maya.EditorManager);
   *
   * // Select each item the user clicks on
   * ed.on('click', function(e) {
   *     ed.selection.select(e.target);
   * });
   *
   * ed.render();
   */
  define("maya/Editor", [
  	"maya/dom/DOMUtils",
  	"maya/dom/DomQuery",
  	"maya/AddOnManager",
  	"maya/NodeChange",
  	"maya/html/Node",
  	"maya/dom/Serializer",
  	"maya/html/Serializer",
  	"maya/dom/Selection",
  	"maya/Formatter",
  	"maya/UndoManager",
  	"maya/EnterKey",
  	"maya/ForceBlocks",
  	"maya/EditorCommands",
  	"maya/util/URI",
  	"maya/dom/ScriptLoader",
  	"maya/dom/EventUtils",
  	"maya/WindowManager",
  	"maya/html/Schema",
  	"maya/html/DomParser",
  	"maya/util/Quirks",
  	"maya/Env",
  	"maya/util/Tools",
  	"maya/EditorObservable",
  	"maya/Shortcuts"
  ], function(
  	DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer,
  	Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
  	URI, ScriptLoader, EventUtils, WindowManager,
  	Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts
  ) {
  	// Shorten these names
  	var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
  	var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
  	var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
  	var Event = EventUtils.Event;
  	var isGecko = Env.gecko, ie = Env.ie;
  
  	/**
  	 * Include documentation for all the events.
  	 *
  	 * @include ../../../tools/docs/maya.Editor.js
  	 */
  
  	/**
  	 * Constructs a editor instance by id.
  	 *
  	 * @constructor
  	 * @method Editor
  	 * @param {String} id Unique id for the editor.
  	 * @param {Object} settings Settings for the editor.
  	 * @param {maya.EditorManager} editorManager EditorManager instance.
  	 * @author Moxiecode
  	 */
  	function Editor(id, settings, editorManager) {
  		var self = this, documentBaseUrl, baseUri;
  
  		documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
  		baseUri = editorManager.baseURI;
  
  		/**
  		 * Name/value collection with editor settings.
  		 *
  		 * @property settings
  		 * @type Object
  		 * @example
  		 * // Get the value of the theme setting
  		 * maya.activeEditor.windowManager.alert("You are using the " + maya.activeEditor.settings.theme + " theme");
  		 */
  		self.settings = settings = extend({
  			id: id,
  			theme: 'modern',
  			delta_width: 0,
  			delta_height: 0,
  			popup_css: '',
  			plugins: '',
  			document_base_url: documentBaseUrl,
  			add_form_submit_trigger: true,
  			submit_patch: true,
  			add_unload_trigger: true,
  			convert_urls: true,
  			relative_urls: true,
  			remove_script_host: true,
  			object_resizing: true,
  			doctype: '<!DOCTYPE html>',
  			visual: true,
  			font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
  
  			// See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
  			font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
  			forced_root_block: 'p',
  			hidden_input: true,
  			padd_empty_editor: true,
  			render_ui: true,
  			indentation: '30px',
  			inline_styles: true,
  			convert_fonts_to_spans: true,
  			indent: 'simple',
  			indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
  				'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  			indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
  				'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  			validate: true,
  			entity_encoding: 'named',
  			url_converter: self.convertURL,
  			url_converter_scope: self,
  			ie7_compat: true
  		}, settings);
  
  		AddOnManager.language = settings.language || 'en';
  		AddOnManager.languageLoad = settings.language_load;
  
  		AddOnManager.baseURL = editorManager.baseURL;
  
  		/**
  		 * Editor instance id, normally the same as the div/textarea that was replaced.
  		 *
  		 * @property id
  		 * @type String
  		 */
  		self.id = settings.id = id;
  
  		/**
  		 * State to force the editor to return false on a isDirty call.
  		 *
  		 * @property isNotDirty
  		 * @type Boolean
  		 * @example
  		 * function ajaxSave() {
  		 *     var ed = maya.get('elm1');
  		 *
  		 *     // Save contents using some XHR call
  		 *     alert(ed.getContent());
  		 *
  		 *     ed.isNotDirty = true; // Force not dirty state
  		 * }
  		 */
  		self.isNotDirty = true;
  
  		/**
  		 * Name/Value object containting plugin instances.
  		 *
  		 * @property plugins
  		 * @type Object
  		 * @example
  		 * // Execute a method inside a plugin directly
  		 * maya.activeEditor.plugins.someplugin.someMethod();
  		 */
  		self.plugins = {};
  
  		/**
  		 * URI object to document configured for the maya instance.
  		 *
  		 * @property documentBaseURI
  		 * @type maya.util.URI
  		 * @example
  		 * // Get relative URL from the location of document_base_url
  		 * maya.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
  		 *
  		 * // Get absolute URL from the location of document_base_url
  		 * maya.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
  		 */
  		self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
  			base_uri: baseUri
  		});
  
  		/**
  		 * URI object to current document that holds the maya editor instance.
  		 *
  		 * @property baseURI
  		 * @type maya.util.URI
  		 * @example
  		 * // Get relative URL from the location of the API
  		 * maya.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
  		 *
  		 * // Get absolute URL from the location of the API
  		 * maya.activeEditor.baseURI.toAbsolute('somefile.htm');
  		 */
  		self.baseURI = baseUri;
  
  		/**
  		 * Array with CSS files to load into the iframe.
  		 *
  		 * @property contentCSS
  		 * @type Array
  		 */
  		self.contentCSS = [];
  
  		/**
  		 * Array of CSS styles to add to head of document when the editor loads.
  		 *
  		 * @property contentStyles
  		 * @type Array
  		 */
  		self.contentStyles = [];
  
  		// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
  		self.shortcuts = new Shortcuts(self);
  		self.loadedCSS = {};
  		self.editorCommands = new EditorCommands(self);
  
  		if (settings.target) {
  			self.targetElm = settings.target;
  		}
  
  		self.suffix = editorManager.suffix;
  		self.editorManager = editorManager;
  		self.inline = settings.inline;
  
  		if (settings.cache_suffix) {
  			Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, '');
  		}
  
  		// Call setup
  		editorManager.fire('SetupEditor', self);
  		self.execCallback('setup', self);
  
  		/**
  		 * Dom query instance with default scope to the editor document and default element is the body of the editor.
  		 *
  		 * @property $
  		 * @type maya.dom.DomQuery
  		 * @example
  		 * maya.activeEditor.$('p').css('color', 'red');
  		 * maya.activeEditor.$().append('<p>new</p>');
  		 */
  		self.$ = DomQuery.overrideDefaults(function() {
  			return {
  				context: self.inline ? self.getBody() : self.getDoc(),
  				element: self.getBody()
  			};
  		});
  	}
  
  	Editor.prototype = {
  		/**
  		 * Renderes the editor/adds it to the page.
  		 *
  		 * @method render
  		 */
  		render: function() {
  			var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
  
  			function readyHandler() {
  				DOM.unbind(window, 'ready', readyHandler);
  				self.render();
  			}
  
  			// Page is not loaded yet, wait for it
  			if (!Event.domLoaded) {
  				DOM.bind(window, 'ready', readyHandler);
  				return;
  			}
  
  			// Element not found, then skip initialization
  			if (!self.getElement()) {
  				return;
  			}
  
  			// No editable support old iOS versions etc
  			if (!Env.contentEditable) {
  				return;
  			}
  
  			// Hide target element early to prevent content flashing
  			if (!settings.inline) {
  				self.orgVisibility = self.getElement().style.visibility;
  				self.getElement().style.visibility = 'hidden';
  			} else {
  				self.inline = true;
  			}
  
  			var form = self.getElement().form || DOM.getParent(id, 'form');
  			if (form) {
  				self.formElement = form;
  
  				// Add hidden input for non input elements inside form elements
  				if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
  					DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
  					self.hasHiddenInput = true;
  				}
  
  				// Pass submit/reset from form to editor instance
  				self.formEventDelegate = function(e) {
  					self.fire(e.type, e);
  				};
  
  				DOM.bind(form, 'submit reset', self.formEventDelegate);
  
  				// Reset contents in editor when the form is reset
  				self.on('reset', function() {
  					self.setContent(self.startContent, {format: 'raw'});
  				});
  
  				// Check page uses id="submit" or name="submit" for it's submit button
  				if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
  					form._mceOldSubmit = form.submit;
  					form.submit = function() {
  						self.editorManager.triggerSave();
  						self.isNotDirty = true;
  
  						return form._mceOldSubmit(form);
  					};
  				}
  			}
  
  			/**
  			 * Window manager reference, use this to open new windows and dialogs.
  			 *
  			 * @property windowManager
  			 * @type maya.WindowManager
  			 * @example
  			 * // Shows an alert message
  			 * maya.activeEditor.windowManager.alert('Hello world!');
  			 *
  			 * // Opens a new dialog with the file.htm file and the size 320x240
  			 * // It also adds a custom parameter this can be retrieved by using mayaPopup.getWindowArg inside the dialog.
  			 * maya.activeEditor.windowManager.open({
  			 *    url: 'file.htm',
  			 *    width: 320,
  			 *    height: 240
  			 * }, {
  			 *    custom_param: 1
  			 * });
  			 */
  			self.windowManager = new WindowManager(self);
  
  			if (settings.encoding == 'xml') {
  				self.on('GetContent', function(e) {
  					if (e.save) {
  						e.content = DOM.encode(e.content);
  					}
  				});
  			}
  
  			if (settings.add_form_submit_trigger) {
  				self.on('submit', function() {
  					if (self.initialized) {
  						self.save();
  					}
  				});
  			}
  
  			if (settings.add_unload_trigger) {
  				self._beforeUnload = function() {
  					if (self.initialized && !self.destroyed && !self.isHidden()) {
  						self.save({format: 'raw', no_events: true, set_dirty: false});
  					}
  				};
  
  				self.editorManager.on('BeforeUnload', self._beforeUnload);
  			}
  
  			// Load scripts
  			function loadScripts() {
  				var scriptLoader = ScriptLoader.ScriptLoader;
  
  				if (settings.language && settings.language != 'en' && !settings.language_url) {
  					settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
  				}
  
  				if (settings.language_url) {
  					scriptLoader.add(settings.language_url);
  				}
  
  				if (settings.theme && typeof settings.theme != "function" &&
  					settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
  					var themeUrl = settings.theme_url;
  
  					if (themeUrl) {
  						themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
  					} else {
  						themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
  					}
  
  					ThemeManager.load(settings.theme, themeUrl);
  				}
  
  				if (Tools.isArray(settings.plugins)) {
  					settings.plugins = settings.plugins.join(' ');
  				}
  
  				each(settings.external_plugins, function(url, name) {
  					PluginManager.load(name, url);
  					settings.plugins += ' ' + name;
  				});
  
  				each(settings.plugins.split(/[ ,]/), function(plugin) {
  					plugin = trim(plugin);
  
  					if (plugin && !PluginManager.urls[plugin]) {
  						if (plugin.charAt(0) == '-') {
  							plugin = plugin.substr(1, plugin.length);
  
  							var dependencies = PluginManager.dependencies(plugin);
  
  							each(dependencies, function(dep) {
  								var defaultSettings = {
  									prefix: 'plugins/',
  									resource: dep,
  									suffix: '/plugin' + suffix + '.js'
  								};
  
  								dep = PluginManager.createUrl(defaultSettings, dep);
  								PluginManager.load(dep.resource, dep);
  							});
  						} else {
  							PluginManager.load(plugin, {
  								prefix: 'plugins/',
  								resource: plugin,
  								suffix: '/plugin' + suffix + '.js'
  							});
  						}
  					}
  				});
  
  				scriptLoader.loadQueue(function() {
  					if (!self.removed) {
  						self.init();
  					}
  				});
  			}
  
  			loadScripts();
  		},
  
  		/**
  		 * Initializes the editor this will be called automatically when
  		 * all plugins/themes and language packs are loaded by the rendered method.
  		 * This method will setup the iframe and create the theme and plugin instances.
  		 *
  		 * @method init
  		 */
  		init: function() {
  			var self = this, settings = self.settings, elm = self.getElement();
  			var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
  
  			this.editorManager.i18n.setCode(settings.language);
  			self.rtl = this.editorManager.i18n.rtl;
  			self.editorManager.add(self);
  
  			settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
  
  			/**
  			 * Reference to the theme instance that was used to generate the UI.
  			 *
  			 * @property theme
  			 * @type maya.Theme
  			 * @example
  			 * // Executes a method on the theme directly
  			 * maya.activeEditor.theme.someMethod();
  			 */
  			if (settings.theme) {
  				if (typeof settings.theme != "function") {
  					settings.theme = settings.theme.replace(/-/, '');
  					Theme = ThemeManager.get(settings.theme);
  					self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
  
  					if (self.theme.init) {
  						self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$);
  					}
  				} else {
  					self.theme = settings.theme;
  				}
  			}
  
  			function initPlugin(plugin) {
  				var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
  
  				pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
  				plugin = trim(plugin);
  				if (Plugin && inArray(initializedPlugins, plugin) === -1) {
  					each(PluginManager.dependencies(plugin), function(dep) {
  						initPlugin(dep);
  					});
  
  					pluginInstance = new Plugin(self, pluginUrl, self.$);
  
  					self.plugins[plugin] = pluginInstance;
  
  					if (pluginInstance.init) {
  						pluginInstance.init(self, pluginUrl);
  						initializedPlugins.push(plugin);
  					}
  				}
  			}
  
  			// Create all plugins
  			each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
  
  			// Measure box
  			if (settings.render_ui && self.theme) {
  				self.orgDisplay = elm.style.display;
  
  				if (typeof settings.theme != "function") {
  					w = settings.width || elm.style.width || elm.offsetWidth;
  					h = settings.height || elm.style.height || elm.offsetHeight;
  					minHeight = settings.min_height || 100;
  					re = /^[0-9\.]+(|px)$/i;
  
  					if (re.test('' + w)) {
  						w = Math.max(parseInt(w, 10), 100);
  					}
  
  					if (re.test('' + h)) {
  						h = Math.max(parseInt(h, 10), minHeight);
  					}
  
  					// Render UI
  					o = self.theme.renderUI({
  						targetNode: elm,
  						width: w,
  						height: h,
  						deltaWidth: settings.delta_width,
  						deltaHeight: settings.delta_height
  					});
  
  					// Resize editor
  					if (!settings.content_editable) {
  						h = (o.iframeHeight || h) + (typeof h == 'number' ? (o.deltaHeight || 0) : '');
  						if (h < minHeight) {
  							h = minHeight;
  						}
  					}
  				} else {
  					o = settings.theme(self, elm);
  
  					// Convert element type to id:s
  					if (o.editorContainer.nodeType) {
  						o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
  					}
  
  					// Convert element type to id:s
  					if (o.iframeContainer.nodeType) {
  						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
  					}
  
  					// Use specified iframe height or the targets offsetHeight
  					h = o.iframeHeight || elm.offsetHeight;
  				}
  
  				self.editorContainer = o.editorContainer;
  			}
  
  			// Load specified content CSS last
  			if (settings.content_css) {
  				each(explode(settings.content_css), function(u) {
  					self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
  				});
  			}
  
  			// Load specified content CSS last
  			if (settings.content_style) {
  				self.contentStyles.push(settings.content_style);
  			}
  
  			// Content editable mode ends here
  			if (settings.content_editable) {
  				elm = n = o = null; // Fix IE leak
  				return self.initContentBody();
  			}
  
  			self.iframeHTML = settings.doctype + '<html><head>';
  
  			// We only need to override paths if we have to
  			// IE has a bug where it remove site absolute urls to relative ones if this is specified
  			if (settings.document_base_url != self.documentBaseUrl) {
  				self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
  			}
  
  			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
  			if (!Env.caretAfter && settings.ie7_compat) {
  				self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
  			}
  
  			self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  
  			// Load the CSS by injecting them into the HTML this will reduce "flicker"
  			for (i = 0; i < self.contentCSS.length; i++) {
  				var cssUrl = self.contentCSS[i];
  				self.iframeHTML += (
  					'<link type="text/css" ' +
  						'rel="stylesheet" ' +
  						'href="' + Tools._addCacheSuffix(cssUrl) + '" />'
  				);
  				self.loadedCSS[cssUrl] = true;
  			}
  
  			bodyId = settings.body_id || 'maya';
  			if (bodyId.indexOf('=') != -1) {
  				bodyId = self.getParam('body_id', '', 'hash');
  				bodyId = bodyId[self.id] || bodyId;
  			}
  
  			bodyClass = settings.body_class || '';
  			if (bodyClass.indexOf('=') != -1) {
  				bodyClass = self.getParam('body_class', '', 'hash');
  				bodyClass = bodyClass[self.id] || '';
  			}
  
  			if (settings.content_security_policy) {
  				self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />';
  			}
  
  			self.iframeHTML += '</head><body id="' + bodyId +
  				'" class="mce-content-body ' + bodyClass +
  				'" data-id="' + self.id + '"><br></body></html>';
  
  			/*eslint no-script-url:0 */
  			var domainRelaxUrl = 'javascript:(function(){' +
  				'document.open();document.domain="' + document.domain + '";' +
  				'var ed = window.parent.maya.get("' + self.id + '");document.write(ed.iframeHTML);' +
  				'document.close();ed.initContentBody(true);})()';
  
  			// Domain relaxing is required since the user has messed around with document.domain
  			if (document.domain != location.hostname) {
  				url = domainRelaxUrl;
  			}
  
  			// Create iframe
  			// TODO: ACC add the appropriate description on this.
  			var ifr = DOM.create('iframe', {
  				id: self.id + "_ifr",
  				//src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
  				frameBorder: '0',
  				allowTransparency: "true",
  				title: self.editorManager.translate(
  						"Rich Text Area. Press ALT-F9 for menu. " +
  						"Press ALT-F10 for toolbar. Press ALT-0 for help"
  				),
  				style: {
  					width: '100%',
  					height: h,
  					display: 'block' // Important for Gecko to render the iframe correctly
  				}
  			});
  
  			ifr.onload = function() {
  				ifr.onload = null;
  				self.fire("load");
  			};
  
  			DOM.setAttrib(ifr, "src", url || 'javascript:""');
  
  			self.contentAreaContainer = o.iframeContainer;
  			self.iframeElement = ifr;
  
  			n = DOM.add(o.iframeContainer, ifr);
  
  			// Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
  			// Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
  			if (ie) {
  				try {
  					self.getDoc();
  				} catch (e) {
  					n.src = url = domainRelaxUrl;
  				}
  			}
  
  			if (o.editorContainer) {
  				DOM.get(o.editorContainer).style.display = self.orgDisplay;
  				self.hidden = DOM.isHidden(o.editorContainer);
  			}
  
  			self.getElement().style.display = 'none';
  			DOM.setAttrib(self.id, 'aria-hidden', true);
  
  			if (!url) {
  				self.initContentBody();
  			}
  
  			elm = n = o = null; // Cleanup
  		},
  
  		/**
  		 * This method get called by the init method ones the iframe is loaded.
  		 * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
  		 *
  		 * @method initContentBody
  		 * @private
  		 */
  		initContentBody: function(skipWrite) {
  			var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText;
  
  			// Restore visibility on target element
  			if (!settings.inline) {
  				self.getElement().style.visibility = self.orgVisibility;
  			}
  
  			// Setup iframe body
  			if (!skipWrite && !settings.content_editable) {
  				doc.open();
  				doc.write(self.iframeHTML);
  				doc.close();
  			}
  
  			if (settings.content_editable) {
  				self.on('remove', function() {
  					var bodyEl = this.getBody();
  
  					DOM.removeClass(bodyEl, 'mce-content-body');
  					DOM.removeClass(bodyEl, 'mce-edit-focus');
  					DOM.setAttrib(bodyEl, 'contentEditable', null);
  				});
  
  				DOM.addClass(targetElm, 'mce-content-body');
  				self.contentDocument = doc = settings.content_document || document;
  				self.contentWindow = settings.content_window || window;
  				self.bodyElement = targetElm;
  
  				// Prevent leak in IE
  				settings.content_document = settings.content_window = null;
  
  				// TODO: Fix this
  				settings.root_name = targetElm.nodeName.toLowerCase();
  			}
  
  			// It will not steal focus while setting contentEditable
  			body = self.getBody();
  			body.disabled = true;
  
  			if (!settings.readonly) {
  				if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
  					body.style.position = 'relative';
  				}
  
  				body.contentEditable = self.getParam('content_editable_state', true);
  			}
  
  			body.disabled = false;
  
  			/**
  			 * Schema instance, enables you to validate elements and it's children.
  			 *
  			 * @property schema
  			 * @type maya.html.Schema
  			 */
  			self.schema = new Schema(settings);
  
  			/**
  			 * DOM instance for the editor.
  			 *
  			 * @property dom
  			 * @type maya.dom.DOMUtils
  			 * @example
  			 * // Adds a class to all paragraphs within the editor
  			 * maya.activeEditor.dom.addClass(maya.activeEditor.dom.select('p'), 'someclass');
  			 */
  			self.dom = new DOMUtils(doc, {
  				keep_values: true,
  				url_converter: self.convertURL,
  				url_converter_scope: self,
  				hex_colors: settings.force_hex_style_colors,
  				class_filter: settings.class_filter,
  				update_styles: true,
  				root_element: self.inline ? self.getBody() : null,
  				collect: settings.content_editable,
  				schema: self.schema,
  				onSetAttrib: function(e) {
  					self.fire('SetAttrib', e);
  				}
  			});
  
  			/**
  			 * HTML parser will be used when contents is inserted into the editor.
  			 *
  			 * @property parser
  			 * @type maya.html.DomParser
  			 */
  			self.parser = new DomParser(settings, self.schema);
  
  			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
  			self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
  				var i = nodes.length, node, dom = self.dom, value, internalName;
  
  				while (i--) {
  					node = nodes[i];
  					value = node.attr(name);
  					internalName = 'data-mce-' + name;
  
  					// Add internal attribute if we need to we don't on a refresh of the document
  					if (!node.attributes.map[internalName]) {
  						if (name === "style") {
  							value = dom.serializeStyle(dom.parseStyle(value), node.name);
  
  							if (!value.length) {
  								value = null;
  							}
  
  							node.attr(internalName, value);
  							node.attr(name, value);
  						} else if (name === "tabindex") {
  							node.attr(internalName, value);
  							node.attr(name, null);
  						} else {
  							node.attr(internalName, self.convertURL(value, name, node.name));
  						}
  					}
  				}
  			});
  
  			// Keep scripts from executing
  			self.parser.addNodeFilter('script', function(nodes) {
  				var i = nodes.length, node;
  
  				while (i--) {
  					node = nodes[i];
  					node.attr('type', 'mce-' + (node.attr('type') || 'no/type'));
  				}
  			});
  
  			self.parser.addNodeFilter('#cdata', function(nodes) {
  				var i = nodes.length, node;
  
  				while (i--) {
  					node = nodes[i];
  					node.type = 8;
  					node.name = '#comment';
  					node.value = '[CDATA[' + node.value + ']]';
  				}
  			});
  
  			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
  				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
  
  				while (i--) {
  					node = nodes[i];
  
  					if (node.isEmpty(nonEmptyElements)) {
  						node.append(new Node('br', 1)).shortEnded = true;
  					}
  				}
  			});
  
  			/**
  			 * DOM serializer for the editor. Will be used when contents is extracted from the editor.
  			 *
  			 * @property serializer
  			 * @type maya.dom.Serializer
  			 * @example
  			 * // Serializes the first paragraph in the editor into a string
  			 * maya.activeEditor.serializer.serialize(maya.activeEditor.dom.select('p')[0]);
  			 */
  			self.serializer = new DomSerializer(settings, self);
  
  			/**
  			 * Selection instance for the editor.
  			 *
  			 * @property selection
  			 * @type maya.dom.Selection
  			 * @example
  			 * // Sets some contents to the current selection in the editor
  			 * maya.activeEditor.selection.setContent('Some contents');
  			 *
  			 * // Gets the current selection
  			 * alert(maya.activeEditor.selection.getContent());
  			 *
  			 * // Selects the first paragraph found
  			 * maya.activeEditor.selection.select(maya.activeEditor.dom.select('p')[0]);
  			 */
  			self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
  
  			/**
  			 * Formatter instance.
  			 *
  			 * @property formatter
  			 * @type maya.Formatter
  			 */
  			self.formatter = new Formatter(self);
  
  			/**
  			 * Undo manager instance, responsible for handling undo levels.
  			 *
  			 * @property undoManager
  			 * @type maya.UndoManager
  			 * @example
  			 * // Undoes the last modification to the editor
  			 * maya.activeEditor.undoManager.undo();
  			 */
  			self.undoManager = new UndoManager(self);
  
  			self.forceBlocks = new ForceBlocks(self);
  			self.enterKey = new EnterKey(self);
  			self._nodeChangeDispatcher = new NodeChange(self);
  
  			self.fire('PreInit');
  
  			if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
  				doc.body.spellcheck = false; // Gecko
  				DOM.setAttrib(body, "spellcheck", "false");
  			}
  
  			self.fire('PostRender');
  
  			self.quirks = new Quirks(self);
  
  			if (settings.directionality) {
  				body.dir = settings.directionality;
  			}
  
  			if (settings.nowrap) {
  				body.style.whiteSpace = "nowrap";
  			}
  
  			if (settings.protect) {
  				self.on('BeforeSetContent', function(e) {
  					each(settings.protect, function(pattern) {
  						e.content = e.content.replace(pattern, function(str) {
  							return '<!--mce:protected ' + escape(str) + '-->';
  						});
  					});
  				});
  			}
  
  			self.on('SetContent', function() {
  				self.addVisual(self.getBody());
  			});
  
  			// Remove empty contents
  			if (settings.padd_empty_editor) {
  				self.on('PostProcess', function(e) {
  					e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
  				});
  			}
  
  			self.load({initial: true, format: 'html'});
  			self.startContent = self.getContent({format: 'raw'});
  
  			/**
  			 * Is set to true after the editor instance has been initialized
  			 *
  			 * @property initialized
  			 * @type Boolean
  			 * @example
  			 * function isEditorInitialized(editor) {
  			 *     return editor && editor.initialized;
  			 * }
  			 */
  			self.initialized = true;
  			self.bindPendingEventDelegates();
  
  			self.fire('init');
  			self.focus(true);
  			self.nodeChanged({initial: true});
  			self.execCallback('init_instance_callback', self);
  
  			// Add editor specific CSS styles
  			if (self.contentStyles.length > 0) {
  				contentCssText = '';
  
  				each(self.contentStyles, function(style) {
  					contentCssText += style + "\r\n";
  				});
  
  				self.dom.addStyle(contentCssText);
  			}
  
  			// Load specified content CSS last
  			each(self.contentCSS, function(cssUrl) {
  				if (!self.loadedCSS[cssUrl]) {
  					self.dom.loadCSS(cssUrl);
  					self.loadedCSS[cssUrl] = true;
  				}
  			});
  
  			// Handle auto focus
  			if (settings.auto_focus) {
  				setTimeout(function() {
  					var editor;
  
  					if (settings.auto_focus === true) {
  						editor = self;
  					} else {
  						editor = self.editorManager.get(settings.auto_focus);
  					}
  
  					editor.focus();
  				}, 100);
  			}
  
  			// Clean up references for IE
  			targetElm = doc = body = null;
  		},
  
  		/**
  		 * Focuses/activates the editor. This will set this editor as the activeEditor in the maya collection
  		 * it will also place DOM focus inside the editor.
  		 *
  		 * @method focus
  		 * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
  		 */
  		focus: function(skipFocus) {
  			var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
  			var controlElm, doc = self.getDoc(), body;
  
  			if (!skipFocus) {
  				// Get selected control element
  				rng = selection.getRng();
  				if (rng.item) {
  					controlElm = rng.item(0);
  				}
  
  				self._refreshContentEditable();
  
  				// Focus the window iframe
  				if (!contentEditable) {
  					// WebKit needs this call to fire focusin event properly see #5948
  					// But Opera pre Blink engine will produce an empty selection so skip Opera
  					if (!Env.opera) {
  						self.getBody().focus();
  					}
  
  					self.getWin().focus();
  				}
  
  				// Focus the body as well since it's contentEditable
  				if (isGecko || contentEditable) {
  					body = self.getBody();
  
  					// Check for setActive since it doesn't scroll to the element
  					if (body.setActive) {
  						// IE 11 sometimes throws "Invalid function" then fallback to focus
  						try {
  							body.setActive();
  						} catch (ex) {
  							body.focus();
  						}
  					} else {
  						body.focus();
  					}
  
  					if (contentEditable) {
  						selection.normalize();
  					}
  				}
  
  				// Restore selected control element
  				// This is needed when for example an image is selected within a
  				// layer a call to focus will then remove the control selection
  				if (controlElm && controlElm.ownerDocument == doc) {
  					rng = doc.body.createControlRange();
  					rng.addElement(controlElm);
  					rng.select();
  				}
  			}
  
  			self.editorManager.setActive(self);
  		},
  
  		/**
  		 * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
  		 * There new event model is a better way to add callback so this method might be removed in the future.
  		 *
  		 * @method execCallback
  		 * @param {String} name Name of the callback to execute.
  		 * @return {Object} Return value passed from callback function.
  		 */
  		execCallback: function(name) {
  			var self = this, callback = self.settings[name], scope;
  
  			if (!callback) {
  				return;
  			}
  
  			// Look through lookup
  			if (self.callbackLookup && (scope = self.callbackLookup[name])) {
  				callback = scope.func;
  				scope = scope.scope;
  			}
  
  			if (typeof callback === 'string') {
  				scope = callback.replace(/\.\w+$/, '');
  				scope = scope ? resolve(scope) : 0;
  				callback = resolve(callback);
  				self.callbackLookup = self.callbackLookup || {};
  				self.callbackLookup[name] = {func: callback, scope: scope};
  			}
  
  			return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
  		},
  
  		/**
  		 * Translates the specified string by replacing variables with language pack items it will also check if there is
  		 * a key mathcin the input.
  		 *
  		 * @method translate
  		 * @param {String} text String to translate by the language pack data.
  		 * @return {String} Translated string.
  		 */
  		translate: function(text) {
  			var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
  
  			if (!text) {
  				return '';
  			}
  
  			return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
  				return i18n.data[lang + '.' + b] || '{#' + b + '}';
  			});
  		},
  
  		/**
  		 * Returns a language pack item by name/key.
  		 *
  		 * @method getLang
  		 * @param {String} name Name/key to get from the language pack.
  		 * @param {String} defaultVal Optional default value to retrive.
  		 */
  		getLang: function(name, defaultVal) {
  			return (
  				this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
  				(defaultVal !== undefined ? defaultVal : '{#' + name + '}')
  			);
  		},
  
  		/**
  		 * Returns a configuration parameter by name.
  		 *
  		 * @method getParam
  		 * @param {String} name Configruation parameter to retrive.
  		 * @param {String} defaultVal Optional default value to return.
  		 * @param {String} type Optional type parameter.
  		 * @return {String} Configuration parameter value or default value.
  		 * @example
  		 * // Returns a specific config value from the currently active editor
  		 * var someval = maya.activeEditor.getParam('myvalue');
  		 *
  		 * // Returns a specific config value from a specific editor instance by id
  		 * var someval2 = maya.get('my_editor').getParam('myvalue');
  		 */
  		getParam: function(name, defaultVal, type) {
  			var value = name in this.settings ? this.settings[name] : defaultVal, output;
  
  			if (type === 'hash') {
  				output = {};
  
  				if (typeof value === 'string') {
  					each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
  						value = value.split('=');
  
  						if (value.length > 1) {
  							output[trim(value[0])] = trim(value[1]);
  						} else {
  							output[trim(value[0])] = trim(value);
  						}
  					});
  				} else {
  					output = value;
  				}
  
  				return output;
  			}
  
  			return value;
  		},
  
  		/**
  		 * Distpaches out a onNodeChange event to all observers. This method should be called when you
  		 * need to update the UI states or element path etc.
  		 *
  		 * @method nodeChanged
  		 * @param {Object} args Optional args to pass to NodeChange event handlers.
  		 */
  		nodeChanged: function(args) {
  			this._nodeChangeDispatcher.nodeChanged(args);
  		},
  
  		/**
  		 * Adds a button that later gets created by the theme in the editors toolbars.
  		 *
  		 * @method addButton
  		 * @param {String} name Button name to add.
  		 * @param {Object} settings Settings object with title, cmd etc.
  		 * @example
  		 * // Adds a custom button to the editor that inserts contents when clicked
  		 * maya.init({
  		 *    ...
  		 *
  		 *    toolbar: 'example'
  		 *
  		 *    setup: function(ed) {
  		 *       ed.addButton('example', {
  		 *          title: 'My title',
  		 *          image: '../js/maya/plugins/example/img/example.gif',
  		 *          onclick: function() {
  		 *             ed.insertContent('Hello world!!');
  		 *          }
  		 *       });
  		 *    }
  		 * });
  		 */
  		addButton: function(name, settings) {
  			var self = this;
  
  			if (settings.cmd) {
  				settings.onclick = function() {
  					self.execCommand(settings.cmd);
  				};
  			}
  
  			if (!settings.text && !settings.icon) {
  				settings.icon = name;
  			}
  
  			self.buttons = self.buttons || {};
  			settings.tooltip = settings.tooltip || settings.title;
  			self.buttons[name] = settings;
  		},
  
  		/**
  		 * Adds a menu item to be used in the menus of the theme. There might be multiple instances
  		 * of this menu item for example it might be used in the main menus of the theme but also in
  		 * the context menu so make sure that it's self contained and supports multiple instances.
  		 *
  		 * @method addMenuItem
  		 * @param {String} name Menu item name to add.
  		 * @param {Object} settings Settings object with title, cmd etc.
  		 * @example
  		 * // Adds a custom menu item to the editor that inserts contents when clicked
  		 * // The context option allows you to add the menu item to an existing default menu
  		 * maya.init({
  		 *    ...
  		 *
  		 *    setup: function(ed) {
  		 *       ed.addMenuItem('example', {
  		 *          text: 'My menu item',
  		 *          context: 'tools',
  		 *          onclick: function() {
  		 *             ed.insertContent('Hello world!!');
  		 *          }
  		 *       });
  		 *    }
  		 * });
  		 */
  		addMenuItem: function(name, settings) {
  			var self = this;
  
  			if (settings.cmd) {
  				settings.onclick = function() {
  					self.execCommand(settings.cmd);
  				};
  			}
  
  			self.menuItems = self.menuItems || {};
  			self.menuItems[name] = settings;
  		},
  
  		/**
  		 * Adds a custom command to the editor, you can also override existing commands with this method.
  		 * The command that you add can be executed with execCommand.
  		 *
  		 * @method addCommand
  		 * @param {String} name Command name to add/override.
  		 * @param {addCommandCallback} callback Function to execute when the command occurs.
  		 * @param {Object} scope Optional scope to execute the function in.
  		 * @example
  		 * // Adds a custom command that later can be executed using execCommand
  		 * maya.init({
  		 *    ...
  		 *
  		 *    setup: function(ed) {
  		 *       // Register example command
  		 *       ed.addCommand('mycommand', function(ui, v) {
  		 *          ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
  		 *       });
  		 *    }
  		 * });
  		 */
  		addCommand: function(name, callback, scope) {
  			/**
  			 * Callback function that gets called when a command is executed.
  			 *
  			 * @callback addCommandCallback
  			 * @param {Boolean} ui Display UI state true/false.
  			 * @param {Object} value Optional value for command.
  			 * @return {Boolean} True/false state if the command was handled or not.
  			 */
  			this.editorCommands.addCommand(name, callback, scope);
  		},
  
  		/**
  		 * Adds a custom query state command to the editor, you can also override existing commands with this method.
  		 * The command that you add can be executed with queryCommandState function.
  		 *
  		 * @method addQueryStateHandler
  		 * @param {String} name Command name to add/override.
  		 * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
  		 * @param {Object} scope Optional scope to execute the function in.
  		 */
  		addQueryStateHandler: function(name, callback, scope) {
  			/**
  			 * Callback function that gets called when a queryCommandState is executed.
  			 *
  			 * @callback addQueryStateHandlerCallback
  			 * @return {Boolean} True/false state if the command is enabled or not like is it bold.
  			 */
  			this.editorCommands.addQueryStateHandler(name, callback, scope);
  		},
  
  		/**
  		 * Adds a custom query value command to the editor, you can also override existing commands with this method.
  		 * The command that you add can be executed with queryCommandValue function.
  		 *
  		 * @method addQueryValueHandler
  		 * @param {String} name Command name to add/override.
  		 * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
  		 * @param {Object} scope Optional scope to execute the function in.
  		 */
  		addQueryValueHandler: function(name, callback, scope) {
  			/**
  			 * Callback function that gets called when a queryCommandValue is executed.
  			 *
  			 * @callback addQueryValueHandlerCallback
  			 * @return {Object} Value of the command or undefined.
  			 */
  			this.editorCommands.addQueryValueHandler(name, callback, scope);
  		},
  
  		/**
  		 * Adds a keyboard shortcut for some command or function.
  		 *
  		 * @method addShortcut
  		 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  		 * @param {String} desc Text description for the command.
  		 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  		 * @param {Object} sc Optional scope to execute the function in.
  		 * @return {Boolean} true/false state if the shortcut was added or not.
  		 */
  		addShortcut: function(pattern, desc, cmdFunc, scope) {
  			this.shortcuts.add(pattern, desc, cmdFunc, scope);
  		},
  
  		/**
  		 * Executes a command on the current instance. These commands can be maya internal commands prefixed with "mce" or
  		 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
  		 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
  		 * return true it will handle the command as a internal browser command.
  		 *
  		 * @method execCommand
  		 * @param {String} cmd Command name to execute, for example mceLink or Bold.
  		 * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
  		 * @param {mixed} value Optional command value, this can be anything.
  		 * @param {Object} args Optional arguments object.
  		 */
  		execCommand: function(cmd, ui, value, args) {
  			return this.editorCommands.execCommand(cmd, ui, value, args);
  		},
  
  		/**
  		 * Returns a command specific state, for example if bold is enabled or not.
  		 *
  		 * @method queryCommandState
  		 * @param {string} cmd Command to query state from.
  		 * @return {Boolean} Command specific state, for example if bold is enabled or not.
  		 */
  		queryCommandState: function(cmd) {
  			return this.editorCommands.queryCommandState(cmd);
  		},
  
  		/**
  		 * Returns a command specific value, for example the current font size.
  		 *
  		 * @method queryCommandValue
  		 * @param {string} cmd Command to query value from.
  		 * @return {Object} Command specific value, for example the current font size.
  		 */
  		queryCommandValue: function(cmd) {
  			return this.editorCommands.queryCommandValue(cmd);
  		},
  
  		/**
  		 * Returns true/false if the command is supported or not.
  		 *
  		 * @method queryCommandSupported
  		 * @param {String} cmd Command that we check support for.
  		 * @return {Boolean} true/false if the command is supported or not.
  		 */
  		queryCommandSupported: function(cmd) {
  			return this.editorCommands.queryCommandSupported(cmd);
  		},
  
  		/**
  		 * Shows the editor and hides any textarea/div that the editor is supposed to replace.
  		 *
  		 * @method show
  		 */
  		show: function() {
  			var self = this;
  
  			if (self.hidden) {
  				self.hidden = false;
  
  				if (self.inline) {
  					self.getBody().contentEditable = true;
  				} else {
  					DOM.show(self.getContainer());
  					DOM.hide(self.id);
  				}
  
  				self.load();
  				self.fire('show');
  			}
  		},
  
  		/**
  		 * Hides the editor and shows any textarea/div that the editor is supposed to replace.
  		 *
  		 * @method hide
  		 */
  		hide: function() {
  			var self = this, doc = self.getDoc();
  
  			if (!self.hidden) {
  				// Fixed bug where IE has a blinking cursor left from the editor
  				if (ie && doc && !self.inline) {
  					doc.execCommand('SelectAll');
  				}
  
  				// We must save before we hide so Safari doesn't crash
  				self.save();
  
  				if (self.inline) {
  					self.getBody().contentEditable = false;
  
  					// Make sure the editor gets blurred
  					if (self == self.editorManager.focusedEditor) {
  						self.editorManager.focusedEditor = null;
  					}
  				} else {
  					DOM.hide(self.getContainer());
  					DOM.setStyle(self.id, 'display', self.orgDisplay);
  				}
  
  				self.hidden = true;
  				self.fire('hide');
  			}
  		},
  
  		/**
  		 * Returns true/false if the editor is hidden or not.
  		 *
  		 * @method isHidden
  		 * @return {Boolean} True/false if the editor is hidden or not.
  		 */
  		isHidden: function() {
  			return !!this.hidden;
  		},
  
  		/**
  		 * Sets the progress state, this will display a throbber/progess for the editor.
  		 * This is ideal for asycronous operations like an AJAX save call.
  		 *
  		 * @method setProgressState
  		 * @param {Boolean} state Boolean state if the progress should be shown or hidden.
  		 * @param {Number} time Optional time to wait before the progress gets shown.
  		 * @return {Boolean} Same as the input state.
  		 * @example
  		 * // Show progress for the active editor
  		 * maya.activeEditor.setProgressState(true);
  		 *
  		 * // Hide progress for the active editor
  		 * maya.activeEditor.setProgressState(false);
  		 *
  		 * // Show progress after 3 seconds
  		 * maya.activeEditor.setProgressState(true, 3000);
  		 */
  		setProgressState: function(state, time) {
  			this.fire('ProgressState', {state: state, time: time});
  		},
  
  		/**
  		 * Loads contents from the textarea or div element that got converted into an editor instance.
  		 * This method will move the contents from that textarea or div into the editor by using setContent
  		 * so all events etc that method has will get dispatched as well.
  		 *
  		 * @method load
  		 * @param {Object} args Optional content object, this gets passed around through the whole load process.
  		 * @return {String} HTML string that got set into the editor.
  		 */
  		load: function(args) {
  			var self = this, elm = self.getElement(), html;
  
  			if (elm) {
  				args = args || {};
  				args.load = true;
  
  				html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
  				args.element = elm;
  
  				if (!args.no_events) {
  					self.fire('LoadContent', args);
  				}
  
  				args.element = elm = null;
  
  				return html;
  			}
  		},
  
  		/**
  		 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
  		 * This method will move the HTML contents from the editor into that textarea or div by getContent
  		 * so all events etc that method has will get dispatched as well.
  		 *
  		 * @method save
  		 * @param {Object} args Optional content object, this gets passed around through the whole save process.
  		 * @return {String} HTML string that got set into the textarea/div.
  		 */
  		save: function(args) {
  			var self = this, elm = self.getElement(), html, form;
  
  			if (!elm || !self.initialized) {
  				return;
  			}
  
  			args = args || {};
  			args.save = true;
  
  			args.element = elm;
  			html = args.content = self.getContent(args);
  
  			if (!args.no_events) {
  				self.fire('SaveContent', args);
  			}
  
  			html = args.content;
  
  			if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
  				// Update DIV element when not in inline mode
  				if (!self.inline) {
  					elm.innerHTML = html;
  				}
  
  				// Update hidden form element
  				if ((form = DOM.getParent(self.id, 'form'))) {
  					each(form.elements, function(elm) {
  						if (elm.name == self.id) {
  							elm.value = html;
  							return false;
  						}
  					});
  				}
  			} else {
  				elm.value = html;
  			}
  
  			args.element = elm = null;
  
  			if (args.set_dirty !== false) {
  				self.isNotDirty = true;
  			}
  
  			return html;
  		},
  
  		/**
  		 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
  		 * the different cleanup rules options.
  		 *
  		 * @method setContent
  		 * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
  		 * @param {Object} args Optional content object, this gets passed around through the whole set process.
  		 * @return {String} HTML string that got set into the editor.
  		 * @example
  		 * // Sets the HTML contents of the activeEditor editor
  		 * maya.activeEditor.setContent('<span>some</span> html');
  		 *
  		 * // Sets the raw contents of the activeEditor editor
  		 * maya.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
  		 *
  		 * // Sets the content of a specific editor (my_editor in this example)
  		 * maya.get('my_editor').setContent(data);
  		 *
  		 * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
  		 * maya.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
  		 */
  		setContent: function(content, args) {
  			var self = this, body = self.getBody(), forcedRootBlockName;
  
  			// Setup args object
  			args = args || {};
  			args.format = args.format || 'html';
  			args.set = true;
  			args.content = content;
  
  			// Do preprocessing
  			if (!args.no_events) {
  				self.fire('BeforeSetContent', args);
  			}
  
  			content = args.content;
  
  			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
  			// It will also be impossible to place the caret in the editor unless there is a BR element present
  			if (content.length === 0 || /^\s+$/.test(content)) {
  				forcedRootBlockName = self.settings.forced_root_block;
  
  				// Check if forcedRootBlock is configured and that the block is a valid child of the body
  				if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
  					// Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
  					content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
  					content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
  				} else if (!ie) {
  					// We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
  					content = '<br data-mce-bogus="1">';
  				}
  
  				self.dom.setHTML(body, content);
  
  				self.fire('SetContent', args);
  			} else {
  				// Parse and serialize the html
  				if (args.format !== 'raw') {
  					content = new Serializer({}, self.schema).serialize(
  						self.parser.parse(content, {isRootContent: true})
  					);
  				}
  
  				// Set the new cleaned contents to the editor
  				args.content = trim(content);
  				self.dom.setHTML(body, args.content);
  
  				// Do post processing
  				if (!args.no_events) {
  					self.fire('SetContent', args);
  				}
  
  				// Don't normalize selection if the focused element isn't the body in
  				// content editable mode since it will steal focus otherwise
  				/*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
  					self.selection.normalize();
  				}*/
  			}
  
  			return args.content;
  		},
  
  		/**
  		 * Gets the content from the editor instance, this will cleanup the content before it gets returned using
  		 * the different cleanup rules options.
  		 *
  		 * @method getContent
  		 * @param {Object} args Optional content object, this gets passed around through the whole get process.
  		 * @return {String} Cleaned content string, normally HTML contents.
  		 * @example
  		 * // Get the HTML contents of the currently active editor
  		 * console.debug(maya.activeEditor.getContent());
  		 *
  		 * // Get the raw contents of the currently active editor
  		 * maya.activeEditor.getContent({format: 'raw'});
  		 *
  		 * // Get content of a specific editor:
  		 * maya.get('content id').getContent()
  		 */
  		getContent: function(args) {
  			var self = this, content, body = self.getBody();
  
  			// Setup args object
  			args = args || {};
  			args.format = args.format || 'html';
  			args.get = true;
  			args.getInner = true;
  
  			// Do preprocessing
  			if (!args.no_events) {
  				self.fire('BeforeGetContent', args);
  			}
  
  			// Get raw contents or by default the cleaned contents
  			if (args.format == 'raw') {
  				content = body.innerHTML;
  			} else if (args.format == 'text') {
  				content = body.innerText || body.textContent;
  			} else {
  				content = self.serializer.serialize(body, args);
  			}
  
  			// Trim whitespace in beginning/end of HTML
  			if (args.format != 'text') {
  				args.content = trim(content);
  			} else {
  				args.content = content;
  			}
  
  			// Do post processing
  			if (!args.no_events) {
  				self.fire('GetContent', args);
  			}
  
  			return args.content;
  		},
  
  		/**
  		 * Inserts content at caret position.
  		 *
  		 * @method insertContent
  		 * @param {String} content Content to insert.
  		 * @param {Object} args Optional args to pass to insert call.
  		 */
  		insertContent: function(content, args) {
  			if (args) {
  				content = extend({content: content}, args);
  			}
  
  			this.execCommand('mceInsertContent', false, content);
  		},
  
  		/**
  		 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  		 *
  		 * @method isDirty
  		 * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  		 * @example
  		 * if (maya.activeEditor.isDirty())
  		 *     alert("You must save your contents.");
  		 */
  		isDirty: function() {
  			return !this.isNotDirty;
  		},
  
  		/**
  		 * Returns the editors container element. The container element wrappes in
  		 * all the elements added to the page for the editor. Such as UI, iframe etc.
  		 *
  		 * @method getContainer
  		 * @return {Element} HTML DOM element for the editor container.
  		 */
  		getContainer: function() {
  			var self = this;
  
  			if (!self.container) {
  				self.container = DOM.get(self.editorContainer || self.id + '_parent');
  			}
  
  			return self.container;
  		},
  
  		/**
  		 * Returns the editors content area container element. The this element is the one who
  		 * holds the iframe or the editable element.
  		 *
  		 * @method getContentAreaContainer
  		 * @return {Element} HTML DOM element for the editor area container.
  		 */
  		getContentAreaContainer: function() {
  			return this.contentAreaContainer;
  		},
  
  		/**
  		 * Returns the target element/textarea that got replaced with a maya editor instance.
  		 *
  		 * @method getElement
  		 * @return {Element} HTML DOM element for the replaced element.
  		 */
  		getElement: function() {
  			if (!this.targetElm) {
  				this.targetElm = DOM.get(this.id);
  			}
  
  			return this.targetElm;
  		},
  
  		/**
  		 * Returns the iframes window object.
  		 *
  		 * @method getWin
  		 * @return {Window} Iframe DOM window object.
  		 */
  		getWin: function() {
  			var self = this, elm;
  
  			if (!self.contentWindow) {
  				elm = self.iframeElement;
  
  				if (elm) {
  					self.contentWindow = elm.contentWindow;
  				}
  			}
  
  			return self.contentWindow;
  		},
  
  		/**
  		 * Returns the iframes document object.
  		 *
  		 * @method getDoc
  		 * @return {Document} Iframe DOM document object.
  		 */
  		getDoc: function() {
  			var self = this, win;
  
  			if (!self.contentDocument) {
  				win = self.getWin();
  
  				if (win) {
  					self.contentDocument = win.document;
  				}
  			}
  
  			return self.contentDocument;
  		},
  
  		/**
  		 * Returns the root element of the editable area.
  		 * For a non-inline iframe-based editor, returns the iframe's body element.
  		 *
  		 * @method getBody
  		 * @return {Element} The root element of the editable area.
  		 */
  		getBody: function() {
  			return this.bodyElement || this.getDoc().body;
  		},
  
  		/**
  		 * URL converter function this gets executed each time a user adds an img, a or
  		 * any other element that has a URL in it. This will be called both by the DOM and HTML
  		 * manipulation functions.
  		 *
  		 * @method convertURL
  		 * @param {string} url URL to convert.
  		 * @param {string} name Attribute name src, href etc.
  		 * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
  		 * @return {string} Converted URL string.
  		 */
  		convertURL: function(url, name, elm) {
  			var self = this, settings = self.settings;
  
  			// Use callback instead
  			if (settings.urlconverter_callback) {
  				return self.execCallback('urlconverter_callback', url, elm, true, name);
  			}
  
  			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
  			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
  				return url;
  			}
  
  			// Convert to relative
  			if (settings.relative_urls) {
  				return self.documentBaseURI.toRelative(url);
  			}
  
  			// Convert to absolute
  			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
  
  			return url;
  		},
  
  		/**
  		 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
  		 *
  		 * @method addVisual
  		 * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
  		 */
  		addVisual: function(elm) {
  			var self = this, settings = self.settings, dom = self.dom, cls;
  
  			elm = elm || self.getBody();
  
  			if (self.hasVisual === undefined) {
  				self.hasVisual = settings.visual;
  			}
  
  			each(dom.select('table,a', elm), function(elm) {
  				var value;
  
  				switch (elm.nodeName) {
  					case 'TABLE':
  						cls = settings.visual_table_class || 'mce-item-table';
  						value = dom.getAttrib(elm, 'border');
  
  						if ((!value || value == '0') && self.hasVisual) {
  							dom.addClass(elm, cls);
  						} else {
  							dom.removeClass(elm, cls);
  						}
  
  						return;
  
  					case 'A':
  						if (!dom.getAttrib(elm, 'href', false)) {
  							value = dom.getAttrib(elm, 'name') || elm.id;
  							cls = settings.visual_anchor_class || 'mce-item-anchor';
  
  							if (value && self.hasVisual) {
  								dom.addClass(elm, cls);
  							} else {
  								dom.removeClass(elm, cls);
  							}
  						}
  
  						return;
  				}
  			});
  
  			self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
  		},
  
  		/**
  		 * Removes the editor from the dom and maya collection.
  		 *
  		 * @method remove
  		 */
  		remove: function() {
  			var self = this;
  
  			if (!self.removed) {
  				self.save();
  				self.removed = 1;
  				self.unbindAllNativeEvents();
  
  				// Remove any hidden input
  				if (self.hasHiddenInput) {
  					DOM.remove(self.getElement().nextSibling);
  				}
  
  				if (!self.inline) {
  					// IE 9 has a bug where the selection stops working if you place the
  					// caret inside the editor then remove the iframe
  					if (ie && ie < 10) {
  						self.getDoc().execCommand('SelectAll', false, null);
  					}
  
  					DOM.setStyle(self.id, 'display', self.orgDisplay);
  					self.getBody().onload = null; // Prevent #6816
  				}
  
  				self.fire('remove');
  
  				self.editorManager.remove(self);
  				DOM.remove(self.getContainer());
  				self.destroy();
  			}
  		},
  
  		/**
  		 * Destroys the editor instance by removing all events, element references or other resources
  		 * that could leak memory. This method will be called automatically when the page is unloaded
  		 * but you can also call it directly if you know what you are doing.
  		 *
  		 * @method destroy
  		 * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
  		 */
  		destroy: function(automatic) {
  			var self = this, form;
  
  			// One time is enough
  			if (self.destroyed) {
  				return;
  			}
  
  			// If user manually calls destroy and not remove
  			// Users seems to have logic that calls destroy instead of remove
  			if (!automatic && !self.removed) {
  				self.remove();
  				return;
  			}
  
  			if (!automatic) {
  				self.editorManager.off('beforeunload', self._beforeUnload);
  
  				// Manual destroy
  				if (self.theme && self.theme.destroy) {
  					self.theme.destroy();
  				}
  
  				// Destroy controls, selection and dom
  				self.selection.destroy();
  				self.dom.destroy();
  			}
  
  			form = self.formElement;
  			if (form) {
  				if (form._mceOldSubmit) {
  					form.submit = form._mceOldSubmit;
  					form._mceOldSubmit = null;
  				}
  
  				DOM.unbind(form, 'submit reset', self.formEventDelegate);
  			}
  
  			self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
  			self.bodyElement = self.contentDocument = self.contentWindow = null;
  			self.iframeElement = self.targetElm = null;
  
  			if (self.selection) {
  				self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
  			}
  
  			self.destroyed = 1;
  		},
  
  		// Internal functions
  
  		_refreshContentEditable: function() {
  			var self = this, body, parent;
  
  			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
  			if (self._isHidden()) {
  				body = self.getBody();
  				parent = body.parentNode;
  
  				parent.removeChild(body);
  				parent.appendChild(body);
  
  				body.focus();
  			}
  		},
  
  		_isHidden: function() {
  			var sel;
  
  			if (!isGecko) {
  				return 0;
  			}
  
  			// Weird, wheres that cursor selection?
  			sel = this.selection.getSel();
  			return (!sel || !sel.rangeCount || sel.rangeCount === 0);
  		}
  	};
  
  	extend(Editor.prototype, EditorObservable);
  
  	return Editor;
  });
  
  // Included from: js/maya/classes/util/I18n.js
  
  /**
   * I18n.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * I18n class that handles translation of maya UI.
   * Uses po style with csharp style parameters.
   *
   * @class maya.util.I18n
   */
  define("maya/util/I18n", [], function() {
  	"use strict";
  
  	var data = {}, code = "en";
  
  	return {
  		/**
  		 * Sets the current language code.
  		 *
  		 * @method setCode
  		 * @param {String} newCode Current language code.
  		 */
  		setCode: function(newCode) {
  			if (newCode) {
  				code = newCode;
  				this.rtl = this.data[newCode] ? this.data[newCode]._dir === 'rtl' : false;
  			}
  		},
  
  		/**
  		 * Returns the current language code.
  		 *
  		 * @return {String} Current language code.
  		 */
  		getCode: function() {
  			return code;
  		},
  
  		/**
  		 * Property gets set to true if a RTL language pack was loaded.
  		 *
  		 * @property rtl
  		 * @type Boolean
  		 */
  		rtl: false,
  
  		/**
  		 * Adds translations for a specific language code.
  		 *
  		 * @method add
  		 * @param {String} code Language code like sv_SE.
  		 * @param {Array} items Name/value array with English en_US to sv_SE.
  		 */
  		add: function(code, items) {
  			var langData = data[code];
  
  			if (!langData) {
  				data[code] = langData = {};
  			}
  
  			for (var name in items) {
  				langData[name] = items[name];
  			}
  
  			this.setCode(code);
  		},
  
  		/**
  		 * Translates the specified text.
  		 *
  		 * It has a few formats:
  		 * I18n.translate("Text");
  		 * I18n.translate(["Text {0}/{1}", 0, 1]);
  		 * I18n.translate({raw: "Raw string"});
  		 *
  		 * @method translate
  		 * @param {String/Object/Array} text Text to translate.
  		 * @return {String} String that got translated.
  		 */
  		translate: function(text) {
  			var langData;
  
  			langData = data[code];
  			if (!langData) {
  				langData = {};
  			}
  
  			if (typeof text == "undefined") {
  				return text;
  			}
  
  			if (typeof text != "string" && text.raw) {
  				return text.raw;
  			}
  
  			if (text.push) {
  				var values = text.slice(1);
  
  				text = (langData[text[0]] || text[0]).replace(/\{([0-9]+)\}/g, function(match1, match2) {
  					return values[match2];
  				});
  			}
  
  			return (langData[text] || text).replace(/{context:\w+}$/, '');
  		},
  
  		data: data
  	};
  });
  
  // Included from: js/maya/classes/FocusManager.js
  
  /**
   * FocusManager.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class manages the focus/blur state of the editor. This class is needed since some
   * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
   *
   * This class will fire two events focus and blur on the editor instances that got affected.
   * It will also handle the restore of selection when the focus is lost and returned.
   *
   * @class maya.FocusManager
   */
  define("maya/FocusManager", [
  	"maya/dom/DOMUtils",
  	"maya/Env"
  ], function(DOMUtils, Env) {
  	var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
  
  	/**
  	 * Constructs a new focus manager instance.
  	 *
  	 * @constructor FocusManager
  	 * @param {maya.EditorManager} editorManager Editor manager instance to handle focus for.
  	 */
  	function FocusManager(editorManager) {
  		function getActiveElement() {
  			try {
  				return document.activeElement;
  			} catch (ex) {
  				// IE sometimes fails to get the activeElement when resizing table
  				// TODO: Investigate this
  				return document.body;
  			}
  		}
  
  		// We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
  		// TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
  		function createBookmark(dom, rng) {
  			if (rng && rng.startContainer) {
  				// Verify that the range is within the root of the editor
  				if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
  					return;
  				}
  
  				return {
  					startContainer: rng.startContainer,
  					startOffset: rng.startOffset,
  					endContainer: rng.endContainer,
  					endOffset: rng.endOffset
  				};
  			}
  
  			return rng;
  		}
  
  		function bookmarkToRng(editor, bookmark) {
  			var rng;
  
  			if (bookmark.startContainer) {
  				rng = editor.getDoc().createRange();
  				rng.setStart(bookmark.startContainer, bookmark.startOffset);
  				rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  			} else {
  				rng = bookmark;
  			}
  
  			return rng;
  		}
  
  		function isUIElement(elm) {
  			return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
  		}
  
  		function registerEvents(e) {
  			var editor = e.editor;
  
  			editor.on('init', function() {
  				// Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
  				if (editor.inline || Env.ie) {
  					// Use the onbeforedeactivate event when available since it works better see #7023
  					if ("onbeforedeactivate" in document && Env.ie < 9) {
  						editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
  							if (e.target != editor.getBody()) {
  								return;
  							}
  
  							try {
  								editor.lastRng = editor.selection.getRng();
  							} catch (ex) {
  								// IE throws "Unexcpected call to method or property access" some times so lets ignore it
  							}
  						});
  					} else {
  						// On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
  						editor.on('nodechange mouseup keyup', function(e) {
  							var node = getActiveElement();
  
  							// Only act on manual nodechanges
  							if (e.type == 'nodechange' && e.selectionChange) {
  								return;
  							}
  
  							// IE 11 reports active element as iframe not body of iframe
  							if (node && node.id == editor.id + '_ifr') {
  								node = editor.getBody();
  							}
  
  							if (editor.dom.isChildOf(node, editor.getBody())) {
  								editor.lastRng = editor.selection.getRng();
  							}
  						});
  					}
  
  					// Handles the issue with WebKit not retaining selection within inline document
  					// If the user releases the mouse out side the body since a mouse up event wont occur on the body
  					if (Env.webkit && !selectionChangeHandler) {
  						selectionChangeHandler = function() {
  							var activeEditor = editorManager.activeEditor;
  
  							if (activeEditor && activeEditor.selection) {
  								var rng = activeEditor.selection.getRng();
  
  								// Store when it's non collapsed
  								if (rng && !rng.collapsed) {
  									editor.lastRng = rng;
  								}
  							}
  						};
  
  						DOM.bind(document, 'selectionchange', selectionChangeHandler);
  					}
  				}
  			});
  
  			editor.on('setcontent', function() {
  				editor.lastRng = null;
  			});
  
  			// Remove last selection bookmark on mousedown see #6305
  			editor.on('mousedown', function() {
  				editor.selection.lastFocusBookmark = null;
  			});
  
  			editor.on('focusin', function() {
  				var focusedEditor = editorManager.focusedEditor;
  
  				if (editor.selection.lastFocusBookmark) {
  					editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
  					editor.selection.lastFocusBookmark = null;
  				}
  
  				if (focusedEditor != editor) {
  					if (focusedEditor) {
  						focusedEditor.fire('blur', {focusedEditor: editor});
  					}
  
  					editorManager.setActive(editor);
  					editorManager.focusedEditor = editor;
  					editor.fire('focus', {blurredEditor: focusedEditor});
  					editor.focus(true);
  				}
  
  				editor.lastRng = null;
  			});
  
  			editor.on('focusout', function() {
  				window.setTimeout(function() {
  					var focusedEditor = editorManager.focusedEditor;
  
  					// Still the same editor the the blur was outside any editor UI
  					if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
  						editor.fire('blur', {focusedEditor: null});
  						editorManager.focusedEditor = null;
  
  						// Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
  						if (editor.selection) {
  							editor.selection.lastFocusBookmark = null;
  						}
  					}
  				}, 0);
  			});
  
  			// Check if focus is moved to an element outside the active editor by checking if the target node
  			// isn't within the body of the activeEditor nor a UI element such as a dialog child control
  			if (!documentFocusInHandler) {
  				documentFocusInHandler = function(e) {
  					var activeEditor = editorManager.activeEditor;
  
  					if (activeEditor && e.target.ownerDocument == document) {
  						// Check to make sure we have a valid selection don't update the bookmark if it's
  						// a focusin to the body of the editor see #7025
  						if (activeEditor.selection && e.target != activeEditor.getBody()) {
  							activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
  						}
  
  						// Fire a blur event if the element isn't a UI element
  						if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
  							activeEditor.fire('blur', {focusedEditor: null});
  							editorManager.focusedEditor = null;
  						}
  					}
  				};
  
  				DOM.bind(document, 'focusin', documentFocusInHandler);
  			}
  
  			// Handle edge case when user starts the selection inside the editor and releases
  			// the mouse outside the editor producing a new selection. This weird workaround is needed since
  			// Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
  			if (editor.inline && !documentMouseUpHandler) {
  				documentMouseUpHandler = function(e) {
  					var activeEditor = editorManager.activeEditor;
  
  					if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
  						var rng = activeEditor.selection.getRng();
  
  						if (!rng.collapsed) {
  							activeEditor.lastRng = rng;
  						}
  					}
  				};
  
  				DOM.bind(document, 'mouseup', documentMouseUpHandler);
  			}
  		}
  
  		function unregisterDocumentEvents(e) {
  			if (editorManager.focusedEditor == e.editor) {
  				editorManager.focusedEditor = null;
  			}
  
  			if (!editorManager.activeEditor) {
  				DOM.unbind(document, 'selectionchange', selectionChangeHandler);
  				DOM.unbind(document, 'focusin', documentFocusInHandler);
  				DOM.unbind(document, 'mouseup', documentMouseUpHandler);
  				selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
  			}
  		}
  
  		editorManager.on('AddEditor', registerEvents);
  		editorManager.on('RemoveEditor', unregisterDocumentEvents);
  	}
  
  	/**
  	 * Returns true if the specified element is part of the UI for example an button or text input.
  	 *
  	 * @method isEditorUIElement
  	 * @param  {Element} elm Element to check if it's part of the UI or not.
  	 * @return {Boolean} True/false state if the element is part of the UI or not.
  	 */
  	FocusManager.isEditorUIElement = function(elm) {
  		// Needs to be converted to string since svg can have focus: #6776
  		return elm.className.toString().indexOf('mce-') !== -1;
  	};
  
  	return FocusManager;
  });
  
  // Included from: js/maya/classes/EditorManager.js
  
  /**
   * EditorManager.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class used as a factory for manager for maya.Editor instances.
   *
   * @example
   * maya.EditorManager.init({});
   *
   * @class maya.EditorManager
   * @mixes maya.util.Observable
   * @static
   */
  define("maya/EditorManager", [
  	"maya/Editor",
  	"maya/dom/DomQuery",
  	"maya/dom/DOMUtils",
  	"maya/util/URI",
  	"maya/Env",
  	"maya/util/Tools",
  	"maya/util/Observable",
  	"maya/util/I18n",
  	"maya/FocusManager"
  ], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
  	var DOM = DOMUtils.DOM;
  	var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
  	var instanceCounter = 0, beforeUnloadDelegate, EditorManager;
  
  	function removeEditorFromList(editor) {
  		var editors = EditorManager.editors, removedFromList;
  
  		delete editors[editor.id];
  
  		for (var i = 0; i < editors.length; i++) {
  			if (editors[i] == editor) {
  				editors.splice(i, 1);
  				removedFromList = true;
  				break;
  			}
  		}
  
  		// Select another editor since the active one was removed
  		if (EditorManager.activeEditor == editor) {
  			EditorManager.activeEditor = editors[0];
  		}
  
  		// Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
  		if (EditorManager.focusedEditor == editor) {
  			EditorManager.focusedEditor = null;
  		}
  
  		return removedFromList;
  	}
  
  	function purgeDestroyedEditor(editor) {
  		// User has manually destroyed the editor lets clean up the mess
  		if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
  			removeEditorFromList(editor);
  			editor.unbindAllNativeEvents();
  			editor.destroy(true);
  			editor = null;
  		}
  
  		return editor;
  	}
  
  	EditorManager = {
  		/**
  		 * Dom query instance.
  		 *
  		 * @property $
  		 * @type maya.dom.DomQuery
  		 */
  		$: DomQuery,
  
  		/**
  		 * Major version of maya build.
  		 *
  		 * @property majorVersion
  		 * @type String
  		 */
  		majorVersion: '4',
  
  		/**
  		 * Minor version of maya build.
  		 *
  		 * @property minorVersion
  		 * @type String
  		 */
  		minorVersion: '1.9',
  
  		/**
  		 * Release date of maya build.
  		 *
  		 * @property releaseDate
  		 * @type String
  		 */
  		releaseDate: '2015-03-10',
  
  		/**
  		 * Collection of editor instances.
  		 *
  		 * @property editors
  		 * @type Object
  		 * @example
  		 * for (edId in maya.editors)
  		 *     maya.editors[edId].save();
  		 */
  		editors: [],
  
  		/**
  		 * Collection of language pack data.
  		 *
  		 * @property i18n
  		 * @type Object
  		 */
  		i18n: I18n,
  
  		/**
  		 * Currently active editor instance.
  		 *
  		 * @property activeEditor
  		 * @type maya.Editor
  		 * @example
  		 * maya.activeEditor.selection.getContent();
  		 * maya.EditorManager.activeEditor.selection.getContent();
  		 */
  		activeEditor: null,
  
  		setup: function() {
  			var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
  
  			// Get base URL for the current document
  			documentBaseURL = document.location.href;
  
  			// Check if the URL is a document based format like: http://site/dir/file and file:///
  			// leave other formats like applewebdata://... intact
  			if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
  				documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
  
  				if (!/[\/\\]$/.test(documentBaseURL)) {
  					documentBaseURL += '/';
  				}
  			}
  
  			// If maya is defined and has a base use that or use the old mayaPreInit
  			preInit = window.maya || window.mayaPreInit;
  			if (preInit) {
  				baseURL = preInit.base || preInit.baseURL;
  				suffix = preInit.suffix;
  			} else {
  				// Get base where the maya script is located
  				var scripts = document.getElementsByTagName('script');
  				for (var i = 0; i < scripts.length; i++) {
  					src = scripts[i].src;
  
  					// Script types supported:
  					// maya.js maya.min.js maya.dev.js
  					// maya.jquery.js maya.jquery.min.js maya.jquery.dev.js
  					// maya.full.js maya.full.min.js maya.full.dev.js
  					if (/maya(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
  						if (src.indexOf('.min') != -1) {
  							suffix = '.min';
  						}
  
  						baseURL = src.substring(0, src.lastIndexOf('/'));
  						break;
  					}
  				}
  
  				// We didn't find any baseURL by looking at the script elements
  				// Try to use the document.currentScript as a fallback
  				if (!baseURL && document.currentScript) {
  					src = document.currentScript.src;
  
  					if (src.indexOf('.min') != -1) {
  						suffix = '.min';
  					}
  
  					baseURL = src.substring(0, src.lastIndexOf('/'));
  				}
  			}
  
  			/**
  			 * Base URL where the root directory if maya is located.
  			 *
  			 * @property baseURL
  			 * @type String
  			 */
  			self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
  
  			/**
  			 * Document base URL where the current document is located.
  			 *
  			 * @property documentBaseURL
  			 * @type String
  			 */
  			self.documentBaseURL = documentBaseURL;
  
  			/**
  			 * Absolute baseURI for the installation path of maya.
  			 *
  			 * @property baseURI
  			 * @type maya.util.URI
  			 */
  			self.baseURI = new URI(self.baseURL);
  
  			/**
  			 * Current suffix to add to each plugin/theme that gets loaded for example ".min".
  			 *
  			 * @property suffix
  			 * @type String
  			 */
  			self.suffix = suffix;
  
  			self.focusManager = new FocusManager(self);
  		},
  
  		/**
  		 * Initializes a set of editors. This method will create editors based on various settings.
  		 *
  		 * @method init
  		 * @param {Object} settings Settings object to be passed to each editor instance.
  		 * @example
  		 * // Initializes a editor using the longer method
  		 * maya.EditorManager.init({
  		 *    some_settings : 'some value'
  		 * });
  		 *
  		 * // Initializes a editor instance using the shorter version
  		 * maya.init({
  		 *    some_settings : 'some value'
  		 * });
  		 */
  		init: function(settings) {
  			var self = this, editors = [];
  
  			function createId(elm) {
  				var id = elm.id;
  
  				// Use element id, or unique name or generate a unique id
  				if (!id) {
  					id = elm.name;
  
  					if (id && !DOM.get(id)) {
  						id = elm.name;
  					} else {
  						// Generate unique name
  						id = DOM.uniqueId();
  					}
  
  					elm.setAttribute('id', id);
  				}
  
  				return id;
  			}
  
  			function createEditor(id, settings, targetElm) {
  				if (!purgeDestroyedEditor(self.get(id))) {
  					var editor = new Editor(id, settings, self);
  
  					editor.targetElm = editor.targetElm || targetElm;
  					editors.push(editor);
  					editor.render();
  				}
  			}
  
  			function execCallback(name) {
  				var callback = settings[name];
  
  				if (!callback) {
  					return;
  				}
  
  				return callback.apply(self, Array.prototype.slice.call(arguments, 2));
  			}
  
  			function hasClass(elm, className) {
  				return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
  			}
  
  			function readyHandler() {
  				var l, co;
  
  				DOM.unbind(window, 'ready', readyHandler);
  
  				execCallback('onpageload');
  
  				if (settings.types) {
  					// Process type specific selector
  					each(settings.types, function(type) {
  						each(DOM.select(type.selector), function(elm) {
  							createEditor(createId(elm), extend({}, settings, type), elm);
  						});
  					});
  
  					return;
  				} else if (settings.selector) {
  					// Process global selector
  					each(DOM.select(settings.selector), function(elm) {
  						createEditor(createId(elm), settings, elm);
  					});
  
  					return;
  				} else if (settings.target) {
  					createEditor(createId(settings.target), settings);
  				}
  
  				// Fallback to old setting
  				switch (settings.mode) {
  					case "exact":
  						l = settings.elements || '';
  
  						if (l.length > 0) {
  							each(explode(l), function(id) {
  								var elm;
  
  								if ((elm = DOM.get(id))) {
  									createEditor(id, settings, elm);
  								} else {
  									each(document.forms, function(f) {
  										each(f.elements, function(e) {
  											if (e.name === id) {
  												id = 'mce_editor_' + instanceCounter++;
  												DOM.setAttrib(e, 'id', id);
  												createEditor(id, settings, e);
  											}
  										});
  									});
  								}
  							});
  						}
  						break;
  
  					case "textareas":
  					case "specific_textareas":
  						each(DOM.select('textarea'), function(elm) {
  							if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
  								return;
  							}
  
  							if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
  								createEditor(createId(elm), settings, elm);
  							}
  						});
  						break;
  				}
  
  				// Call onInit when all editors are initialized
  				if (settings.oninit) {
  					l = co = 0;
  
  					each(editors, function(ed) {
  						co++;
  
  						if (!ed.initialized) {
  							// Wait for it
  							ed.on('init', function() {
  								l++;
  
  								// All done
  								if (l == co) {
  									execCallback('oninit');
  								}
  							});
  						} else {
  							l++;
  						}
  
  						// All done
  						if (l == co) {
  							execCallback('oninit');
  						}
  					});
  				}
  			}
  
  			self.settings = settings;
  
  			DOM.bind(window, 'ready', readyHandler);
  		},
  
  		/**
  		 * Returns a editor instance by id.
  		 *
  		 * @method get
  		 * @param {String/Number} id Editor instance id or index to return.
  		 * @return {maya.Editor} Editor instance to return.
  		 * @example
  		 * // Adds an onclick event to an editor by id (shorter version)
  		 * maya.get('mytextbox').on('click', function(e) {
  		 *    ed.windowManager.alert('Hello world!');
  		 * });
  		 *
  		 * // Adds an onclick event to an editor by id (longer version)
  		 * maya.EditorManager.get('mytextbox').on('click', function(e) {
  		 *    ed.windowManager.alert('Hello world!');
  		 * });
  		 */
  		get: function(id) {
  			if (!arguments.length) {
  				return this.editors;
  			}
  
  			return id in this.editors ? this.editors[id] : null;
  		},
  
  		/**
  		 * Adds an editor instance to the editor collection. This will also set it as the active editor.
  		 *
  		 * @method add
  		 * @param {maya.Editor} editor Editor instance to add to the collection.
  		 * @return {maya.Editor} The same instance that got passed in.
  		 */
  		add: function(editor) {
  			var self = this, editors = self.editors;
  
  			// Add named and index editor instance
  			editors[editor.id] = editor;
  			editors.push(editor);
  
  			// Doesn't call setActive method since we don't want
  			// to fire a bunch of activate/deactivate calls while initializing
  			self.activeEditor = editor;
  
  			/**
  			 * Fires when an editor is added to the EditorManager collection.
  			 *
  			 * @event AddEditor
  			 * @param {Object} e Event arguments.
  			 */
  			self.fire('AddEditor', {editor: editor});
  
  			if (!beforeUnloadDelegate) {
  				beforeUnloadDelegate = function() {
  					self.fire('BeforeUnload');
  				};
  
  				DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
  			}
  
  			return editor;
  		},
  
  		/**
  		 * Creates an editor instance and adds it to the EditorManager collection.
  		 *
  		 * @method createEditor
  		 * @param {String} id Instance id to use for editor.
  		 * @param {Object} settings Editor instance settings.
  		 * @return {maya.Editor} Editor instance that got created.
  		 */
  		createEditor: function(id, settings) {
  			return this.add(new Editor(id, settings, this));
  		},
  
  		/**
  		 * Removes a editor or editors form page.
  		 *
  		 * @example
  		 * // Remove all editors bound to divs
  		 * maya.remove('div');
  		 *
  		 * // Remove all editors bound to textareas
  		 * maya.remove('textarea');
  		 *
  		 * // Remove all editors
  		 * maya.remove();
  		 *
  		 * // Remove specific instance by id
  		 * maya.remove('#id');
  		 *
  		 * @method remove
  		 * @param {maya.Editor/String/Object} [selector] CSS selector or editor instance to remove.
  		 * @return {maya.Editor} The editor that got passed in will be return if it was found otherwise null.
  		 */
  		remove: function(selector) {
  			var self = this, i, editors = self.editors, editor;
  
  			// Remove all editors
  			if (!selector) {
  				for (i = editors.length - 1; i >= 0; i--) {
  					self.remove(editors[i]);
  				}
  
  				return;
  			}
  
  			// Remove editors by selector
  			if (typeof selector == "string") {
  				selector = selector.selector || selector;
  
  				each(DOM.select(selector), function(elm) {
  					editor = editors[elm.id];
  
  					if (editor) {
  						self.remove(editor);
  					}
  				});
  
  				return;
  			}
  
  			// Remove specific editor
  			editor = selector;
  
  			// Not in the collection
  			if (!editors[editor.id]) {
  				return null;
  			}
  
  			/**
  			 * Fires when an editor is removed from EditorManager collection.
  			 *
  			 * @event RemoveEditor
  			 * @param {Object} e Event arguments.
  			 */
  			if (removeEditorFromList(editor)) {
  				self.fire('RemoveEditor', {editor: editor});
  			}
  
  			if (!editors.length) {
  				DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
  			}
  
  			editor.remove();
  
  			return editor;
  		},
  
  		/**
  		 * Executes a specific command on the currently active editor.
  		 *
  		 * @method execCommand
  		 * @param {String} c Command to perform for example Bold.
  		 * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
  		 * @param {String} v Optional value parameter like for example an URL to a link.
  		 * @return {Boolean} true/false if the command was executed or not.
  		 */
  		execCommand: function(cmd, ui, value) {
  			var self = this, editor = self.get(value);
  
  			// Manager commands
  			switch (cmd) {
  				case "mceAddEditor":
  					if (!self.get(value)) {
  						new Editor(value, self.settings, self).render();
  					}
  
  					return true;
  
  				case "mceRemoveEditor":
  					if (editor) {
  						editor.remove();
  					}
  
  					return true;
  
  				case 'mceToggleEditor':
  					if (!editor) {
  						self.execCommand('mceAddEditor', 0, value);
  						return true;
  					}
  
  					if (editor.isHidden()) {
  						editor.show();
  					} else {
  						editor.hide();
  					}
  
  					return true;
  			}
  
  			// Run command on active editor
  			if (self.activeEditor) {
  				return self.activeEditor.execCommand(cmd, ui, value);
  			}
  
  			return false;
  		},
  
  		/**
  		 * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
  		 *
  		 * @method triggerSave
  		 * @example
  		 * // Saves all contents
  		 * maya.triggerSave();
  		 */
  		triggerSave: function() {
  			each(this.editors, function(editor) {
  				editor.save();
  			});
  		},
  
  		/**
  		 * Adds a language pack, this gets called by the loaded language files like en.js.
  		 *
  		 * @method addI18n
  		 * @param {String} code Optional language code.
  		 * @param {Object} items Name/value object with translations.
  		 */
  		addI18n: function(code, items) {
  			I18n.add(code, items);
  		},
  
  		/**
  		 * Translates the specified string using the language pack items.
  		 *
  		 * @method translate
  		 * @param {String/Array/Object} text String to translate
  		 * @return {String} Translated string.
  		 */
  		translate: function(text) {
  			return I18n.translate(text);
  		},
  
  		/**
  		 * Sets the active editor instance and fires the deactivate/activate events.
  		 *
  		 * @method setActive
  		 * @param {maya.Editor} editor Editor instance to set as the active instance.
  		 */
  		setActive: function(editor) {
  			var activeEditor = this.activeEditor;
  
  			if (this.activeEditor != editor) {
  				if (activeEditor) {
  					activeEditor.fire('deactivate', {relatedTarget: editor});
  				}
  
  				editor.fire('activate', {relatedTarget: activeEditor});
  			}
  
  			this.activeEditor = editor;
  		}
  	};
  
  	extend(EditorManager, Observable);
  
  	EditorManager.setup();
  
  	// Export EditorManager as maya/maya in global namespace
  	window.maya = window.maya = EditorManager;
  
  	return EditorManager;
  });
  
  // Included from: js/maya/classes/LegacyInput.js
  
  /**
   * LegacyInput.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  define("maya/LegacyInput", [
  	"maya/EditorManager",
  	"maya/util/Tools"
  ], function(EditorManager, Tools) {
  	var each = Tools.each, explode = Tools.explode;
  
  	EditorManager.on('AddEditor', function(e) {
  		var editor = e.editor;
  
  		editor.on('preInit', function() {
  			var filters, fontSizes, dom, settings = editor.settings;
  
  			function replaceWithSpan(node, styles) {
  				each(styles, function(value, name) {
  					if (value) {
  						dom.setStyle(node, name, value);
  					}
  				});
  
  				dom.rename(node, 'span');
  			}
  
  			function convert(e) {
  				dom = editor.dom;
  
  				if (settings.convert_fonts_to_spans) {
  					each(dom.select('font,u,strike', e.node), function(node) {
  						filters[node.nodeName.toLowerCase()](dom, node);
  					});
  				}
  			}
  
  			if (settings.inline_styles) {
  				fontSizes = explode(settings.font_size_legacy_values);
  
  				filters = {
  					font: function(dom, node) {
  						replaceWithSpan(node, {
  							backgroundColor: node.style.backgroundColor,
  							color: node.color,
  							fontFamily: node.face,
  							fontSize: fontSizes[parseInt(node.size, 10) - 1]
  						});
  					},
  
  					u: function(dom, node) {
  						// HTML5 allows U element
  						if (editor.settings.schema === "html4") {
  							replaceWithSpan(node, {
  								textDecoration: 'underline'
  							});
  						}
  					},
  
  					strike: function(dom, node) {
  						replaceWithSpan(node, {
  							textDecoration: 'line-through'
  						});
  					}
  				};
  
  				editor.on('PreProcess SetContent', convert);
  			}
  		});
  	});
  });
  
  // Included from: js/maya/classes/util/XHR.js
  
  /**
   * XHR.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class enables you to send XMLHTTPRequests cross browser.
   * @class maya.util.XHR
   * @mixes maya.util.Observable
   * @static
   * @example
   * // Sends a low level Ajax request
   * maya.util.XHR.send({
   *    url: 'someurl',
   *    success: function(text) {
   *       console.debug(text);
   *    }
   * });
   *
   * // Add custom header to XHR request
   * maya.util.XHR.on('beforeSend', function(e) {
   *     e.xhr.setRequestHeader('X-Requested-With', 'Something');
   * });
   */
  define("maya/util/XHR", [
  	"maya/util/Observable",
  	"maya/util/Tools"
  ], function(Observable, Tools) {
  	var XHR = {
  		/**
  		 * Sends a XMLHTTPRequest.
  		 * Consult the Wiki for details on what settings this method takes.
  		 *
  		 * @method send
  		 * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
  		 */
  		send: function(settings) {
  			var xhr, count = 0;
  
  			function ready() {
  				if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
  					if (settings.success && count < 10000 && xhr.status == 200) {
  						settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
  					} else if (settings.error) {
  						settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
  					}
  
  					xhr = null;
  				} else {
  					setTimeout(ready, 10);
  				}
  			}
  
  			// Default settings
  			settings.scope = settings.scope || this;
  			settings.success_scope = settings.success_scope || settings.scope;
  			settings.error_scope = settings.error_scope || settings.scope;
  			settings.async = settings.async === false ? false : true;
  			settings.data = settings.data || '';
  
  			xhr = new XMLHttpRequest();
  
  			if (xhr) {
  				if (xhr.overrideMimeType) {
  					xhr.overrideMimeType(settings.content_type);
  				}
  
  				xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
  
  				if (settings.crossDomain) {
  					xhr.withCredentials = true;
  				}
  
  				if (settings.content_type) {
  					xhr.setRequestHeader('Content-Type', settings.content_type);
  				}
  
  				xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  
  				xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr;
  				xhr.send(settings.data);
  
  				// Syncronous request
  				if (!settings.async) {
  					return ready();
  				}
  
  				// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
  				setTimeout(ready, 10);
  			}
  		}
  	};
  
  	Tools.extend(XHR, Observable);
  
  	return XHR;
  });
  
  // Included from: js/maya/classes/util/JSON.js
  
  /**
   * JSON.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * JSON parser and serializer class.
   *
   * @class maya.util.JSON
   * @static
   * @example
   * // JSON parse a string into an object
   * var obj = maya.util.JSON.parse(somestring);
   *
   * // JSON serialize a object into an string
   * var str = maya.util.JSON.serialize(obj);
   */
  define("maya/util/JSON", [], function() {
  	function serialize(o, quote) {
  		var i, v, t, name;
  
  		quote = quote || '"';
  
  		if (o === null) {
  			return 'null';
  		}
  
  		t = typeof o;
  
  		if (t == 'string') {
  			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
  
  			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
  				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
  				if (quote === '"' && a === "'") {
  					return a;
  				}
  
  				i = v.indexOf(b);
  
  				if (i + 1) {
  					return '\\' + v.charAt(i + 1);
  				}
  
  				a = b.charCodeAt().toString(16);
  
  				return '\\u' + '0000'.substring(a.length) + a;
  			}) + quote;
  		}
  
  		if (t == 'object') {
  			if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
  				for (i = 0, v = '['; i < o.length; i++) {
  					v += (i > 0 ? ',' : '') + serialize(o[i], quote);
  				}
  
  				return v + ']';
  			}
  
  			v = '{';
  
  			for (name in o) {
  				if (o.hasOwnProperty(name)) {
  					v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
  						quote + ':' + serialize(o[name], quote) : '';
  				}
  			}
  
  			return v + '}';
  		}
  
  		return '' + o;
  	}
  
  	return {
  		/**
  		 * Serializes the specified object as a JSON string.
  		 *
  		 * @method serialize
  		 * @param {Object} obj Object to serialize as a JSON string.
  		 * @param {String} quote Optional quote string defaults to ".
  		 * @return {string} JSON string serialized from input.
  		 */
  		serialize: serialize,
  
  		/**
  		 * Unserializes/parses the specified JSON string into a object.
  		 *
  		 * @method parse
  		 * @param {string} s JSON String to parse into a JavaScript object.
  		 * @return {Object} Object from input JSON string or undefined if it failed.
  		 */
  		parse: function(text) {
  			try {
  				// Trick uglify JS
  				return window[String.fromCharCode(101) + 'val']('(' + text + ')');
  			} catch (ex) {
  				// Ignore
  			}
  		}
  
  		/**#@-*/
  	};
  });
  
  // Included from: js/maya/classes/util/JSONRequest.js
  
  /**
   * JSONRequest.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class enables you to use JSON-RPC to call backend methods.
   *
   * @class maya.util.JSONRequest
   * @example
   * var json = new maya.util.JSONRequest({
   *     url: 'somebackend.php'
   * });
   *
   * // Send RPC call 1
   * json.send({
   *     method: 'someMethod1',
   *     params: ['a', 'b'],
   *     success: function(result) {
   *         console.dir(result);
   *     }
   * });
   *
   * // Send RPC call 2
   * json.send({
   *     method: 'someMethod2',
   *     params: ['a', 'b'],
   *     success: function(result) {
   *         console.dir(result);
   *     }
   * });
   */
  define("maya/util/JSONRequest", [
  	"maya/util/JSON",
  	"maya/util/XHR",
  	"maya/util/Tools"
  ], function(JSON, XHR, Tools) {
  	var extend = Tools.extend;
  
  	function JSONRequest(settings) {
  		this.settings = extend({}, settings);
  		this.count = 0;
  	}
  
  	/**
  	 * Simple helper function to send a JSON-RPC request without the need to initialize an object.
  	 * Consult the Wiki API documentation for more details on what you can pass to this function.
  	 *
  	 * @method sendRPC
  	 * @static
  	 * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
  	 */
  	JSONRequest.sendRPC = function(o) {
  		return new JSONRequest().send(o);
  	};
  
  	JSONRequest.prototype = {
  		/**
  		 * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
  		 *
  		 * @method send
  		 * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
  		 */
  		send: function(args) {
  			var ecb = args.error, scb = args.success;
  
  			args = extend(this.settings, args);
  
  			args.success = function(c, x) {
  				c = JSON.parse(c);
  
  				if (typeof c == 'undefined') {
  					c = {
  						error: 'JSON Parse error.'
  					};
  				}
  
  				if (c.error) {
  					ecb.call(args.error_scope || args.scope, c.error, x);
  				} else {
  					scb.call(args.success_scope || args.scope, c.result);
  				}
  			};
  
  			args.error = function(ty, x) {
  				if (ecb) {
  					ecb.call(args.error_scope || args.scope, ty, x);
  				}
  			};
  
  			args.data = JSON.serialize({
  				id: args.id || 'c' + (this.count++),
  				method: args.method,
  				params: args.params
  			});
  
  			// JSON content type for Ruby on rails. Bug: #1883287
  			args.content_type = 'application/json';
  
  			XHR.send(args);
  		}
  	};
  
  	return JSONRequest;
  });
  
  // Included from: js/maya/classes/util/JSONP.js
  
  /**
   * JSONP.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  define("maya/util/JSONP", [
  	"maya/dom/DOMUtils"
  ], function(DOMUtils) {
  	return {
  		callbacks: {},
  		count: 0,
  
  		send: function(settings) {
  			var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
  			var id = 'maya_jsonp_' + count;
  
  			self.callbacks[count] = function(json) {
  				dom.remove(id);
  				delete self.callbacks[count];
  
  				settings.callback(json);
  			};
  
  			dom.add(dom.doc.body, 'script', {
  				id: id,
  				src: settings.url,
  				type: 'text/javascript'
  			});
  
  			self.count++;
  		}
  	};
  });
  
  // Included from: js/maya/classes/util/LocalStorage.js
  
  /**
   * LocalStorage.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
   * Storage is done using userData on IE 7 and a special serialization format. The format is designed
   * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
   * makes it possible to store for example HTML data.
   *
   * Storage format for userData:
   * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
   *
   * For example this data key1=value1,key2=value2 would be:
   * 4,key1,6,value1,4,key2,6,value2
   *
   * @class maya.util.LocalStorage
   * @static
   * @version 4.0
   * @example
   * maya.util.LocalStorage.setItem('key', 'value');
   * var value = maya.util.LocalStorage.getItem('key');
   */
  define("maya/util/LocalStorage", [], function() {
  	var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
  
  	// Check for native support
  	try {
  		if (window.localStorage) {
  			return localStorage;
  		}
  	} catch (ex) {
  		// Ignore
  	}
  
  	userDataKey = "maya";
  	storageElm = document.documentElement;
  	hasOldIEDataSupport = !!storageElm.addBehavior;
  
  	if (hasOldIEDataSupport) {
  		storageElm.addBehavior('#default#userData');
  	}
  
  	/**
  	 * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
  	 */
  	function updateKeys() {
  		keys = [];
  
  		for (var key in items) {
  			keys.push(key);
  		}
  
  		LocalStorage.length = keys.length;
  	}
  
  	/**
  	 * Loads the userData string and parses it into the items structure.
  	 */
  	function load() {
  		var key, data, value, pos = 0;
  
  		items = {};
  
  		// localStorage can be disabled on WebKit/Gecko so make a dummy storage
  		if (!hasOldIEDataSupport) {
  			return;
  		}
  
  		function next(end) {
  			var value, nextPos;
  
  			nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
  			if (nextPos === -1 || nextPos > data.length) {
  				return null;
  			}
  
  			value = data.substring(pos, nextPos);
  			pos = nextPos + 1;
  
  			return value;
  		}
  
  		storageElm.load(userDataKey);
  		data = storageElm.getAttribute(userDataKey) || '';
  
  		do {
  			var offset = next();
  			if (offset === null) {
  				break;
  			}
  
  			key = next(parseInt(offset, 32) || 0);
  			if (key !== null) {
  				offset = next();
  				if (offset === null) {
  					break;
  				}
  
  				value = next(parseInt(offset, 32) || 0);
  
  				if (key) {
  					items[key] = value;
  				}
  			}
  		} while (key !== null);
  
  		updateKeys();
  	}
  
  	/**
  	 * Saves the items structure into a the userData format.
  	 */
  	function save() {
  		var value, data = '';
  
  		// localStorage can be disabled on WebKit/Gecko so make a dummy storage
  		if (!hasOldIEDataSupport) {
  			return;
  		}
  
  		for (var key in items) {
  			value = items[key];
  			data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
  		}
  
  		storageElm.setAttribute(userDataKey, data);
  
  		try {
  			storageElm.save(userDataKey);
  		} catch (ex) {
  			// Ignore disk full
  		}
  
  		updateKeys();
  	}
  
  	LocalStorage = {
  		/**
  		 * Length of the number of items in storage.
  		 *
  		 * @property length
  		 * @type Number
  		 * @return {Number} Number of items in storage.
  		 */
  		//length:0,
  
  		/**
  		 * Returns the key name by index.
  		 *
  		 * @method key
  		 * @param {Number} index Index of key to return.
  		 * @return {String} Key value or null if it wasn't found.
  		 */
  		key: function(index) {
  			return keys[index];
  		},
  
  		/**
  		 * Returns the value if the specified key or null if it wasn't found.
  		 *
  		 * @method getItem
  		 * @param {String} key Key of item to retrive.
  		 * @return {String} Value of the specified item or null if it wasn't found.
  		 */
  		getItem: function(key) {
  			return key in items ? items[key] : null;
  		},
  
  		/**
  		 * Sets the value of the specified item by it's key.
  		 *
  		 * @method setItem
  		 * @param {String} key Key of the item to set.
  		 * @param {String} value Value of the item to set.
  		 */
  		setItem: function(key, value) {
  			items[key] = "" + value;
  			save();
  		},
  
  		/**
  		 * Removes the specified item by key.
  		 *
  		 * @method removeItem
  		 * @param {String} key Key of item to remove.
  		 */
  		removeItem: function(key) {
  			delete items[key];
  			save();
  		},
  
  		/**
  		 * Removes all items.
  		 *
  		 * @method clear
  		 */
  		clear: function() {
  			items = {};
  			save();
  		}
  	};
  
  	load();
  
  	return LocalStorage;
  });
  
  // Included from: js/maya/classes/Compat.js
  
  /**
   * Compat.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * maya core class.
   *
   * @static
   * @class maya
   * @borrow-members maya.EditorManager
   * @borrow-members maya.util.Tools
   */
  define("maya/Compat", [
  	"maya/dom/DOMUtils",
  	"maya/dom/EventUtils",
  	"maya/dom/ScriptLoader",
  	"maya/AddOnManager",
  	"maya/util/Tools",
  	"maya/Env"
  ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
  	var maya = window.maya;
  
  	/**
  	 * @property {maya.dom.DOMUtils} DOM Global DOM instance.
  	 * @property {maya.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
  	 * @property {maya.AddOnManager} PluginManager Global PluginManager instance.
  	 * @property {maya.AddOnManager} ThemeManager Global ThemeManager instance.
  	 */
  	maya.DOM = DOMUtils.DOM;
  	maya.ScriptLoader = ScriptLoader.ScriptLoader;
  	maya.PluginManager = AddOnManager.PluginManager;
  	maya.ThemeManager = AddOnManager.ThemeManager;
  
  	maya.dom = maya.dom || {};
  	maya.dom.Event = EventUtils.Event;
  
  	Tools.each(Tools, function(func, key) {
  		maya[key] = func;
  	});
  
  	Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
  		maya[name] = Env[name.substr(2).toLowerCase()];
  	});
  
  	return {};
  });
  
  // Describe the different namespaces
  
  /**
   * Root level namespace this contains classes directly releated to the maya editor.
   *
   * @namespace maya
   */
  
  /**
   * Contains classes for handling the browsers DOM.
   *
   * @namespace maya.dom
   */
  
  /**
   * Contains html parser and serializer logic.
   *
   * @namespace maya.html
   */
  
  /**
   * Contains the different UI types such as buttons, listboxes etc.
   *
   * @namespace maya.ui
   */
  
  /**
   * Contains various utility classes such as json parser, cookies etc.
   *
   * @namespace maya.util
   */
  
  // Included from: js/maya/classes/ui/Layout.js
  
  /**
   * Layout.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Base layout manager class.
   *
   * @class maya.ui.Layout
   */
  define("maya/ui/Layout", [
  	"maya/util/Class",
  	"maya/util/Tools"
  ], function(Class, Tools) {
  	"use strict";
  
  	return Class.extend({
  		Defaults: {
  			firstControlClass: 'first',
  			lastControlClass: 'last'
  		},
  
  		/**
  		 * Constructs a layout instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			this.settings = Tools.extend({}, this.Defaults, settings);
  		},
  
  		/**
  		 * This method gets invoked before the layout renders the controls.
  		 *
  		 * @method preRender
  		 * @param {maya.ui.Container} container Container instance to preRender.
  		 */
  		preRender: function(container) {
  			container.addClass(this.settings.containerClass, 'body');
  		},
  
  		/**
  		 * Applies layout classes to the container.
  		 *
  		 * @private
  		 */
  		applyClasses: function(container) {
  			var self = this, settings = self.settings, items, firstClass, lastClass;
  
  			items = container.items().filter(':visible');
  			firstClass = settings.firstControlClass;
  			lastClass = settings.lastControlClass;
  
  			items.each(function(item) {
  				item.removeClass(firstClass).removeClass(lastClass);
  
  				if (settings.controlClass) {
  					item.addClass(settings.controlClass);
  				}
  			});
  
  			items.eq(0).addClass(firstClass);
  			items.eq(-1).addClass(lastClass);
  		},
  
  		/**
  		 * Renders the specified container and any layout specific HTML.
  		 *
  		 * @method renderHtml
  		 * @param {maya.ui.Container} container Container to render HTML for.
  		 */
  		renderHtml: function(container) {
  			var self = this, settings = self.settings, items, html = '';
  
  			items = container.items();
  			items.eq(0).addClass(settings.firstControlClass);
  			items.eq(-1).addClass(settings.lastControlClass);
  
  			items.each(function(item) {
  				if (settings.controlClass) {
  					item.addClass(settings.controlClass);
  				}
  
  				html += item.renderHtml();
  			});
  
  			return html;
  		},
  
  		/**
  		 * Recalculates the positions of the controls in the specified container.
  		 *
  		 * @method recalc
  		 * @param {maya.ui.Container} container Container instance to recalc.
  		 */
  		recalc: function() {
  		},
  
  		/**
  		 * This method gets invoked after the layout renders the controls.
  		 *
  		 * @method postRender
  		 * @param {maya.ui.Container} container Container instance to postRender.
  		 */
  		postRender: function() {
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/AbsoluteLayout.js
  
  /**
   * AbsoluteLayout.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * LayoutManager for absolute positioning. This layout manager is more of
   * a base class for other layouts but can be created and used directly.
   *
   * @-x-less AbsoluteLayout.less
   * @class maya.ui.AbsoluteLayout
   * @extends maya.ui.Layout
   */
  define("maya/ui/AbsoluteLayout", [
  	"maya/ui/Layout"
  ], function(Layout) {
  	"use strict";
  
  	return Layout.extend({
  		Defaults: {
  			containerClass: 'abs-layout',
  			controlClass: 'abs-layout-item'
  		},
  
  		/**
  		 * Recalculates the positions of the controls in the specified container.
  		 *
  		 * @method recalc
  		 * @param {maya.ui.Container} container Container instance to recalc.
  		 */
  		recalc: function(container) {
  			container.items().filter(':visible').each(function(ctrl) {
  				var settings = ctrl.settings;
  
  				ctrl.layoutRect({
  					x: settings.x,
  					y: settings.y,
  					w: settings.w,
  					h: settings.h
  				});
  
  				if (ctrl.recalc) {
  					ctrl.recalc();
  				}
  			});
  		},
  
  		/**
  		 * Renders the specified container and any layout specific HTML.
  		 *
  		 * @method renderHtml
  		 * @param {maya.ui.Container} container Container to render HTML for.
  		 */
  		renderHtml: function(container) {
  			return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Tooltip.js
  
  /**
   * Tooltip.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a tooltip instance.
   *
   * @-x-less ToolTip.less
   * @class maya.ui.ToolTip
   * @extends maya.ui.Control
   * @mixes maya.ui.Movable
   */
  define("maya/ui/Tooltip", [
  	"maya/ui/Control",
  	"maya/ui/Movable"
  ], function(Control, Movable) {
  	return Control.extend({
  		Mixins: [Movable],
  
  		Defaults: {
  			classes: 'widget tooltip tooltip-n'
  		},
  
  		/**
  		 * Sets/gets the current label text.
  		 *
  		 * @method text
  		 * @param {String} [text] New label text.
  		 * @return {String|maya.ui.Tooltip} Current text or current label instance.
  		 */
  		text: function(value) {
  			var self = this;
  
  			if (typeof value != "undefined") {
  				self._value = value;
  
  				if (self._rendered) {
  					self.getEl().lastChild.innerHTML = self.encode(value);
  				}
  
  				return self;
  			}
  
  			return self._value;
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, prefix = self.classPrefix;
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' +
  					'<div class="' + prefix + 'tooltip-arrow"></div>' +
  					'<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Repaints the control after a layout operation.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			var self = this, style, rect;
  
  			style = self.getEl().style;
  			rect = self._layoutRect;
  
  			style.left = rect.x + 'px';
  			style.top = rect.y + 'px';
  			style.zIndex = 0xFFFF + 0xFFFF;
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Widget.js
  
  /**
   * Widget.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Widget base class a widget is a control that has a tooltip and some basic states.
   *
   * @class maya.ui.Widget
   * @extends maya.ui.Control
   */
  define("maya/ui/Widget", [
  	"maya/ui/Control",
  	"maya/ui/Tooltip"
  ], function(Control, Tooltip) {
  	"use strict";
  
  	var tooltip;
  
  	var Widget = Control.extend({
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {String} tooltip Tooltip text to display when hovering.
  		 * @setting {Boolean} autofocus True if the control should be focused when rendered.
  		 * @setting {String} text Text to display inside widget.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  			settings = self.settings;
  			self.canFocus = true;
  
  			if (settings.tooltip && Widget.tooltips !== false) {
  				self.on('mouseenter', function(e) {
  					var tooltip = self.tooltip().moveTo(-0xFFFF);
  
  					if (e.control == self) {
  						var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
  
  						tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
  						tooltip.toggleClass('tooltip-nw', rel == 'bc-tl');
  						tooltip.toggleClass('tooltip-ne', rel == 'bc-tr');
  
  						tooltip.moveRel(self.getEl(), rel);
  					} else {
  						tooltip.hide();
  					}
  				});
  
  				self.on('mouseleave mousedown click', function() {
  					self.tooltip().hide();
  				});
  			}
  
  			self.aria('label', settings.ariaLabel || settings.tooltip);
  		},
  
  		/**
  		 * Returns the current tooltip instance.
  		 *
  		 * @method tooltip
  		 * @return {maya.ui.Tooltip} Tooltip instance.
  		 */
  		tooltip: function() {
  			if (!tooltip) {
  				tooltip = new Tooltip({type: 'tooltip'});
  				tooltip.renderTo();
  			}
  
  			return tooltip;
  		},
  
  		/**
  		 * Sets/gets the active state of the widget.
  		 *
  		 * @method active
  		 * @param {Boolean} [state] State if the control is active.
  		 * @return {Boolean|maya.ui.Widget} True/false or current widget instance.
  		 */
  		active: function(state) {
  			var self = this, undef;
  
  			if (state !== undef) {
  				self.aria('pressed', state);
  				self.toggleClass('active', state);
  			}
  
  			return self._super(state);
  		},
  
  		/**
  		 * Sets/gets the disabled state of the widget.
  		 *
  		 * @method disabled
  		 * @param {Boolean} [state] State if the control is disabled.
  		 * @return {Boolean|maya.ui.Widget} True/false or current widget instance.
  		 */
  		disabled: function(state) {
  			var self = this, undef;
  
  			if (state !== undef) {
  				self.aria('disabled', state);
  				self.toggleClass('disabled', state);
  			}
  
  			return self._super(state);
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this, settings = self.settings;
  
  			self._rendered = true;
  
  			self._super();
  
  			if (!self.parent() && (settings.width || settings.height)) {
  				self.initLayoutRect();
  				self.repaint();
  			}
  
  			if (settings.autofocus) {
  				self.focus();
  			}
  		},
  
  		/**
  		 * Removes the current control from DOM and from UI collections.
  		 *
  		 * @method remove
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		remove: function() {
  			this._super();
  
  			if (tooltip) {
  				tooltip.remove();
  				tooltip = null;
  			}
  		}
  	});
  
  	return Widget;
  });
  
  // Included from: js/maya/classes/ui/Button.js
  
  /**
   * Button.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is used to create buttons. You can create them directly or through the Factory.
   *
   * @example
   * // Create and render a button to the body element
   * maya.ui.Factory.create({
   *     type: 'button',
   *     text: 'My button'
   * }).renderTo(document.body);
   *
   * @-x-less Button.less
   * @class maya.ui.Button
   * @extends maya.ui.Widget
   */
  define("maya/ui/Button", [
  	"maya/ui/Widget"
  ], function(Widget) {
  	"use strict";
  
  	return Widget.extend({
  		Defaults: {
  			classes: "widget btn",
  			role: "button"
  		},
  
  		/**
  		 * Constructs a new button instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {String} size Size of the button small|medium|large.
  		 * @setting {String} image Image to use for icon.
  		 * @setting {String} icon Icon to use for button.
  		 */
  		init: function(settings) {
  			var self = this, size;
  
  			self.on('click mousedown', function(e) {
  				e.preventDefault();
  			});
  
  			self._super(settings);
  			size = settings.size;
  
  			if (settings.subtype) {
  				self.addClass(settings.subtype);
  			}
  
  			if (size) {
  				self.addClass('btn-' + size);
  			}
  		},
  
  		/**
  		 * Sets/gets the current button icon.
  		 *
  		 * @method icon
  		 * @param {String} [icon] New icon identifier.
  		 * @return {String|maya.ui.MenuButton} Current icon or current MenuButton instance.
  		 */
  		icon: function(icon) {
  			var self = this, prefix = self.classPrefix;
  
  			if (typeof icon == 'undefined') {
  				return self.settings.icon;
  			}
  
  			self.settings.icon = icon;
  			icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  
  			if (self._rendered) {
  				var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
  
  				if (icon) {
  					if (!iconElm || iconElm != btnElm.firstChild) {
  						iconElm = document.createElement('i');
  						btnElm.insertBefore(iconElm, btnElm.firstChild);
  					}
  
  					iconElm.className = icon;
  				} else if (iconElm) {
  					btnElm.removeChild(iconElm);
  				}
  
  				self.text(self._text); // Set text again to fix whitespace between icon + text
  			}
  
  			return self;
  		},
  
  		/**
  		 * Repaints the button for example after it's been resizes by a layout engine.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			var btnStyle = this.getEl().firstChild.style;
  
  			btnStyle.width = btnStyle.height = "100%";
  
  			this._super();
  		},
  
  		/**
  		 * Sets/gets the current button text.
  		 *
  		 * @method text
  		 * @param {String} [text] New button text.
  		 * @return {String|maya.ui.Button} Current text or current Button instance.
  		 */
  		text: function(text) {
  			var self = this;
  
  			if (self._rendered) {
  				var textNode = self.getEl().lastChild.lastChild;
  				if (textNode) {
  					textNode.data = self.translate(text);
  				}
  			}
  
  			return self._super(text);
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, prefix = self.classPrefix;
  			var icon = self.settings.icon, image;
  
  			image = self.settings.image;
  			if (image) {
  				icon = 'none';
  
  				// Support for [high dpi, low dpi] image sources
  				if (typeof image != "string") {
  					image = window.getSelection ? image[0] : image[1];
  				}
  
  				image = ' style="background-image: url(\'' + image + '\')"';
  			} else {
  				image = '';
  			}
  
  			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
  					'<button role="presentation" type="button" tabindex="-1">' +
  						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  						(self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
  					'</button>' +
  				'</div>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/ButtonGroup.js
  
  /**
   * ButtonGroup.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This control enables you to put multiple buttons into a group. This is
   * useful when you want to combine similar toolbar buttons into a group.
   *
   * @example
   * // Create and render a buttongroup with two buttons to the body element
   * maya.ui.Factory.create({
   *     type: 'buttongroup',
   *     items: [
   *         {text: 'Button A'},
   *         {text: 'Button B'}
   *     ]
   * }).renderTo(document.body);
   *
   * @-x-less ButtonGroup.less
   * @class maya.ui.ButtonGroup
   * @extends maya.ui.Container
   */
  define("maya/ui/ButtonGroup", [
  	"maya/ui/Container"
  ], function(Container) {
  	"use strict";
  
  	return Container.extend({
  		Defaults: {
  			defaultType: 'button',
  			role: 'group'
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, layout = self._layout;
  
  			self.addClass('btn-group');
  			self.preRender();
  			layout.preRender(self);
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '">' +
  					'<div id="' + self._id + '-body">' +
  						(self.settings.html || '') + layout.renderHtml(self) +
  					'</div>' +
  				'</div>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Checkbox.js
  
  /**
   * Checkbox.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This control creates a custom checkbox.
   *
   * @example
   * // Create and render a checkbox to the body element
   * maya.ui.Factory.create({
   *     type: 'checkbox',
   *     checked: true,
   *     text: 'My checkbox'
   * }).renderTo(document.body);
   *
   * @-x-less Checkbox.less
   * @class maya.ui.Checkbox
   * @extends maya.ui.Widget
   */
  define("maya/ui/Checkbox", [
  	"maya/ui/Widget"
  ], function(Widget) {
  	"use strict";
  
  	return Widget.extend({
  		Defaults: {
  			classes: "checkbox",
  			role: "checkbox",
  			checked: false
  		},
  
  		/**
  		 * Constructs a new Checkbox instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {Boolean} checked True if the checkbox should be checked by default.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  
  			self.on('click mousedown', function(e) {
  				e.preventDefault();
  			});
  
  			self.on('click', function(e) {
  				e.preventDefault();
  
  				if (!self.disabled()) {
  					self.checked(!self.checked());
  				}
  			});
  
  			self.checked(self.settings.checked);
  		},
  
  		/**
  		 * Getter/setter function for the checked state.
  		 *
  		 * @method checked
  		 * @param {Boolean} [state] State to be set.
  		 * @return {Boolean|maya.ui.Checkbox} True/false or checkbox if it's a set operation.
  		 */
  		checked: function(state) {
  			var self = this;
  
  			if (typeof state != "undefined") {
  				if (state) {
  					self.addClass('checked');
  				} else {
  					self.removeClass('checked');
  				}
  
  				self._checked = state;
  				self.aria('checked', state);
  
  				return self;
  			}
  
  			return self._checked;
  		},
  
  		/**
  		 * Getter/setter function for the value state.
  		 *
  		 * @method value
  		 * @param {Boolean} [state] State to be set.
  		 * @return {Boolean|maya.ui.Checkbox} True/false or checkbox if it's a set operation.
  		 */
  		value: function(state) {
  			return this.checked(state);
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, prefix = self.classPrefix;
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
  					'<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
  					'<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
  				'</div>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/ComboBox.js
  
  /**
   * ComboBox.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class creates a combobox control. Select box that you select a value from or
   * type a value into.
   *
   * @-x-less ComboBox.less
   * @class maya.ui.ComboBox
   * @extends maya.ui.Widget
   */
  define("maya/ui/ComboBox", [
  	"maya/ui/Widget",
  	"maya/ui/Factory",
  	"maya/ui/DomUtils"
  ], function(Widget, Factory, DomUtils) {
  	"use strict";
  
  	return Widget.extend({
  		/**
  		 * Constructs a new control instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {String} placeholder Placeholder text to display.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  			self.addClass('combobox');
  			self.subinput = true;
  			self.ariaTarget = 'inp'; // TODO: Figure out a better way
  
  			settings = self.settings;
  			settings.menu = settings.menu || settings.values;
  
  			if (settings.menu) {
  				settings.icon = 'caret';
  			}
  
  			self.on('click', function(e) {
  				var elm = e.target, root = self.getEl();
  
  				while (elm && elm != root) {
  					if (elm.id && elm.id.indexOf('-open') != -1) {
  						self.fire('action');
  
  						if (settings.menu) {
  							self.showMenu();
  
  							if (e.aria) {
  								self.menu.items()[0].focus();
  							}
  						}
  					}
  
  					elm = elm.parentNode;
  				}
  			});
  
  			// TODO: Rework this
  			self.on('keydown', function(e) {
  				if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
  					self.parents().reverse().each(function(ctrl) {
  						e.preventDefault();
  						self.fire('change');
  
  						if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  							ctrl.fire('submit', {data: ctrl.toJSON()});
  							return false;
  						}
  					});
  				}
  			});
  
  			if (settings.placeholder) {
  				self.addClass('placeholder');
  
  				self.on('focusin', function() {
  					if (!self._hasOnChange) {
  						DomUtils.on(self.getEl('inp'), 'change', function() {
  							self.fire('change');
  						});
  
  						self._hasOnChange = true;
  					}
  
  					if (self.hasClass('placeholder')) {
  						self.getEl('inp').value = '';
  						self.removeClass('placeholder');
  					}
  				});
  
  				self.on('focusout', function() {
  					if (self.value().length === 0) {
  						self.getEl('inp').value = settings.placeholder;
  						self.addClass('placeholder');
  					}
  				});
  			}
  		},
  
  		showMenu: function() {
  			var self = this, settings = self.settings, menu;
  
  			if (!self.menu) {
  				menu = settings.menu || [];
  
  				// Is menu array then auto constuct menu control
  				if (menu.length) {
  					menu = {
  						type: 'menu',
  						items: menu
  					};
  				} else {
  					menu.type = menu.type || 'menu';
  				}
  
  				self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
  				self.fire('createmenu');
  				self.menu.reflow();
  				self.menu.on('cancel', function(e) {
  					if (e.control === self.menu) {
  						self.focus();
  					}
  				});
  
  				self.menu.on('show hide', function(e) {
  					e.control.items().each(function(ctrl) {
  						ctrl.active(ctrl.value() == self.value());
  					});
  				}).fire('show');
  
  				self.menu.on('select', function(e) {
  					self.value(e.control.value());
  				});
  
  				self.on('focusin', function(e) {
  					if (e.target.tagName.toUpperCase() == 'INPUT') {
  						self.menu.hide();
  					}
  				});
  
  				self.aria('expanded', true);
  			}
  
  			self.menu.show();
  			self.menu.layoutRect({w: self.layoutRect().w});
  			self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  		},
  
  		/**
  		 * Getter/setter function for the control value.
  		 *
  		 * @method value
  		 * @param {String} [value] Value to be set.
  		 * @return {String|maya.ui.ComboBox} Value or self if it's a set operation.
  		 */
  		value: function(value) {
  			var self = this;
  
  			if (typeof value != "undefined") {
  				self._value = value;
  				self.removeClass('placeholder');
  
  				if (self._rendered) {
  					self.getEl('inp').value = value;
  				}
  
  				return self;
  			}
  
  			if (self._rendered) {
  				value = self.getEl('inp').value;
  
  				if (value != self.settings.placeholder) {
  					return value;
  				}
  
  				return '';
  			}
  
  			return self._value;
  		},
  
  		/**
  		 * Getter/setter function for the disabled state.
  		 *
  		 * @method value
  		 * @param {Boolean} [state] State to be set.
  		 * @return {Boolean|maya.ui.ComboBox} True/false or self if it's a set operation.
  		 */
  		disabled: function(state) {
  			var self = this;
  
  			if (self._rendered && typeof state != 'undefined') {
  				self.getEl('inp').disabled = state;
  			}
  
  			return self._super(state);
  		},
  
  		/**
  		 * Focuses the input area of the control.
  		 *
  		 * @method focus
  		 */
  		focus: function() {
  			this.getEl('inp').focus();
  		},
  
  		/**
  		 * Repaints the control after a layout operation.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
  			var width, lineHeight;
  
  			if (openElm) {
  				width = rect.w - DomUtils.getSize(openElm).width - 10;
  			} else {
  				width = rect.w - 10;
  			}
  
  			// Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  			var doc = document;
  			if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  				lineHeight = (self.layoutRect().h - 2) + 'px';
  			}
  
  			DomUtils.css(elm.firstChild, {
  				width: width,
  				lineHeight: lineHeight
  			});
  
  			self._super();
  
  			return self;
  		},
  
  		/**
  		 * Post render method. Called after the control has been rendered to the target.
  		 *
  		 * @method postRender
  		 * @return {maya.ui.ComboBox} Current combobox instance.
  		 */
  		postRender: function() {
  			var self = this;
  
  			DomUtils.on(this.getEl('inp'), 'change', function() {
  				self.fire('change');
  			});
  
  			return self._super();
  		},
  
  		remove: function() {
  			DomUtils.off(this.getEl('inp'));
  			this._super();
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
  			var value = settings.value || settings.placeholder || '';
  			var icon, text, openBtnHtml = '', extraAttrs = '';
  
  			if ("spellcheck" in settings) {
  				extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  			}
  
  			if (settings.maxLength) {
  				extraAttrs += ' maxlength="' + settings.maxLength + '"';
  			}
  
  			if (settings.size) {
  				extraAttrs += ' size="' + settings.size + '"';
  			}
  
  			if (settings.subtype) {
  				extraAttrs += ' type="' + settings.subtype + '"';
  			}
  
  			if (self.disabled()) {
  				extraAttrs += ' disabled="disabled"';
  			}
  
  			icon = settings.icon;
  			if (icon && icon != 'caret') {
  				icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
  			}
  
  			text = self._text;
  
  			if (icon || text) {
  				openBtnHtml = (
  					'<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
  						'<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
  							(icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
  							(text ? (icon ? ' ' : '') + text : '') +
  						'</button>' +
  					'</div>'
  				);
  
  				self.addClass('has-open');
  			}
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '">' +
  					'<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
  					value + '" hidefocus="1"' + extraAttrs + ' />' +
  					openBtnHtml +
  				'</div>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/ColorBox.js
  
  /**
   * ColorBox.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This widget lets you enter colors and browse for colors by pressing the color button. It also displays
   * a preview of the current color.
   *
   * @-x-less ColorBox.less
   * @class maya.ui.ColorBox
   * @extends maya.ui.ComboBox
   */
  define("maya/ui/ColorBox", [
  	"maya/ui/ComboBox"
  ], function(ComboBox) {
  	"use strict";
  
  	return ComboBox.extend({
  		/**
  		 * Constructs a new control instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			settings.spellcheck = false;
  
  			if (settings.onaction) {
  				settings.icon = 'none';
  			}
  
  			self._super(settings);
  
  			self.addClass('colorbox');
  			self.on('change keyup postrender', function() {
  				self.repaintColor(self.value());
  			});
  		},
  
  		repaintColor: function(value) {
  			var elm = this.getEl().getElementsByTagName('i')[0];
  
  			if (elm) {
  				try {
  					elm.style.background = value;
  				} catch (ex) {
  					// Ignore
  				}
  			}
  		},
  
  		value: function(value) {
  			var self = this;
  
  			if (typeof value != "undefined") {
  				if (self._rendered) {
  					self.repaintColor(value);
  				}
  			}
  
  			return self._super(value);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/PanelButton.js
  
  /**
   * PanelButton.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new panel button.
   *
   * @class maya.ui.PanelButton
   * @extends maya.ui.Button
   */
  define("maya/ui/PanelButton", [
  	"maya/ui/Button",
  	"maya/ui/FloatPanel"
  ], function(Button, FloatPanel) {
  	"use strict";
  
  	return Button.extend({
  		/**
  		 * Shows the panel for the button.
  		 *
  		 * @method showPanel
  		 */
  		showPanel: function() {
  			var self = this, settings = self.settings;
  
  			self.active(true);
  
  			if (!self.panel) {
  				var panelSettings = settings.panel;
  
  				// Wrap panel in grid layout if type if specified
  				// This makes it possible to add forms or other containers directly in the panel option
  				if (panelSettings.type) {
  					panelSettings = {
  						layout: 'grid',
  						items: panelSettings
  					};
  				}
  
  				panelSettings.role = panelSettings.role || 'dialog';
  				panelSettings.popover = true;
  				panelSettings.autohide = true;
  				panelSettings.ariaRoot = true;
  
  				self.panel = new FloatPanel(panelSettings).on('hide', function() {
  					self.active(false);
  				}).on('cancel', function(e) {
  					e.stopPropagation();
  					self.focus();
  					self.hidePanel();
  				}).parent(self).renderTo(self.getContainerElm());
  
  				self.panel.fire('show');
  				self.panel.reflow();
  			} else {
  				self.panel.show();
  			}
  
  			self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
  		},
  
  		/**
  		 * Hides the panel for the button.
  		 *
  		 * @method hidePanel
  		 */
  		hidePanel: function() {
  			var self = this;
  
  			if (self.panel) {
  				self.panel.hide();
  			}
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this;
  
  			self.aria('haspopup', true);
  
  			self.on('click', function(e) {
  				if (e.control === self) {
  					if (self.panel && self.panel.visible()) {
  						self.hidePanel();
  					} else {
  						self.showPanel();
  						self.panel.focus(!!e.aria);
  					}
  				}
  			});
  
  			return self._super();
  		},
  
  		remove: function() {
  			if (this.panel) {
  				this.panel.remove();
  				this.panel = null;
  			}
  
  			return this._super();
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/ColorButton.js
  
  /**
   * ColorButton.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class creates a color button control. This is a split button in which the main
   * button has a visual representation of the currently selected color. When clicked
   * the caret button displays a color picker, allowing the user to select a new color.
   *
   * @-x-less ColorButton.less
   * @class maya.ui.ColorButton
   * @extends maya.ui.PanelButton
   */
  define("maya/ui/ColorButton", [
  	"maya/ui/PanelButton",
  	"maya/dom/DOMUtils"
  ], function(PanelButton, DomUtils) {
  	"use strict";
  
  	var DOM = DomUtils.DOM;
  
  	return PanelButton.extend({
  		/**
  		 * Constructs a new ColorButton instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			this._super(settings);
  			this.addClass('colorbutton');
  		},
  
  		/**
  		 * Getter/setter for the current color.
  		 *
  		 * @method color
  		 * @param {String} [color] Color to set.
  		 * @return {String|maya.ui.ColorButton} Current color or current instance.
  		 */
  		color: function(color) {
  			if (color) {
  				this._color = color;
  				this.getEl('preview').style.backgroundColor = color;
  				return this;
  			}
  
  			return this._color;
  		},
  
  		/**
  		 * Resets the current color.
  		 *
  		 * @method resetColor
  		 * @return {maya.ui.ColorButton} Current instance.
  		 */
  		resetColor: function() {
  			this._color = null;
  			this.getEl('preview').style.backgroundColor = null;
  			return this;
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, prefix = self.classPrefix;
  			var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  			var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
  					'<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
  						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  						'<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
  						(self._text ? (icon ? ' ' : '') + (self._text) : '') +
  					'</button>' +
  					'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  						' <i class="' + prefix + 'caret"></i>' +
  					'</button>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this, onClickHandler = self.settings.onclick;
  
  			self.on('click', function(e) {
  				if (e.aria && e.aria.key == 'down') {
  					return;
  				}
  
  				if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
  					e.stopImmediatePropagation();
  					onClickHandler.call(self, e);
  				}
  			});
  
  			delete self.settings.onclick;
  
  			return self._super();
  		}
  	});
  });
  
  // Included from: js/maya/classes/util/Color.js
  
  /**
   * Color.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class lets you parse/serialize colors and convert rgb/hsb.
   *
   * @class maya.util.Color
   * @example
   * var white = new maya.util.Color({r: 255, g: 255, b: 255});
   * var red = new maya.util.Color('#FF0000');
   *
   * console.log(white.toHex(), red.toHsv());
   */
  define("maya/util/Color", [], function() {
  	var min = Math.min, max = Math.max, round = Math.round;
  
  	/**
  	 * Constructs a new color instance.
  	 *
  	 * @constructor
  	 * @method Color
  	 * @param {String} value Optional initial value to parse.
  	 */
  	function Color(value) {
  		var self = this, r = 0, g = 0, b = 0;
  
  		function rgb2hsv(r, g, b) {
  			var h, s, v, d, minRGB, maxRGB;
  
  			h = 0;
  			s = 0;
  			v = 0;
  			r = r / 255;
  			g = g / 255;
  			b = b / 255;
  
  			minRGB = min(r, min(g, b));
  			maxRGB = max(r, max(g, b));
  
  			if (minRGB == maxRGB) {
  				v = minRGB;
  
  				return {
  					h: 0,
  					s: 0,
  					v: v * 100
  				};
  			}
  
  			/*eslint no-nested-ternary:0 */
  			d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r);
  			h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5);
  			h = 60 * (h - d / (maxRGB - minRGB));
  			s = (maxRGB - minRGB) / maxRGB;
  			v = maxRGB;
  
  			return {
  				h: round(h),
  				s: round(s * 100),
  				v: round(v * 100)
  			};
  		}
  
  		function hsvToRgb(hue, saturation, brightness) {
  			var side, chroma, x, match;
  
  			hue = (parseInt(hue, 10) || 0) % 360;
  			saturation = parseInt(saturation, 10) / 100;
  			brightness = parseInt(brightness, 10) / 100;
  			saturation = max(0, min(saturation, 1));
  			brightness = max(0, min(brightness, 1));
  
  			if (saturation === 0) {
  				r = g = b = round(255 * brightness);
  				return;
  			}
  
  			side = hue / 60;
  			chroma = brightness * saturation;
  			x = chroma * (1 - Math.abs(side % 2 - 1));
  			match = brightness - chroma;
  
  			switch (Math.floor(side)) {
  				case 0:
  					r = chroma;
  					g = x;
  					b = 0;
  					break;
  
  				case 1:
  					r = x;
  					g = chroma;
  					b = 0;
  					break;
  
  				case 2:
  					r = 0;
  					g = chroma;
  					b = x;
  					break;
  
  				case 3:
  					r = 0;
  					g = x;
  					b = chroma;
  					break;
  
  				case 4:
  					r = x;
  					g = 0;
  					b = chroma;
  					break;
  
  				case 5:
  					r = chroma;
  					g = 0;
  					b = x;
  					break;
  
  				default:
  					r = g = b = 0;
  			}
  
  			r = round(255 * (r + match));
  			g = round(255 * (g + match));
  			b = round(255 * (b + match));
  		}
  
  		/**
  		 * Returns the hex string of the current color. For example: #ff00ff
  		 *
  		 * @method toHex
  		 * @return {String} Hex string of current color.
  		 */
  		function toHex() {
  			function hex(val) {
  				val = parseInt(val, 10).toString(16);
  
  				return val.length > 1 ? val : '0' + val;
  			}
  
  			return '#' + hex(r) + hex(g) + hex(b);
  		}
  
  		/**
  		 * Returns the r, g, b values of the color. Each channel has a range from 0-255.
  		 *
  		 * @method toRgb
  		 * @return {Object} Object with r, g, b fields.
  		 */
  		function toRgb() {
  			return {
  				r: r,
  				g: g,
  				b: b
  			};
  		}
  
  		/**
  		 * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100.
  		 *
  		 * @method toHsv
  		 * @return {Object} Object with h, s, v fields.
  		 */
  		function toHsv() {
  			return rgb2hsv(r, g, b);
  		}
  
  		/**
  		 * Parses the specified value and populates the color instance.
  		 *
  		 * Supported format examples:
  		 *  * rbg(255,0,0)
  		 *  * #ff0000
  		 *  * #fff
  		 *  * {r: 255, g: 0, b: 0}
  		 *  * {h: 360, s: 100, v: 100}
  		 *
  		 * @method parse
  		 * @param {Object/String} value Color value to parse.
  		 * @return {maya.util.Color} Current color instance.
  		 */
  		function parse(value) {
  			var matches;
  
  			if (typeof value == 'object') {
  				if ("r" in value) {
  					r = value.r;
  					g = value.g;
  					b = value.b;
  				} else if ("v" in value) {
  					hsvToRgb(value.h, value.s, value.v);
  				}
  			} else {
  				if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) {
  					r = parseInt(matches[1], 10);
  					g = parseInt(matches[2], 10);
  					b = parseInt(matches[3], 10);
  				} else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) {
  					r = parseInt(matches[1], 16);
  					g = parseInt(matches[2], 16);
  					b = parseInt(matches[3], 16);
  				} else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) {
  					r = parseInt(matches[1] + matches[1], 16);
  					g = parseInt(matches[2] + matches[2], 16);
  					b = parseInt(matches[3] + matches[3], 16);
  				}
  			}
  
  			r = r < 0 ? 0 : (r > 255 ? 255 : r);
  			g = g < 0 ? 0 : (g > 255 ? 255 : g);
  			b = b < 0 ? 0 : (b > 255 ? 255 : b);
  
  			return self;
  		}
  
  		if (value) {
  			parse(value);
  		}
  
  		self.toRgb = toRgb;
  		self.toHsv = toHsv;
  		self.toHex = toHex;
  		self.parse = parse;
  	}
  
  	return Color;
  });
  
  // Included from: js/maya/classes/ui/ColorPicker.js
  
  /**
   * ColorPicker.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Color picker widget lets you select colors.
   *
   * @-x-less ColorPicker.less
   * @class maya.ui.ColorPicker
   * @extends maya.ui.Widget
   */
  define("maya/ui/ColorPicker", [
  	"maya/ui/Widget",
  	"maya/ui/DragHelper",
  	"maya/ui/DomUtils",
  	"maya/util/Color"
  ], function(Widget, DragHelper, DomUtils, Color) {
  	"use strict";
  
  	return Widget.extend({
  		Defaults: {
  			classes: "widget colorpicker"
  		},
  
  		/**
  		 * Constructs a new colorpicker instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {String} color Initial color value.
  		 */
  		init: function(settings) {
  			this._super(settings);
  		},
  
  		postRender: function() {
  			var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm;
  
  			hueRootElm = self.getEl('h');
  			huePointElm = self.getEl('hp');
  			svRootElm = self.getEl('sv');
  			svPointElm = self.getEl('svp');
  
  			function getPos(elm, event) {
  				var pos = DomUtils.getPos(elm), x, y;
  
  				x = event.pageX - pos.x;
  				y = event.pageY - pos.y;
  
  				x = Math.max(0, Math.min(x / elm.clientWidth, 1));
  				y = Math.max(0, Math.min(y / elm.clientHeight, 1));
  
  				return {
  					x: x,
  					y: y
  				};
  			}
  
  			function updateColor(hsv, hueUpdate) {
  				var hue = (360 - hsv.h) / 360;
  
  				DomUtils.css(huePointElm, {
  					top: (hue * 100) + '%'
  				});
  
  				if (!hueUpdate) {
  					DomUtils.css(svPointElm, {
  						left: hsv.s + '%',
  						top: (100 - hsv.v) + '%'
  					});
  				}
  
  				svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex();
  				self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h});
  			}
  
  			function updateSaturationAndValue(e) {
  				var pos;
  
  				pos = getPos(svRootElm, e);
  				hsv.s = pos.x * 100;
  				hsv.v = (1 - pos.y) * 100;
  
  				updateColor(hsv);
  				self.fire('change');
  			}
  
  			function updateHue(e) {
  				var pos;
  
  				pos = getPos(hueRootElm, e);
  				hsv = color.toHsv();
  				hsv.h = (1 - pos.y) * 360;
  				updateColor(hsv, true);
  				self.fire('change');
  			}
  
  			self._repaint = function() {
  				hsv = color.toHsv();
  				updateColor(hsv);
  			};
  
  			self._super();
  
  			self._svdraghelper = new DragHelper(self._id + '-sv', {
  				start: updateSaturationAndValue,
  				drag: updateSaturationAndValue
  			});
  
  			self._hdraghelper = new DragHelper(self._id + '-h', {
  				start: updateHue,
  				drag: updateHue
  			});
  
  			self._repaint();
  		},
  
  		rgb: function() {
  			return this.color().toRgb();
  		},
  
  		value: function(value) {
  			var self = this;
  
  			if (arguments.length) {
  				self.color().parse(value);
  
  				if (self._rendered) {
  					self._repaint();
  				}
  			} else {
  				return self.color().toHex();
  			}
  		},
  
  		color: function() {
  			if (!this._color) {
  				this._color = new Color();
  			}
  
  			return this._color;
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, prefix = self.classPrefix, hueHtml;
  			var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000';
  
  			function getOldIeFallbackHtml() {
  				var i, l, html = '', gradientPrefix, stopsList;
  
  				gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=';
  				stopsList = stops.split(',');
  				for (i = 0, l = stopsList.length - 1; i < l; i++) {
  					html += (
  						'<div class="' + prefix + 'colorpicker-h-chunk" style="' +
  							'height:' + (100 / l) + '%;' +
  							gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' +
  							'-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' +
  						'"></div>'
  					);
  				}
  
  				return html;
  			}
  
  			var gradientCssText = (
  				'background: -ms-linear-gradient(top,' + stops + ');' +
  				'background: linear-gradient(to bottom,' + stops + ');'
  			);
  
  			hueHtml = (
  				'<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' +
  					getOldIeFallbackHtml() +
  					'<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' +
  				'</div>'
  			);
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '">' +
  					'<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' +
  						'<div class="' + prefix + 'colorpicker-overlay1">' +
  							'<div class="' + prefix + 'colorpicker-overlay2">' +
  								'<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' +
  									'<div class="' + prefix + 'colorpicker-selector2"></div>' +
  								'</div>' +
  							'</div>' +
  						'</div>' +
  					'</div>' +
  					hueHtml +
  				'</div>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Path.js
  
  /**
   * Path.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new path control.
   *
   * @-x-less Path.less
   * @class maya.ui.Path
   * @extends maya.ui.Widget
   */
  define("maya/ui/Path", [
  	"maya/ui/Widget"
  ], function(Widget) {
  	"use strict";
  
  	return Widget.extend({
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {String} delimiter Delimiter to display between items in path.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			if (!settings.delimiter) {
  				settings.delimiter = '\u00BB';
  			}
  
  			self._super(settings);
  			self.addClass('path');
  			self.canFocus = true;
  
  			self.on('click', function(e) {
  				var index, target = e.target;
  
  				if ((index = target.getAttribute('data-index'))) {
  					self.fire('select', {value: self.data()[index], index: index});
  				}
  			});
  		},
  
  		/**
  		 * Focuses the current control.
  		 *
  		 * @method focus
  		 * @return {maya.ui.Control} Current control instance.
  		 */
  		focus: function() {
  			var self = this;
  
  			self.getEl().firstChild.focus();
  
  			return self;
  		},
  
  		/**
  		 * Sets/gets the data to be used for the path.
  		 *
  		 * @method data
  		 * @param {Array} data Array with items name is rendered to path.
  		 */
  		data: function(data) {
  			var self = this;
  
  			if (typeof data !== "undefined") {
  				self._data = data;
  				self.update();
  
  				return self;
  			}
  
  			return self._data;
  		},
  
  		/**
  		 * Updated the path.
  		 *
  		 * @private
  		 */
  		update: function() {
  			this.innerHtml(this._getPathHtml());
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this;
  
  			self._super();
  
  			self.data(self.settings.data);
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this;
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '">' +
  					self._getPathHtml() +
  				'</div>'
  			);
  		},
  
  		_getPathHtml: function() {
  			var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix;
  
  			for (i = 0, l = parts.length; i < l; i++) {
  				html += (
  					(i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
  					'<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
  					i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
  				);
  			}
  
  			if (!html) {
  				html = '<div class="' + prefix + 'path-item">\u00a0</div>';
  			}
  
  			return html;
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/ElementPath.js
  
  /**
   * ElementPath.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This control creates an path for the current selections parent elements in maya.
   *
   * @class maya.ui.ElementPath
   * @extends maya.ui.Path
   */
  define("maya/ui/ElementPath", [
  	"maya/ui/Path",
  	"maya/EditorManager"
  ], function(Path, EditorManager) {
  	return Path.extend({
  		/**
  		 * Post render method. Called after the control has been rendered to the target.
  		 *
  		 * @method postRender
  		 * @return {maya.ui.ElementPath} Current combobox instance.
  		 */
  		postRender: function() {
  			var self = this, editor = EditorManager.activeEditor;
  
  			function isHidden(elm) {
  				if (elm.nodeType === 1) {
  					if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
  						return true;
  					}
  
  					if (elm.getAttribute('data-mce-type') === 'bookmark') {
  						return true;
  					}
  				}
  
  				return false;
  			}
  
  			if (editor.settings.elementpath !== false) {
  				self.on('select', function(e) {
  					editor.focus();
  					editor.selection.select(this.data()[e.index].element);
  					editor.nodeChanged();
  				});
  
  				editor.on('nodeChange', function(e) {
  					var outParents = [], parents = e.parents, i = parents.length;
  
  					while (i--) {
  						if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
  							var args = editor.fire('ResolveName', {
  								name: parents[i].nodeName.toLowerCase(),
  								target: parents[i]
  							});
  
  							if (!args.isDefaultPrevented()) {
  								outParents.push({name: args.name, element: parents[i]});
  							}
  
  							if (args.isPropagationStopped()) {
  								break;
  							}
  						}
  					}
  
  					self.data(outParents);
  				});
  			}
  
  			return self._super();
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/FormItem.js
  
  /**
   * FormItem.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class is a container created by the form element with
   * a label and control item.
   *
   * @class maya.ui.FormItem
   * @extends maya.ui.Container
   * @setting {String} label Label to display for the form item.
   */
  define("maya/ui/FormItem", [
  	"maya/ui/Container"
  ], function(Container) {
  	"use strict";
  
  	return Container.extend({
  		Defaults: {
  			layout: 'flex',
  			align: 'center',
  			defaults: {
  				flex: 1
  			}
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, layout = self._layout, prefix = self.classPrefix;
  
  			self.addClass('formitem');
  			layout.preRender(self);
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  					(self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
  						self.settings.title + '</div>') : '') +
  					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  						(self.settings.html || '') + layout.renderHtml(self) +
  					'</div>' +
  				'</div>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Form.js
  
  /**
   * Form.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class creates a form container. A form container has the ability
   * to automatically wrap items in maya.ui.FormItem instances.
   *
   * Each FormItem instance is a container for the label and the item.
   *
   * @example
   * maya.ui.Factory.create({
   *     type: 'form',
   *     items: [
   *         {type: 'textbox', label: 'My text box'}
   *     ]
   * }).renderTo(document.body);
   *
   * @class maya.ui.Form
   * @extends maya.ui.Container
   */
  define("maya/ui/Form", [
  	"maya/ui/Container",
  	"maya/ui/FormItem",
  	"maya/util/Tools"
  ], function(Container, FormItem, Tools) {
  	"use strict";
  
  	return Container.extend({
  		Defaults: {
  			containerCls: 'form',
  			layout: 'flex',
  			direction: 'column',
  			align: 'stretch',
  			flex: 1,
  			padding: 20,
  			labelGap: 30,
  			spacing: 10,
  			callbacks: {
  				submit: function() {
  					this.submit();
  				}
  			}
  		},
  
  		/**
  		 * This method gets invoked before the control is rendered.
  		 *
  		 * @method preRender
  		 */
  		preRender: function() {
  			var self = this, items = self.items();
  
  			if (!self.settings.formItemDefaults) {
  				self.settings.formItemDefaults = {
  					layout: 'flex',
  					autoResize: "overflow",
  					defaults: {flex: 1}
  				};
  			}
  
  			// Wrap any labeled items in FormItems
  			items.each(function(ctrl) {
  				var formItem, label = ctrl.settings.label;
  
  				if (label) {
  					formItem = new FormItem(Tools.extend({
  						items: {
  							type: 'label',
  							id: ctrl._id + '-l',
  							text: label,
  							flex: 0,
  							forId: ctrl._id,
  							disabled: ctrl.disabled()
  						}
  					}, self.settings.formItemDefaults));
  
  					formItem.type = 'formitem';
  					ctrl.aria('labelledby', ctrl._id + '-l');
  
  					if (typeof ctrl.settings.flex == "undefined") {
  						ctrl.settings.flex = 1;
  					}
  
  					self.replace(ctrl, formItem);
  					formItem.add(ctrl);
  				}
  			});
  		},
  
  		/**
  		 * Recalcs label widths.
  		 *
  		 * @private
  		 */
  		recalcLabels: function() {
  			var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items;
  
  			if (self.settings.labelGapCalc === false) {
  				return;
  			}
  
  			if (self.settings.labelGapCalc == "children") {
  				items = self.find('formitem');
  			} else {
  				items = self.items();
  			}
  
  			items.filter('formitem').each(function(item) {
  				var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
  
  				maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
  				labels.push(labelCtrl);
  			});
  
  			labelGap = self.settings.labelGap || 0;
  
  			i = labels.length;
  			while (i--) {
  				labels[i].settings.minWidth = maxLabelWidth + labelGap;
  			}
  		},
  
  		/**
  		 * Getter/setter for the visibility state.
  		 *
  		 * @method visible
  		 * @param {Boolean} [state] True/false state to show/hide.
  		 * @return {maya.ui.Form|Boolean} True/false state or current control.
  		 */
  		visible: function(state) {
  			var val = this._super(state);
  
  			if (state === true && this._rendered) {
  				this.recalcLabels();
  			}
  
  			return val;
  		},
  
  		/**
  		 * Fires a submit event with the serialized form.
  		 *
  		 * @method submit
  		 * @return {Object} Event arguments object.
  		 */
  		submit: function() {
  			return this.fire('submit', {data: this.toJSON()});
  		},
  
  		/**
  		 * Post render method. Called after the control has been rendered to the target.
  		 *
  		 * @method postRender
  		 * @return {maya.ui.ComboBox} Current combobox instance.
  		 */
  		postRender: function() {
  			var self = this;
  
  			self._super();
  			self.recalcLabels();
  			self.fromJSON(self.settings.data);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/FieldSet.js
  
  /**
   * FieldSet.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class creates fieldset containers.
   *
   * @-x-less FieldSet.less
   * @class maya.ui.FieldSet
   * @extends maya.ui.Form
   */
  define("maya/ui/FieldSet", [
  	"maya/ui/Form"
  ], function(Form) {
  	"use strict";
  
  	return Form.extend({
  		Defaults: {
  			containerCls: 'fieldset',
  			layout: 'flex',
  			direction: 'column',
  			align: 'stretch',
  			flex: 1,
  			padding: "25 15 5 15",
  			labelGap: 30,
  			spacing: 10,
  			border: 1
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, layout = self._layout, prefix = self.classPrefix;
  
  			self.preRender();
  			layout.preRender(self);
  
  			return (
  				'<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  					(self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
  						self.settings.title + '</legend>') : '') +
  					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  						(self.settings.html || '') + layout.renderHtml(self) +
  					'</div>' +
  				'</fieldset>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/FilePicker.js
  
  /**
   * FilePicker.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*global maya:true */
  
  /**
   * This class creates a file picker control.
   *
   * @class maya.ui.FilePicker
   * @extends maya.ui.ComboBox
   */
  define("maya/ui/FilePicker", [
  	"maya/ui/ComboBox",
  	"maya/util/Tools"
  ], function(ComboBox, Tools) {
  	"use strict";
  
  	return ComboBox.extend({
  		/**
  		 * Constructs a new control instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			var self = this, editor = maya.activeEditor, editorSettings = editor.settings;
  			var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;
  
  			settings.spellcheck = false;
  
  			fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
  			if (fileBrowserCallbackTypes) {
  				fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
  			}
  
  			if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) {
  				fileBrowserCallback = editorSettings.file_picker_callback;
  				if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
  					actionCallback = function() {
  						var meta = self.fire('beforecall').meta;
  
  						meta = Tools.extend({filetype: settings.filetype}, meta);
  
  						// file_picker_callback(callback, currentValue, metaData)
  						fileBrowserCallback.call(
  							editor,
  							function(value, meta) {
  								self.value(value).fire('change', {meta: meta});
  							},
  							self.value(),
  							meta
  						);
  					};
  				} else {
  					// Legacy callback: file_picker_callback(id, currentValue, filetype, window)
  					fileBrowserCallback = editorSettings.file_browser_callback;
  					if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
  						actionCallback = function() {
  							fileBrowserCallback(
  								self.getEl('inp').id,
  								self.value(),
  								settings.filetype,
  								window
  							);
  						};
  					}
  				}
  			}
  
  			if (actionCallback) {
  				settings.icon = 'browse';
  				settings.onaction = actionCallback;
  			}
  
  			self._super(settings);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/FitLayout.js
  
  /**
   * FitLayout.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This layout manager will resize the control to be the size of it's parent container.
   * In other words width: 100% and height: 100%.
   *
   * @-x-less FitLayout.less
   * @class maya.ui.FitLayout
   * @extends maya.ui.AbsoluteLayout
   */
  define("maya/ui/FitLayout", [
  	"maya/ui/AbsoluteLayout"
  ], function(AbsoluteLayout) {
  	"use strict";
  
  	return AbsoluteLayout.extend({
  		/**
  		 * Recalculates the positions of the controls in the specified container.
  		 *
  		 * @method recalc
  		 * @param {maya.ui.Container} container Container instance to recalc.
  		 */
  		recalc: function(container) {
  			var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox();
  
  			container.items().filter(':visible').each(function(ctrl) {
  				ctrl.layoutRect({
  					x: paddingBox.left,
  					y: paddingBox.top,
  					w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
  					h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
  				});
  
  				if (ctrl.recalc) {
  					ctrl.recalc();
  				}
  			});
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/FlexLayout.js
  
  /**
   * FlexLayout.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This layout manager works similar to the CSS flex box.
   *
   * @setting {String} direction row|row-reverse|column|column-reverse
   * @setting {Number} flex A positive-number to flex by.
   * @setting {String} align start|end|center|stretch
   * @setting {String} pack start|end|justify
   *
   * @class maya.ui.FlexLayout
   * @extends maya.ui.AbsoluteLayout
   */
  define("maya/ui/FlexLayout", [
  	"maya/ui/AbsoluteLayout"
  ], function(AbsoluteLayout) {
  	"use strict";
  
  	return AbsoluteLayout.extend({
  		/**
  		 * Recalculates the positions of the controls in the specified container.
  		 *
  		 * @method recalc
  		 * @param {maya.ui.Container} container Container instance to recalc.
  		 */
  		recalc: function(container) {
  			// A ton of variables, needs to be in the same scope for performance
  			var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
  			var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
  			var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
  			var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
  			var alignDeltaSizeName, alignContentSizeName;
  			var max = Math.max, min = Math.min;
  
  			// Get container items, properties and settings
  			items = container.items().filter(':visible');
  			contLayoutRect = container.layoutRect();
  			contPaddingBox = container._paddingBox;
  			contSettings = container.settings;
  			direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
  			align = contSettings.align;
  			pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
  			spacing = contSettings.spacing || 0;
  
  			if (direction == "row-reversed" || direction == "column-reverse") {
  				items = items.set(items.toArray().reverse());
  				direction = direction.split('-')[0];
  			}
  
  			// Setup axis variable name for row/column direction since the calculations is the same
  			if (direction == "column") {
  				posName = "y";
  				sizeName = "h";
  				minSizeName = "minH";
  				maxSizeName = "maxH";
  				innerSizeName = "innerH";
  				beforeName = 'top';
  				deltaSizeName = "deltaH";
  				contentSizeName = "contentH";
  
  				alignBeforeName = "left";
  				alignSizeName = "w";
  				alignAxisName = "x";
  				alignInnerSizeName = "innerW";
  				alignMinSizeName = "minW";
  				alignAfterName = "right";
  				alignDeltaSizeName = "deltaW";
  				alignContentSizeName = "contentW";
  			} else {
  				posName = "x";
  				sizeName = "w";
  				minSizeName = "minW";
  				maxSizeName = "maxW";
  				innerSizeName = "innerW";
  				beforeName = 'left';
  				deltaSizeName = "deltaW";
  				contentSizeName = "contentW";
  
  				alignBeforeName = "top";
  				alignSizeName = "h";
  				alignAxisName = "y";
  				alignInnerSizeName = "innerH";
  				alignMinSizeName = "minH";
  				alignAfterName = "bottom";
  				alignDeltaSizeName = "deltaH";
  				alignContentSizeName = "contentH";
  			}
  
  			// Figure out total flex, availableSpace and collect any max size elements
  			availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
  			maxAlignEndPos = totalFlex = 0;
  			for (i = 0, l = items.length; i < l; i++) {
  				ctrl = items[i];
  				ctrlLayoutRect = ctrl.layoutRect();
  				ctrlSettings = ctrl.settings;
  				flex = ctrlSettings.flex;
  				availableSpace -= (i < l - 1 ? spacing : 0);
  
  				if (flex > 0) {
  					totalFlex += flex;
  
  					// Flexed item has a max size then we need to check if we will hit that size
  					if (ctrlLayoutRect[maxSizeName]) {
  						maxSizeItems.push(ctrl);
  					}
  
  					ctrlLayoutRect.flex = flex;
  				}
  
  				availableSpace -= ctrlLayoutRect[minSizeName];
  
  				// Calculate the align end position to be used to check for overflow/underflow
  				size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
  				if (size > maxAlignEndPos) {
  					maxAlignEndPos = size;
  				}
  			}
  
  			// Calculate minW/minH
  			rect = {};
  			if (availableSpace < 0) {
  				rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  			} else {
  				rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  			}
  
  			rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
  
  			rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
  			rect[alignContentSizeName] = maxAlignEndPos;
  			rect.minW = min(rect.minW, contLayoutRect.maxW);
  			rect.minH = min(rect.minH, contLayoutRect.maxH);
  			rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
  			rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
  
  			// Resize container container if minSize was changed
  			if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  				rect.w = rect.minW;
  				rect.h = rect.minH;
  
  				container.layoutRect(rect);
  				this.recalc(container);
  
  				// Forced recalc for example if items are hidden/shown
  				if (container._lastRect === null) {
  					var parentCtrl = container.parent();
  					if (parentCtrl) {
  						parentCtrl._lastRect = null;
  						parentCtrl.recalc();
  					}
  				}
  
  				return;
  			}
  
  			// Handle max size elements, check if they will become to wide with current options
  			ratio = availableSpace / totalFlex;
  			for (i = 0, l = maxSizeItems.length; i < l; i++) {
  				ctrl = maxSizeItems[i];
  				ctrlLayoutRect = ctrl.layoutRect();
  				maxSize = ctrlLayoutRect[maxSizeName];
  				size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
  
  				if (size > maxSize) {
  					availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
  					totalFlex -= ctrlLayoutRect.flex;
  					ctrlLayoutRect.flex = 0;
  					ctrlLayoutRect.maxFlexSize = maxSize;
  				} else {
  					ctrlLayoutRect.maxFlexSize = 0;
  				}
  			}
  
  			// Setup new ratio, target layout rect, start position
  			ratio = availableSpace / totalFlex;
  			pos = contPaddingBox[beforeName];
  			rect = {};
  
  			// Handle pack setting moves the start position to end, center
  			if (totalFlex === 0) {
  				if (pack == "end") {
  					pos = availableSpace + contPaddingBox[beforeName];
  				} else if (pack == "center") {
  					pos = Math.round(
  						(contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
  					) + contPaddingBox[beforeName];
  
  					if (pos < 0) {
  						pos = contPaddingBox[beforeName];
  					}
  				} else if (pack == "justify") {
  					pos = contPaddingBox[beforeName];
  					spacing = Math.floor(availableSpace / (items.length - 1));
  				}
  			}
  
  			// Default aligning (start) the other ones needs to be calculated while doing the layout
  			rect[alignAxisName] = contPaddingBox[alignBeforeName];
  
  			// Start laying out controls
  			for (i = 0, l = items.length; i < l; i++) {
  				ctrl = items[i];
  				ctrlLayoutRect = ctrl.layoutRect();
  				size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
  
  				// Align the control on the other axis
  				if (align === "center") {
  					rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
  				} else if (align === "stretch") {
  					rect[alignSizeName] = max(
  						ctrlLayoutRect[alignMinSizeName] || 0,
  						contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
  					);
  					rect[alignAxisName] = contPaddingBox[alignBeforeName];
  				} else if (align === "end") {
  					rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
  				}
  
  				// Calculate new size based on flex
  				if (ctrlLayoutRect.flex > 0) {
  					size += ctrlLayoutRect.flex * ratio;
  				}
  
  				rect[sizeName] = size;
  				rect[posName] = pos;
  				ctrl.layoutRect(rect);
  
  				// Recalculate containers
  				if (ctrl.recalc) {
  					ctrl.recalc();
  				}
  
  				// Move x/y position
  				pos += size + spacing;
  			}
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/FlowLayout.js
  
  /**
   * FlowLayout.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This layout manager will place the controls by using the browsers native layout.
   *
   * @-x-less FlowLayout.less
   * @class maya.ui.FlowLayout
   * @extends maya.ui.Layout
   */
  define("maya/ui/FlowLayout", [
  	"maya/ui/Layout"
  ], function(Layout) {
  	return Layout.extend({
  		Defaults: {
  			containerClass: 'flow-layout',
  			controlClass: 'flow-layout-item',
  			endClass: 'break'
  		},
  
  		/**
  		 * Recalculates the positions of the controls in the specified container.
  		 *
  		 * @method recalc
  		 * @param {maya.ui.Container} container Container instance to recalc.
  		 */
  		recalc: function(container) {
  			container.items().filter(':visible').each(function(ctrl) {
  				if (ctrl.recalc) {
  					ctrl.recalc();
  				}
  			});
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/FormatControls.js
  
  /**
   * FormatControls.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Internal class containing all maya specific control types such as
   * format listboxes, fontlist boxes, toolbar buttons etc.
   *
   * @class maya.ui.FormatControls
   */
  define("maya/ui/FormatControls", [
  	"maya/ui/Control",
  	"maya/ui/Widget",
  	"maya/ui/FloatPanel",
  	"maya/util/Tools",
  	"maya/EditorManager",
  	"maya/Env"
  ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
  	var each = Tools.each;
  
  	EditorManager.on('AddEditor', function(e) {
  		if (e.editor.rtl) {
  			Control.rtl = true;
  		}
  
  		registerControls(e.editor);
  	});
  
  	Control.translate = function(text) {
  		return EditorManager.translate(text);
  	};
  
  	Widget.tooltips = !Env.iOS;
  
  	function registerControls(editor) {
  		var formatMenu;
  
  		function createListBoxChangeHandler(items, formatName) {
  			return function() {
  				var self = this;
  
  				editor.on('nodeChange', function(e) {
  					var formatter = editor.formatter;
  					var value = null;
  
  					each(e.parents, function(node) {
  						each(items, function(item) {
  							if (formatName) {
  								if (formatter.matchNode(node, formatName, {value: item.value})) {
  									value = item.value;
  								}
  							} else {
  								if (formatter.matchNode(node, item.value)) {
  									value = item.value;
  								}
  							}
  
  							if (value) {
  								return false;
  							}
  						});
  
  						if (value) {
  							return false;
  						}
  					});
  
  					self.value(value);
  				});
  			};
  		}
  
  		function createFormats(formats) {
  			formats = formats.replace(/;$/, '').split(';');
  
  			var i = formats.length;
  			while (i--) {
  				formats[i] = formats[i].split('=');
  			}
  
  			return formats;
  		}
  
  		function createFormatMenu() {
  			var count = 0, newFormats = [];
  
  			var defaultStyleFormats = [
  				{title: 'Headings', items: [
  					{title: 'Heading 1', format: 'h1'},
  					{title: 'Heading 2', format: 'h2'},
  					{title: 'Heading 3', format: 'h3'},
  					{title: 'Heading 4', format: 'h4'},
  					{title: 'Heading 5', format: 'h5'},
  					{title: 'Heading 6', format: 'h6'}
  				]},
  
  				{title: 'Inline', items: [
  					{title: 'Bold', icon: 'bold', format: 'bold'},
  					{title: 'Italic', icon: 'italic', format: 'italic'},
  					{title: 'Underline', icon: 'underline', format: 'underline'},
  					{title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
  					{title: 'Superscript', icon: 'superscript', format: 'superscript'},
  					{title: 'Subscript', icon: 'subscript', format: 'subscript'},
  					{title: 'Code', icon: 'code', format: 'code'}
  				]},
  
  				{title: 'Blocks', items: [
  					{title: 'Paragraph', format: 'p'},
  					{title: 'Blockquote', format: 'blockquote'},
  					{title: 'Div', format: 'div'},
  					{title: 'Pre', format: 'pre'}
  				]},
  
  				{title: 'Alignment', items: [
  					{title: 'Left', icon: 'alignleft', format: 'alignleft'},
  					{title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
  					{title: 'Right', icon: 'alignright', format: 'alignright'},
  					{title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
  				]}
  			];
  
  			function createMenu(formats) {
  				var menu = [];
  
  				if (!formats) {
  					return;
  				}
  
  				each(formats, function(format) {
  					var menuItem = {
  						text: format.title,
  						icon: format.icon
  					};
  
  					if (format.items) {
  						menuItem.menu = createMenu(format.items);
  					} else {
  						var formatName = format.format || "custom" + count++;
  
  						if (!format.format) {
  							format.name = formatName;
  							newFormats.push(format);
  						}
  
  						menuItem.format = formatName;
  						menuItem.cmd = format.cmd;
  					}
  
  					menu.push(menuItem);
  				});
  
  				return menu;
  			}
  
  			function createStylesMenu() {
  				var menu;
  
  				if (editor.settings.style_formats_merge) {
  					if (editor.settings.style_formats) {
  						menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
  					} else {
  						menu = createMenu(defaultStyleFormats);
  					}
  				} else {
  					menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
  				}
  
  				return menu;
  			}
  
  			editor.on('init', function() {
  				each(newFormats, function(format) {
  					editor.formatter.register(format.name, format);
  				});
  			});
  
  			return {
  				type: 'menu',
  				items: createStylesMenu(),
  				onPostRender: function(e) {
  					editor.fire('renderFormatsMenu', {control: e.control});
  				},
  				itemDefaults: {
  					preview: true,
  
  					textStyle: function() {
  						if (this.settings.format) {
  							return editor.formatter.getCssText(this.settings.format);
  						}
  					},
  
  					onPostRender: function() {
  						var self = this;
  
  						self.parent().on('show', function() {
  							var formatName, command;
  
  							formatName = self.settings.format;
  							if (formatName) {
  								self.disabled(!editor.formatter.canApply(formatName));
  								self.active(editor.formatter.match(formatName));
  							}
  
  							command = self.settings.cmd;
  							if (command) {
  								self.active(editor.queryCommandState(command));
  							}
  						});
  					},
  
  					onclick: function() {
  						if (this.settings.format) {
  							toggleFormat(this.settings.format);
  						}
  
  						if (this.settings.cmd) {
  							editor.execCommand(this.settings.cmd);
  						}
  					}
  				}
  			};
  		}
  
  		formatMenu = createFormatMenu();
  
  		// Simple format controls <control/format>:<UI text>
  		each({
  			bold: 'Bold',
  			italic: 'Italic',
  			underline: 'Underline',
  			strikethrough: 'Strikethrough',
  			subscript: 'Subscript',
  			superscript: 'Superscript'
  		}, function(text, name) {
  			editor.addButton(name, {
  				tooltip: text,
  				onPostRender: function() {
  					var self = this;
  
  					// TODO: Fix this
  					if (editor.formatter) {
  						editor.formatter.formatChanged(name, function(state) {
  							self.active(state);
  						});
  					} else {
  						editor.on('init', function() {
  							editor.formatter.formatChanged(name, function(state) {
  								self.active(state);
  							});
  						});
  					}
  				},
  				onclick: function() {
  					toggleFormat(name);
  				}
  			});
  		});
  
  		// Simple command controls <control>:[<UI text>,<Command>]
  		each({
  			outdent: ['Decrease indent', 'Outdent'],
  			indent: ['Increase indent', 'Indent'],
  			cut: ['Cut', 'Cut'],
  			copy: ['Copy', 'Copy'],
  			paste: ['Paste', 'Paste'],
  			help: ['Help', 'mceHelp'],
  			selectall: ['Select all', 'SelectAll'],
  			removeformat: ['Clear formatting', 'RemoveFormat'],
  			visualaid: ['Visual aids', 'mceToggleVisualAid'],
  			newdocument: ['New document', 'mceNewDocument']
  		}, function(item, name) {
  			editor.addButton(name, {
  				tooltip: item[0],
  				cmd: item[1]
  			});
  		});
  
  		// Simple command controls with format state
  		each({
  			blockquote: ['Blockquote', 'mceBlockQuote'],
  			numlist: ['Numbered list', 'InsertOrderedList'],
  			bullist: ['Bullet list', 'InsertUnorderedList'],
  			subscript: ['Subscript', 'Subscript'],
  			superscript: ['Superscript', 'Superscript'],
  			alignleft: ['Align left', 'JustifyLeft'],
  			aligncenter: ['Align center', 'JustifyCenter'],
  			alignright: ['Align right', 'JustifyRight'],
  			alignjustify: ['Justify', 'JustifyFull']
  		}, function(item, name) {
  			editor.addButton(name, {
  				tooltip: item[0],
  				cmd: item[1],
  				onPostRender: function() {
  					var self = this;
  
  					// TODO: Fix this
  					if (editor.formatter) {
  						editor.formatter.formatChanged(name, function(state) {
  							self.active(state);
  						});
  					} else {
  						editor.on('init', function() {
  							editor.formatter.formatChanged(name, function(state) {
  								self.active(state);
  							});
  						});
  					}
  				}
  			});
  		});
  
  		function toggleUndoRedoState(type) {
  			return function() {
  				var self = this;
  
  				type = type == 'redo' ? 'hasRedo' : 'hasUndo';
  
  				function checkState() {
  					return editor.undoManager ? editor.undoManager[type]() : false;
  				}
  
  				self.disabled(!checkState());
  				editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() {
  					self.disabled(!checkState());
  				});
  			};
  		}
  
  		function toggleVisualAidState() {
  			var self = this;
  
  			editor.on('VisualAid', function(e) {
  				self.active(e.hasVisual);
  			});
  
  			self.active(editor.hasVisual);
  		}
  
  		editor.addButton('undo', {
  			tooltip: 'Undo',
  			onPostRender: toggleUndoRedoState('undo'),
  			cmd: 'undo'
  		});
  
  		editor.addButton('redo', {
  			tooltip: 'Redo',
  			onPostRender: toggleUndoRedoState('redo'),
  			cmd: 'redo'
  		});
  
  		editor.addMenuItem('newdocument', {
  			text: 'New document',
  			icon: 'newdocument',
  			cmd: 'mceNewDocument'
  		});
  
  		editor.addMenuItem('undo', {
  			text: 'Undo',
  			icon: 'undo',
  			shortcut: 'Meta+Z',
  			onPostRender: toggleUndoRedoState('undo'),
  			cmd: 'undo'
  		});
  
  		editor.addMenuItem('redo', {
  			text: 'Redo',
  			icon: 'redo',
  			shortcut: 'Meta+Y',
  			onPostRender: toggleUndoRedoState('redo'),
  			cmd: 'redo'
  		});
  
  		editor.addMenuItem('visualaid', {
  			text: 'Visual aids',
  			selectable: true,
  			onPostRender: toggleVisualAidState,
  			cmd: 'mceToggleVisualAid'
  		});
  
  		each({
  			cut: ['Cut', 'Cut', 'Meta+X'],
  			copy: ['Copy', 'Copy', 'Meta+C'],
  			paste: ['Paste', 'Paste', 'Meta+V'],
  			selectall: ['Select all', 'SelectAll', 'Meta+A'],
  			bold: ['Bold', 'Bold', 'Meta+B'],
  			italic: ['Italic', 'Italic', 'Meta+I'],
  			underline: ['Underline', 'Underline'],
  			strikethrough: ['Strikethrough', 'Strikethrough'],
  			subscript: ['Subscript', 'Subscript'],
  			superscript: ['Superscript', 'Superscript'],
  			removeformat: ['Clear formatting', 'RemoveFormat']
  		}, function(item, name) {
  			editor.addMenuItem(name, {
  				text: item[0],
  				icon: name,
  				shortcut: item[2],
  				cmd: item[1]
  			});
  		});
  
  		editor.on('mousedown', function() {
  			FloatPanel.hideAll();
  		});
  
  		function toggleFormat(fmt) {
  			if (fmt.control) {
  				fmt = fmt.control.value();
  			}
  
  			if (fmt) {
  				editor.execCommand('mceToggleFormat', false, fmt);
  			}
  		}
  
  		editor.addButton('styleselect', {
  			type: 'menubutton',
  			text: 'Formats',
  			menu: formatMenu
  		});
  
  		editor.addButton('formatselect', function() {
  			var items = [], blocks = createFormats(editor.settings.block_formats ||
  				'Paragraph=p;' +
  				'Heading 1=h1;' +
  				'Heading 2=h2;' +
  				'Heading 3=h3;' +
  				'Heading 4=h4;' +
  				'Heading 5=h5;' +
  				'Heading 6=h6;' +
  				'Preformatted=pre'
  			);
  
  			each(blocks, function(block) {
  				items.push({
  					text: block[0],
  					value: block[1],
  					textStyle: function() {
  						return editor.formatter.getCssText(block[1]);
  					}
  				});
  			});
  
  			return {
  				type: 'listbox',
  				text: blocks[0][0],
  				values: items,
  				fixedWidth: true,
  				onselect: toggleFormat,
  				onPostRender: createListBoxChangeHandler(items)
  			};
  		});
  
  		editor.addButton('fontselect', function() {
  			var defaultFontsFormats =
  				'Andale Mono=andale mono,monospace;' +
  				'Arial=arial,helvetica,sans-serif;' +
  				'Arial Black=arial black,sans-serif;' +
  				'Book Antiqua=book antiqua,palatino,serif;' +
  				'Comic Sans MS=comic sans ms,sans-serif;' +
  				'Courier New=courier new,courier,monospace;' +
  				'Georgia=georgia,palatino,serif;' +
  				'Helvetica=helvetica,arial,sans-serif;' +
  				'Impact=impact,sans-serif;' +
  				'Symbol=symbol;' +
  				'Tahoma=tahoma,arial,helvetica,sans-serif;' +
  				'Terminal=terminal,monaco,monospace;' +
  				'Times New Roman=times new roman,times,serif;' +
  				'Trebuchet MS=trebuchet ms,geneva,sans-serif;' +
  				'Verdana=verdana,geneva,sans-serif;' +
  				'Webdings=webdings;' +
  				'Wingdings=wingdings,zapf dingbats';
  
  			var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
  
  			each(fonts, function(font) {
  				items.push({
  					text: {raw: font[0]},
  					value: font[1],
  					textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
  				});
  			});
  
  			return {
  				type: 'listbox',
  				text: 'Font Family',
  				tooltip: 'Font Family',
  				values: items,
  				fixedWidth: true,
  				onPostRender: createListBoxChangeHandler(items, 'fontname'),
  				onselect: function(e) {
  					if (e.control.settings.value) {
  						editor.execCommand('FontName', false, e.control.settings.value);
  					}
  				}
  			};
  		});
  
  		editor.addButton('fontsizeselect', function() {
  			var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
  			var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
  
  			each(fontsize_formats.split(' '), function(item) {
  				var text = item, value = item;
  				// Allow text=value font sizes.
  				var values = item.split('=');
  				if (values.length > 1) {
  					text = values[0];
  					value = values[1];
  				}
  				items.push({text: text, value: value});
  			});
  
  			return {
  				type: 'listbox',
  				text: 'Font Sizes',
  				tooltip: 'Font Sizes',
  				values: items,
  				fixedWidth: true,
  				onPostRender: createListBoxChangeHandler(items, 'fontsize'),
  				onclick: function(e) {
  					if (e.control.settings.value) {
  						editor.execCommand('FontSize', false, e.control.settings.value);
  					}
  				}
  			};
  		});
  
  		editor.addMenuItem('formats', {
  			text: 'Formats',
  			menu: formatMenu
  		});
  	}
  });
  
  // Included from: js/maya/classes/ui/GridLayout.js
  
  /**
   * GridLayout.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This layout manager places controls in a grid.
   *
   * @setting {Number} spacing Spacing between controls.
   * @setting {Number} spacingH Horizontal spacing between controls.
   * @setting {Number} spacingV Vertical spacing between controls.
   * @setting {Number} columns Number of columns to use.
   * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
   * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
   * @setting {String} pack start|end
   *
   * @class maya.ui.GridLayout
   * @extends maya.ui.AbsoluteLayout
   */
  define("maya/ui/GridLayout", [
  	"maya/ui/AbsoluteLayout"
  ], function(AbsoluteLayout) {
  	"use strict";
  
  	return AbsoluteLayout.extend({
  		/**
  		 * Recalculates the positions of the controls in the specified container.
  		 *
  		 * @method recalc
  		 * @param {maya.ui.Container} container Container instance to recalc.
  		 */
  		recalc: function(container) {
  			var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
  				ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
  				colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx;
  
  			// Get layout settings
  			settings = container.settings;
  			items = container.items().filter(':visible');
  			contLayoutRect = container.layoutRect();
  			cols = settings.columns || Math.ceil(Math.sqrt(items.length));
  			rows = Math.ceil(items.length / cols);
  			spacingH = settings.spacingH || settings.spacing || 0;
  			spacingV = settings.spacingV || settings.spacing || 0;
  			alignH = settings.alignH || settings.align;
  			alignV = settings.alignV || settings.align;
  			contPaddingBox = container._paddingBox;
  			reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl();
  
  			if (alignH && typeof alignH == "string") {
  				alignH = [alignH];
  			}
  
  			if (alignV && typeof alignV == "string") {
  				alignV = [alignV];
  			}
  
  			// Zero padd columnWidths
  			for (x = 0; x < cols; x++) {
  				colWidths.push(0);
  			}
  
  			// Zero padd rowHeights
  			for (y = 0; y < rows; y++) {
  				rowHeights.push(0);
  			}
  
  			// Calculate columnWidths and rowHeights
  			for (y = 0; y < rows; y++) {
  				for (x = 0; x < cols; x++) {
  					ctrl = items[y * cols + x];
  
  					// Out of bounds
  					if (!ctrl) {
  						break;
  					}
  
  					ctrlLayoutRect = ctrl.layoutRect();
  					ctrlMinWidth = ctrlLayoutRect.minW;
  					ctrlMinHeight = ctrlLayoutRect.minH;
  
  					colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
  					rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
  				}
  			}
  
  			// Calculate maxX
  			availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
  			for (maxX = 0, x = 0; x < cols; x++) {
  				maxX += colWidths[x] + (x > 0 ? spacingH : 0);
  				availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
  			}
  
  			// Calculate maxY
  			availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
  			for (maxY = 0, y = 0; y < rows; y++) {
  				maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
  				availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
  			}
  
  			maxX += contPaddingBox.left + contPaddingBox.right;
  			maxY += contPaddingBox.top + contPaddingBox.bottom;
  
  			// Calculate minW/minH
  			rect = {};
  			rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
  			rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
  
  			rect.contentW = rect.minW - contLayoutRect.deltaW;
  			rect.contentH = rect.minH - contLayoutRect.deltaH;
  			rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
  			rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
  			rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
  			rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
  
  			// Resize container container if minSize was changed
  			if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  				rect.w = rect.minW;
  				rect.h = rect.minH;
  
  				container.layoutRect(rect);
  				this.recalc(container);
  
  				// Forced recalc for example if items are hidden/shown
  				if (container._lastRect === null) {
  					var parentCtrl = container.parent();
  					if (parentCtrl) {
  						parentCtrl._lastRect = null;
  						parentCtrl.recalc();
  					}
  				}
  
  				return;
  			}
  
  			// Update contentW/contentH so absEnd moves correctly
  			if (contLayoutRect.autoResize) {
  				rect = container.layoutRect(rect);
  				rect.contentW = rect.minW - contLayoutRect.deltaW;
  				rect.contentH = rect.minH - contLayoutRect.deltaH;
  			}
  
  			var flexV;
  
  			if (settings.packV == 'start') {
  				flexV = 0;
  			} else {
  				flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
  			}
  
  			// Calculate totalFlex
  			var totalFlex = 0;
  			var flexWidths = settings.flexWidths;
  			if (flexWidths) {
  				for (x = 0; x < flexWidths.length; x++) {
  					totalFlex += flexWidths[x];
  				}
  			} else {
  				totalFlex = cols;
  			}
  
  			// Calculate new column widths based on flex values
  			var ratio = availableWidth / totalFlex;
  			for (x = 0; x < cols; x++) {
  				colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
  			}
  
  			// Move/resize controls
  			posY = contPaddingBox.top;
  			for (y = 0; y < rows; y++) {
  				posX = contPaddingBox.left;
  				height = rowHeights[y] + flexV;
  
  				for (x = 0; x < cols; x++) {
  					if (reverseRows) {
  						idx = y * cols + cols - 1 - x;
  					} else {
  						idx = y * cols + x;
  					}
  
  					ctrl = items[idx];
  
  					// No more controls to render then break
  					if (!ctrl) {
  						break;
  					}
  
  					// Get control settings and calculate x, y
  					ctrlSettings = ctrl.settings;
  					ctrlLayoutRect = ctrl.layoutRect();
  					width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
  					ctrlLayoutRect.x = posX;
  					ctrlLayoutRect.y = posY;
  
  					// Align control horizontal
  					align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
  					if (align == "center") {
  						ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
  					} else if (align == "right") {
  						ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
  					} else if (align == "stretch") {
  						ctrlLayoutRect.w = width;
  					}
  
  					// Align control vertical
  					align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
  					if (align == "center") {
  						ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
  					} else if (align == "bottom") {
  						ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
  					} else if (align == "stretch") {
  						ctrlLayoutRect.h = height;
  					}
  
  					ctrl.layoutRect(ctrlLayoutRect);
  
  					posX += width + spacingH;
  
  					if (ctrl.recalc) {
  						ctrl.recalc();
  					}
  				}
  
  				posY += height + spacingV;
  			}
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Iframe.js
  
  /**
   * Iframe.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /*jshint scripturl:true */
  
  /**
   * This class creates an iframe.
   *
   * @setting {String} url Url to open in the iframe.
   *
   * @-x-less Iframe.less
   * @class maya.ui.Iframe
   * @extends maya.ui.Widget
   */
  define("maya/ui/Iframe", [
  	"maya/ui/Widget"
  ], function(Widget) {
  	"use strict";
  
  	return Widget.extend({
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this;
  
  			self.addClass('iframe');
  			self.canFocus = false;
  
  			/*eslint no-script-url:0 */
  			return (
  				'<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
  				(self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
  			);
  		},
  
  		/**
  		 * Setter for the iframe source.
  		 *
  		 * @method src
  		 * @param {String} src Source URL for iframe.
  		 */
  		src: function(src) {
  			this.getEl().src = src;
  		},
  
  		/**
  		 * Inner HTML for the iframe.
  		 *
  		 * @method html
  		 * @param {String} html HTML string to set as HTML inside the iframe.
  		 * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
  		 * @return {maya.ui.Iframe} Current iframe control.
  		 */
  		html: function(html, callback) {
  			var self = this, body = this.getEl().contentWindow.document.body;
  
  			// Wait for iframe to initialize IE 10 takes time
  			if (!body) {
  				setTimeout(function() {
  					self.html(html);
  				}, 0);
  			} else {
  				body.innerHTML = html;
  
  				if (callback) {
  					callback();
  				}
  			}
  
  			return this;
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Label.js
  
  /**
   * Label.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class creates a label element. A label is a simple text control
   * that can be bound to other controls.
   *
   * @-x-less Label.less
   * @class maya.ui.Label
   * @extends maya.ui.Widget
   */
  define("maya/ui/Label", [
  	"maya/ui/Widget",
  	"maya/ui/DomUtils"
  ], function(Widget, DomUtils) {
  	"use strict";
  
  	return Widget.extend({
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @param {Boolean} multiline Multiline label.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  			self.addClass('widget');
  			self.addClass('label');
  			self.canFocus = false;
  
  			if (settings.multiline) {
  				self.addClass('autoscroll');
  			}
  
  			if (settings.strong) {
  				self.addClass('strong');
  			}
  		},
  
  		/**
  		 * Initializes the current controls layout rect.
  		 * This will be executed by the layout managers to determine the
  		 * default minWidth/minHeight etc.
  		 *
  		 * @method initLayoutRect
  		 * @return {Object} Layout rect instance.
  		 */
  		initLayoutRect: function() {
  			var self = this, layoutRect = self._super();
  
  			if (self.settings.multiline) {
  				var size = DomUtils.getSize(self.getEl());
  
  				// Check if the text fits within maxW if not then try word wrapping it
  				if (size.width > layoutRect.maxW) {
  					layoutRect.minW = layoutRect.maxW;
  					self.addClass('multiline');
  				}
  
  				self.getEl().style.width = layoutRect.minW + 'px';
  				layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
  			}
  
  			return layoutRect;
  		},
  
  		/**
  		 * Repaints the control after a layout operation.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			var self = this;
  
  			if (!self.settings.multiline) {
  				self.getEl().style.lineHeight = self.layoutRect().h + 'px';
  			}
  
  			return self._super();
  		},
  
  		/**
  		 * Sets/gets the current label text.
  		 *
  		 * @method text
  		 * @param {String} [text] New label text.
  		 * @return {String|maya.ui.Label} Current text or current label instance.
  		 */
  		text: function(text) {
  			var self = this;
  
  			if (self._rendered && text) {
  				this.innerHtml(self.encode(text));
  			}
  
  			return self._super(text);
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, forId = self.settings.forId;
  
  			return (
  				'<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
  					self.encode(self._text) +
  				'</label>'
  			);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Toolbar.js
  
  /**
   * Toolbar.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new toolbar.
   *
   * @class maya.ui.Toolbar
   * @extends maya.ui.Container
   */
  define("maya/ui/Toolbar", [
  	"maya/ui/Container"
  ], function(Container) {
  	"use strict";
  
  	return Container.extend({
  		Defaults: {
  			role: 'toolbar',
  			layout: 'flow'
  		},
  
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  			self.addClass('toolbar');
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this;
  
  			self.items().addClass('toolbar-item');
  
  			return self._super();
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/MenuBar.js
  
  /**
   * MenuBar.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new menubar.
   *
   * @-x-less MenuBar.less
   * @class maya.ui.MenuBar
   * @extends maya.ui.Toolbar
   */
  define("maya/ui/MenuBar", [
  	"maya/ui/Toolbar"
  ], function(Toolbar) {
  	"use strict";
  
  	return Toolbar.extend({
  		Defaults: {
  			role: 'menubar',
  			containerCls: 'menubar',
  			ariaRoot: true,
  			defaults: {
  				type: 'menubutton'
  			}
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/MenuButton.js
  
  /**
   * MenuButton.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new menu button.
   *
   * @-x-less MenuButton.less
   * @class maya.ui.MenuButton
   * @extends maya.ui.Button
   */
  define("maya/ui/MenuButton", [
  	"maya/ui/Button",
  	"maya/ui/Factory",
  	"maya/ui/MenuBar"
  ], function(Button, Factory, MenuBar) {
  	"use strict";
  
  	// TODO: Maybe add as some global function
  	function isChildOf(node, parent) {
  		while (node) {
  			if (parent === node) {
  				return true;
  			}
  
  			node = node.parentNode;
  		}
  
  		return false;
  	}
  
  	var MenuButton = Button.extend({
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._renderOpen = true;
  			self._super(settings);
  
  			self.addClass('menubtn');
  
  			if (settings.fixedWidth) {
  				self.addClass('fixed-width');
  			}
  
  			self.aria('haspopup', true);
  			self.hasPopup = true;
  		},
  
  		/**
  		 * Shows the menu for the button.
  		 *
  		 * @method showMenu
  		 */
  		showMenu: function() {
  			var self = this, settings = self.settings, menu;
  
  			if (self.menu && self.menu.visible()) {
  				return self.hideMenu();
  			}
  
  			if (!self.menu) {
  				menu = settings.menu || [];
  
  				// Is menu array then auto constuct menu control
  				if (menu.length) {
  					menu = {
  						type: 'menu',
  						items: menu
  					};
  				} else {
  					menu.type = menu.type || 'menu';
  				}
  
  				self.menu = Factory.create(menu).parent(self).renderTo();
  				self.fire('createmenu');
  				self.menu.reflow();
  				self.menu.on('cancel', function(e) {
  					if (e.control.parent() === self.menu) {
  						e.stopPropagation();
  						self.focus();
  						self.hideMenu();
  					}
  				});
  
  				// Move focus to button when a menu item is selected/clicked
  				self.menu.on('select', function() {
  					self.focus();
  				});
  
  				self.menu.on('show hide', function(e) {
  					if (e.control == self.menu) {
  						self.activeMenu(e.type == 'show');
  					}
  
  					self.aria('expanded', e.type == 'show');
  				}).fire('show');
  			}
  
  			self.menu.show();
  			self.menu.layoutRect({w: self.layoutRect().w});
  			self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  		},
  
  		/**
  		 * Hides the menu for the button.
  		 *
  		 * @method hideMenu
  		 */
  		hideMenu: function() {
  			var self = this;
  
  			if (self.menu) {
  				self.menu.items().each(function(item) {
  					if (item.hideMenu) {
  						item.hideMenu();
  					}
  				});
  
  				self.menu.hide();
  			}
  		},
  
  		/**
  		 * Sets the active menu state.
  		 *
  		 * @private
  		 */
  		activeMenu: function(state) {
  			this.toggleClass('active', state);
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, prefix = self.classPrefix;
  			var icon = self.settings.icon, image;
  
  			image = self.settings.image;
  			if (image) {
  				icon = 'none';
  
  				// Support for [high dpi, low dpi] image sources
  				if (typeof image != "string") {
  					image = window.getSelection ? image[0] : image[1];
  				}
  
  				image = ' style="background-image: url(\'' + image + '\')"';
  			} else {
  				image = '';
  			}
  
  			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  
  			self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
  					'<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
  						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  						'<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
  						' <i class="' + prefix + 'caret"></i>' +
  					'</button>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Gets invoked after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this;
  
  			self.on('click', function(e) {
  				if (e.control === self && isChildOf(e.target, self.getEl())) {
  					self.showMenu();
  
  					if (e.aria) {
  						self.menu.items()[0].focus();
  					}
  				}
  			});
  
  			self.on('mouseenter', function(e) {
  				var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
  
  				if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
  					parent.items().filter('MenuButton').each(function(ctrl) {
  						if (ctrl.hideMenu && ctrl != overCtrl) {
  							if (ctrl.menu && ctrl.menu.visible()) {
  								hasVisibleSiblingMenu = true;
  							}
  
  							ctrl.hideMenu();
  						}
  					});
  
  					if (hasVisibleSiblingMenu) {
  						overCtrl.focus(); // Fix for: #5887
  						overCtrl.showMenu();
  					}
  				}
  			});
  
  			return self._super();
  		},
  
  		/**
  		 * Sets/gets the current button text.
  		 *
  		 * @method text
  		 * @param {String} [text] New button text.
  		 * @return {String|maya.ui.MenuButton} Current text or current MenuButton instance.
  		 */
  		text: function(text) {
  			var self = this, i, children;
  
  			if (self._rendered) {
  				children = self.getEl('open').getElementsByTagName('span');
  				for (i = 0; i < children.length; i++) {
  					children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text);
  				}
  			}
  
  			return this._super(text);
  		},
  
  		/**
  		 * Removes the control and it's menus.
  		 *
  		 * @method remove
  		 */
  		remove: function() {
  			this._super();
  
  			if (this.menu) {
  				this.menu.remove();
  			}
  		}
  	});
  
  	return MenuButton;
  });
  
  // Included from: js/maya/classes/ui/ListBox.js
  
  /**
   * ListBox.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new list box control.
   *
   * @-x-less ListBox.less
   * @class maya.ui.ListBox
   * @extends maya.ui.MenuButton
   */
  define("maya/ui/ListBox", [
  	"maya/ui/MenuButton"
  ], function(MenuButton) {
  	"use strict";
  
  	return MenuButton.extend({
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {Array} values Array with values to add to list box.
  		 */
  		init: function(settings) {
  			var self = this, values, selected, selectedText, lastItemCtrl;
  
  			function setSelected(menuValues) {
  				// Try to find a selected value
  				for (var i = 0; i < menuValues.length; i++) {
  					selected = menuValues[i].selected || settings.value === menuValues[i].value;
  
  					if (selected) {
  						selectedText = selectedText || menuValues[i].text;
  						self._value = menuValues[i].value;
  						break;
  					}
  
  					// If the value has a submenu, try to find the selected values in that menu
  					if (menuValues[i].menu) {
  						setSelected(menuValues[i].menu);
  					}
  				}
  			}
  
  			self._values = values = settings.values;
  			if (values) {
  				setSelected(values);
  
  				// Default with first item
  				if (!selected && values.length > 0) {
  					selectedText = values[0].text;
  					self._value = values[0].value;
  				}
  
  				settings.menu = values;
  			}
  
  			settings.text = settings.text || selectedText || values[0].text;
  
  			self._super(settings);
  			self.addClass('listbox');
  
  			self.on('select', function(e) {
  				var ctrl = e.control;
  
  				if (lastItemCtrl) {
  					e.lastControl = lastItemCtrl;
  				}
  
  				if (settings.multiple) {
  					ctrl.active(!ctrl.active());
  				} else {
  					self.value(e.control.settings.value);
  				}
  
  				lastItemCtrl = ctrl;
  			});
  		},
  
  		/**
  		 * Getter/setter function for the control value.
  		 *
  		 * @method value
  		 * @param {String} [value] Value to be set.
  		 * @return {Boolean/maya.ui.ListBox} Value or self if it's a set operation.
  		 */
  		value: function(value) {
  			var self = this, active, selectedText, menu;
  
  			function activateByValue(menu, value) {
  				menu.items().each(function(ctrl) {
  					active = ctrl.value() === value;
  
  					if (active) {
  						selectedText = selectedText || ctrl.text();
  					}
  
  					ctrl.active(active);
  
  					if (ctrl.menu) {
  						activateByValue(ctrl.menu, value);
  					}
  				});
  			}
  
  			function setActiveValues(menuValues) {
  				for (var i = 0; i < menuValues.length; i++) {
  					active = menuValues[i].value == value;
  
  					if (active) {
  						selectedText = selectedText || menuValues[i].text;
  					}
  
  					menuValues[i].active = active;
  
  					if (menuValues[i].menu) {
  						setActiveValues(menuValues[i].menu);
  					}
  				}
  			}
  
  			if (typeof value != "undefined") {
  				if (self.menu) {
  					activateByValue(self.menu, value);
  				} else {
  					menu = self.settings.menu;
  					setActiveValues(menu);
  				}
  
  				self.text(selectedText || this.settings.text);
  			}
  
  			return self._super(value);
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/MenuItem.js
  
  /**
   * MenuItem.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new menu item.
   *
   * @-x-less MenuItem.less
   * @class maya.ui.MenuItem
   * @extends maya.ui.Widget
   */
  define("maya/ui/MenuItem", [
  	"maya/ui/Widget",
  	"maya/ui/Factory",
  	"maya/Env"
  ], function(Widget, Factory, Env) {
  	"use strict";
  
  	return Widget.extend({
  		Defaults: {
  			border: 0,
  			role: 'menuitem'
  		},
  
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {Boolean} selectable Selectable menu.
  		 * @setting {Array} menu Submenu array with items.
  		 * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self.hasPopup = true;
  
  			self._super(settings);
  
  			settings = self.settings;
  
  			self.addClass('menu-item');
  
  			if (settings.menu) {
  				self.addClass('menu-item-expand');
  			}
  
  			if (settings.preview) {
  				self.addClass('menu-item-preview');
  			}
  
  			if (self._text === '-' || self._text === '|') {
  				self.addClass('menu-item-sep');
  				self.aria('role', 'separator');
  				self._text = '-';
  			}
  
  			if (settings.selectable) {
  				self.aria('role', 'menuitemcheckbox');
  				self.addClass('menu-item-checkbox');
  				settings.icon = 'selected';
  			}
  
  			if (!settings.preview && !settings.selectable) {
  				self.addClass('menu-item-normal');
  			}
  
  			self.on('mousedown', function(e) {
  				e.preventDefault();
  			});
  
  			if (settings.menu && !settings.ariaHideMenu) {
  				self.aria('haspopup', true);
  			}
  		},
  
  		/**
  		 * Returns true/false if the menuitem has sub menu.
  		 *
  		 * @method hasMenus
  		 * @return {Boolean} True/false state if it has submenu.
  		 */
  		hasMenus: function() {
  			return !!this.settings.menu;
  		},
  
  		/**
  		 * Shows the menu for the menu item.
  		 *
  		 * @method showMenu
  		 */
  		showMenu: function() {
  			var self = this, settings = self.settings, menu, parent = self.parent();
  
  			parent.items().each(function(ctrl) {
  				if (ctrl !== self) {
  					ctrl.hideMenu();
  				}
  			});
  
  			if (settings.menu) {
  				menu = self.menu;
  
  				if (!menu) {
  					menu = settings.menu;
  
  					// Is menu array then auto constuct menu control
  					if (menu.length) {
  						menu = {
  							type: 'menu',
  							items: menu
  						};
  					} else {
  						menu.type = menu.type || 'menu';
  					}
  
  					if (parent.settings.itemDefaults) {
  						menu.itemDefaults = parent.settings.itemDefaults;
  					}
  
  					menu = self.menu = Factory.create(menu).parent(self).renderTo();
  					menu.reflow();
  					menu.on('cancel', function(e) {
  						e.stopPropagation();
  						self.focus();
  						menu.hide();
  					});
  					menu.on('show hide', function(e) {
  						e.control.items().each(function(ctrl) {
  							ctrl.active(ctrl.settings.selected);
  						});
  					}).fire('show');
  
  					menu.on('hide', function(e) {
  						if (e.control === menu) {
  							self.removeClass('selected');
  						}
  					});
  
  					menu.submenu = true;
  				} else {
  					menu.show();
  				}
  
  				menu._parentMenu = parent;
  
  				menu.addClass('menu-sub');
  
  				var rel = menu.testMoveRel(
  					self.getEl(),
  					self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
  				);
  
  				menu.moveRel(self.getEl(), rel);
  				menu.rel = rel;
  
  				rel = 'menu-sub-' + rel;
  				menu.removeClass(menu._lastRel);
  				menu.addClass(rel);
  				menu._lastRel = rel;
  
  				self.addClass('selected');
  				self.aria('expanded', true);
  			}
  		},
  
  		/**
  		 * Hides the menu for the menu item.
  		 *
  		 * @method hideMenu
  		 */
  		hideMenu: function() {
  			var self = this;
  
  			if (self.menu) {
  				self.menu.items().each(function(item) {
  					if (item.hideMenu) {
  						item.hideMenu();
  					}
  				});
  
  				self.menu.hide();
  				self.aria('expanded', false);
  			}
  
  			return self;
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
  			var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
  
  			// Converts shortcut format to Mac/PC variants
  			function convertShortcut(shortcut) {
  				var i, value, replace = {};
  
  				if (Env.mac) {
  					replace = {
  						alt: '&#x2325;',
  						ctrl: '&#x2318;',
  						shift: '&#x21E7;',
  						meta: '&#x2318;'
  					};
  				} else {
  					replace = {
  						meta: 'Ctrl'
  					};
  				}
  
  				shortcut = shortcut.split('+');
  
  				for (i = 0; i < shortcut.length; i++) {
  					value = replace[shortcut[i].toLowerCase()];
  
  					if (value) {
  						shortcut[i] = value;
  					}
  				}
  
  				return shortcut.join('+');
  			}
  
  			if (icon) {
  				self.parent().addClass('menu-has-icons');
  			}
  
  			if (settings.image) {
  				icon = 'none';
  				image = ' style="background-image: url(\'' + settings.image + '\')"';
  			}
  
  			if (shortcut) {
  				shortcut = convertShortcut(shortcut);
  			}
  
  			icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
  					(text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
  					(text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
  					(shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
  					(settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Gets invoked after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this, settings = self.settings;
  
  			var textStyle = settings.textStyle;
  			if (typeof textStyle == "function") {
  				textStyle = textStyle.call(this);
  			}
  
  			if (textStyle) {
  				var textElm = self.getEl('text');
  				if (textElm) {
  					textElm.setAttribute('style', textStyle);
  				}
  			}
  
  			self.on('mouseenter click', function(e) {
  				if (e.control === self) {
  					if (!settings.menu && e.type === 'click') {
  						self.fire('select');
  						self.parent().hideAll();
  					} else {
  						self.showMenu();
  
  						if (e.aria) {
  							self.menu.focus(true);
  						}
  					}
  				}
  			});
  
  			self._super();
  
  			return self;
  		},
  
  		active: function(state) {
  			if (typeof state != "undefined") {
  				this.aria('checked', state);
  			}
  
  			return this._super(state);
  		},
  
  		/**
  		 * Removes the control and it's menus.
  		 *
  		 * @method remove
  		 */
  		remove: function() {
  			this._super();
  
  			if (this.menu) {
  				this.menu.remove();
  			}
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Menu.js
  
  /**
   * Menu.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new menu.
   *
   * @-x-less Menu.less
   * @class maya.ui.Menu
   * @extends maya.ui.FloatPanel
   */
  define("maya/ui/Menu", [
  	"maya/ui/FloatPanel",
  	"maya/ui/MenuItem",
  	"maya/util/Tools"
  ], function(FloatPanel, MenuItem, Tools) {
  	"use strict";
  
  	var Menu = FloatPanel.extend({
  		Defaults: {
  			defaultType: 'menuitem',
  			border: 1,
  			layout: 'stack',
  			role: 'application',
  			bodyRole: 'menu',
  			ariaRoot: true
  		},
  
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			settings.autohide = true;
  			settings.constrainToViewport = true;
  
  			if (settings.itemDefaults) {
  				var items = settings.items, i = items.length;
  
  				while (i--) {
  					items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
  				}
  			}
  
  			self._super(settings);
  			self.addClass('menu');
  		},
  
  		/**
  		 * Repaints the control after a layout operation.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			this.toggleClass('menu-align', true);
  
  			this._super();
  
  			this.getEl().style.height = '';
  			this.getEl('body').style.height = '';
  
  			return this;
  		},
  
  		/**
  		 * Hides/closes the menu.
  		 *
  		 * @method cancel
  		 */
  		cancel: function() {
  			var self = this;
  
  			self.hideAll();
  			self.fire('select');
  		},
  
  		/**
  		 * Hide menu and all sub menus.
  		 *
  		 * @method hideAll
  		 */
  		hideAll: function() {
  			var self = this;
  
  			this.find('menuitem').exec('hideMenu');
  
  			return self._super();
  		},
  /*
  		getContainerElm: function() {
  			var doc = document, id = this.classPrefix + 'menucontainer';
  
  			var elm = doc.getElementById(id);
  			if (!elm) {
  				elm = doc.createElement('div');
  				elm.id = id;
  				elm.setAttribute('role', 'application');
  				elm.className = this.classPrefix + '-reset';
  				elm.style.position = 'absolute';
  				elm.style.top = elm.style.left = '0';
  				elm.style.overflow = 'visible';
  				doc.body.appendChild(elm);
  			}
  
  			return elm;
  		},
  */
  		/**
  		 * Invoked before the menu is rendered.
  		 *
  		 * @method preRender
  		 */
  		preRender: function() {
  			var self = this;
  
  			self.items().each(function(ctrl) {
  				var settings = ctrl.settings;
  
  				if (settings.icon || settings.selectable) {
  					self._hasIcons = true;
  					return false;
  				}
  			});
  
  			return self._super();
  		}
  	});
  
  	return Menu;
  });
  
  // Included from: js/maya/classes/ui/Radio.js
  
  /**
   * Radio.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new radio button.
   *
   * @-x-less Radio.less
   * @class maya.ui.Radio
   * @extends maya.ui.Checkbox
   */
  define("maya/ui/Radio", [
  	"maya/ui/Checkbox"
  ], function(Checkbox) {
  	"use strict";
  
  	return Checkbox.extend({
  		Defaults: {
  			classes: "radio",
  			role: "radio"
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/ResizeHandle.js
  
  /**
   * ResizeHandle.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
   *
   * @-x-less ResizeHandle.less
   * @class maya.ui.ResizeHandle
   * @extends maya.ui.Widget
   */
  define("maya/ui/ResizeHandle", [
  	"maya/ui/Widget",
  	"maya/ui/DragHelper"
  ], function(Widget, DragHelper) {
  	"use strict";
  
  	return Widget.extend({
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, prefix = self.classPrefix;
  
  			self.addClass('resizehandle');
  
  			if (self.settings.direction == "both") {
  				self.addClass('resizehandle-both');
  			}
  
  			self.canFocus = false;
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '">' +
  					'<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this;
  
  			self._super();
  
  			self.resizeDragHelper = new DragHelper(this._id, {
  				start: function() {
  					self.fire('ResizeStart');
  				},
  
  				drag: function(e) {
  					if (self.settings.direction != "both") {
  						e.deltaX = 0;
  					}
  
  					self.fire('Resize', e);
  				},
  
  				stop: function() {
  					self.fire('ResizeEnd');
  				}
  			});
  		},
  
  		remove: function() {
  			if (this.resizeDragHelper) {
  				this.resizeDragHelper.destroy();
  			}
  
  			return this._super();
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Spacer.js
  
  /**
   * Spacer.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a spacer. This control is used in flex layouts for example.
   *
   * @-x-less Spacer.less
   * @class maya.ui.Spacer
   * @extends maya.ui.Widget
   */
  define("maya/ui/Spacer", [
  	"maya/ui/Widget"
  ], function(Widget) {
  	"use strict";
  
  	return Widget.extend({
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this;
  
  			self.addClass('spacer');
  			self.canFocus = false;
  
  			return '<div id="' + self._id + '" class="' + self.classes() + '"></div>';
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/SplitButton.js
  
  /**
   * SplitButton.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a split button.
   *
   * @-x-less SplitButton.less
   * @class maya.ui.SplitButton
   * @extends maya.ui.MenuButton
   */
  define("maya/ui/SplitButton", [
  	"maya/ui/MenuButton",
  	"maya/ui/DomUtils"
  ], function(MenuButton, DomUtils) {
  	return MenuButton.extend({
  		Defaults: {
  			classes: "widget btn splitbtn",
  			role: "button"
  		},
  
  		/**
  		 * Repaints the control after a layout operation.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;
  
  			self._super();
  
  			mainButtonElm = elm.firstChild;
  			menuButtonElm = elm.lastChild;
  
  			DomUtils.css(mainButtonElm, {
  				width: rect.w - DomUtils.getSize(menuButtonElm).width,
  				height: rect.h - 2
  			});
  
  			DomUtils.css(menuButtonElm, {
  				height: rect.h - 2
  			});
  
  			return self;
  		},
  
  		/**
  		 * Sets the active menu state.
  		 *
  		 * @private
  		 */
  		activeMenu: function(state) {
  			var self = this;
  
  			DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state);
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, prefix = self.classPrefix, image;
  			var icon = self.settings.icon;
  
  			image = self.settings.image;
  			if (image) {
  				icon = 'none';
  
  				// Support for [high dpi, low dpi] image sources
  				if (typeof image != "string") {
  					image = window.getSelection ? image[0] : image[1];
  				}
  
  				image = ' style="background-image: url(\'' + image + '\')"';
  			} else {
  				image = '';
  			}
  
  			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  
  			return (
  				'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
  					'<button type="button" hidefocus="1" tabindex="-1">' +
  						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  						(self._text ? (icon ? ' ' : '') + self._text : '') +
  					'</button>' +
  					'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  						//(icon ? '<i class="' + icon + '"></i>' : '') +
  						(self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
  						' <i class="' + prefix + 'caret"></i>' +
  					'</button>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this, onClickHandler = self.settings.onclick;
  
  			self.on('click', function(e) {
  				var node = e.target;
  
  				if (e.control == this) {
  					// Find clicks that is on the main button
  					while (node) {
  						if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
  							e.stopImmediatePropagation();
  							onClickHandler.call(this, e);
  							return;
  						}
  
  						node = node.parentNode;
  					}
  				}
  			});
  
  			delete self.settings.onclick;
  
  			return self._super();
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/StackLayout.js
  
  /**
   * StackLayout.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This layout uses the browsers layout when the items are blocks.
   *
   * @-x-less StackLayout.less
   * @class maya.ui.StackLayout
   * @extends maya.ui.FlowLayout
   */
  define("maya/ui/StackLayout", [
  	"maya/ui/FlowLayout"
  ], function(FlowLayout) {
  	"use strict";
  
  	return FlowLayout.extend({
  		Defaults: {
  			containerClass: 'stack-layout',
  			controlClass: 'stack-layout-item',
  			endClass: 'break'
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/TabPanel.js
  
  /**
   * TabPanel.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a tab panel control.
   *
   * @-x-less TabPanel.less
   * @class maya.ui.TabPanel
   * @extends maya.ui.Panel
   *
   * @setting {Number} activeTab Active tab index.
   */
  define("maya/ui/TabPanel", [
  	"maya/ui/Panel",
  	"maya/ui/DomUtils"
  ], function(Panel, DomUtils) {
  	"use strict";
  
  	return Panel.extend({
  		Defaults: {
  			layout: 'absolute',
  			defaults: {
  				type: 'panel'
  			}
  		},
  
  		/**
  		 * Activates the specified tab by index.
  		 *
  		 * @method activateTab
  		 * @param {Number} idx Index of the tab to activate.
  		 */
  		activateTab: function(idx) {
  			var activeTabElm;
  
  			if (this.activeTabId) {
  				activeTabElm = this.getEl(this.activeTabId);
  				DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
  				activeTabElm.setAttribute('aria-selected', "false");
  			}
  
  			this.activeTabId = 't' + idx;
  
  			activeTabElm = this.getEl('t' + idx);
  			activeTabElm.setAttribute('aria-selected', "true");
  			DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
  
  			this.items()[idx].show().fire('showtab');
  			this.reflow();
  
  			this.items().each(function(item, i) {
  				if (idx != i) {
  					item.hide();
  				}
  			});
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
  
  			self.preRender();
  			layout.preRender(self);
  
  			self.items().each(function(ctrl, i) {
  				var id = self._id + '-t' + i;
  
  				ctrl.aria('role', 'tabpanel');
  				ctrl.aria('labelledby', id);
  
  				tabsHtml += (
  					'<div id="' + id + '" class="' + prefix + 'tab" ' +
  						'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
  						self.encode(ctrl.settings.title) +
  					'</div>'
  				);
  			});
  
  			return (
  				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  					'<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
  						tabsHtml +
  					'</div>' +
  					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  						layout.renderHtml(self) +
  					'</div>' +
  				'</div>'
  			);
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this;
  
  			self._super();
  
  			self.settings.activeTab = self.settings.activeTab || 0;
  			self.activateTab(self.settings.activeTab);
  
  			this.on('click', function(e) {
  				var targetParent = e.target.parentNode;
  
  				if (e.target.parentNode.id == self._id + '-head') {
  					var i = targetParent.childNodes.length;
  
  					while (i--) {
  						if (targetParent.childNodes[i] == e.target) {
  							self.activateTab(i);
  						}
  					}
  				}
  			});
  		},
  
  		/**
  		 * Initializes the current controls layout rect.
  		 * This will be executed by the layout managers to determine the
  		 * default minWidth/minHeight etc.
  		 *
  		 * @method initLayoutRect
  		 * @return {Object} Layout rect instance.
  		 */
  		initLayoutRect: function() {
  			var self = this, rect, minW, minH;
  
  			minW = DomUtils.getSize(self.getEl('head')).width;
  			minW = minW < 0 ? 0 : minW;
  			minH = 0;
  
  			self.items().each(function(item) {
  				minW = Math.max(minW, item.layoutRect().minW);
  				minH = Math.max(minH, item.layoutRect().minH);
  			});
  
  			self.items().each(function(ctrl) {
  				ctrl.settings.x = 0;
  				ctrl.settings.y = 0;
  				ctrl.settings.w = minW;
  				ctrl.settings.h = minH;
  
  				ctrl.layoutRect({
  					x: 0,
  					y: 0,
  					w: minW,
  					h: minH
  				});
  			});
  
  			var headH = DomUtils.getSize(self.getEl('head')).height;
  
  			self.settings.minWidth = minW;
  			self.settings.minHeight = minH + headH;
  
  			rect = self._super();
  			rect.deltaH += headH;
  			rect.innerH = rect.h - rect.deltaH;
  
  			return rect;
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/TextBox.js
  
  /**
   * TextBox.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * Creates a new textbox.
   *
   * @-x-less TextBox.less
   * @class maya.ui.TextBox
   * @extends maya.ui.Widget
   */
  define("maya/ui/TextBox", [
  	"maya/ui/Widget",
  	"maya/ui/DomUtils"
  ], function(Widget, DomUtils) {
  	"use strict";
  
  	return Widget.extend({
  		/**
  		 * Constructs a instance with the specified settings.
  		 *
  		 * @constructor
  		 * @param {Object} settings Name/value object with settings.
  		 * @setting {Boolean} multiline True if the textbox is a multiline control.
  		 * @setting {Number} maxLength Max length for the textbox.
  		 * @setting {Number} size Size of the textbox in characters.
  		 */
  		init: function(settings) {
  			var self = this;
  
  			self._super(settings);
  
  			self._value = settings.value || '';
  			self.addClass('textbox');
  
  			if (settings.multiline) {
  				self.addClass('multiline');
  			} else {
  				// TODO: Rework this
  				self.on('keydown', function(e) {
  					if (e.keyCode == 13) {
  						self.parents().reverse().each(function(ctrl) {
  							e.preventDefault();
  
  							if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  								ctrl.fire('submit', {data: ctrl.toJSON()});
  								return false;
  							}
  						});
  					}
  				});
  			}
  		},
  
  		/**
  		 * Getter/setter function for the disabled state.
  		 *
  		 * @method value
  		 * @param {Boolean} [state] State to be set.
  		 * @return {Boolean|maya.ui.ComboBox} True/false or self if it's a set operation.
  		 */
  		disabled: function(state) {
  			var self = this;
  
  			if (self._rendered && typeof state != 'undefined') {
  				self.getEl().disabled = state;
  			}
  
  			return self._super(state);
  		},
  
  		/**
  		 * Getter/setter function for the control value.
  		 *
  		 * @method value
  		 * @param {String} [value] Value to be set.
  		 * @return {String|maya.ui.ComboBox} Value or self if it's a set operation.
  		 */
  		value: function(value) {
  			var self = this;
  
  			if (typeof value != "undefined") {
  				self._value = value;
  
  				if (self._rendered) {
  					self.getEl().value = value;
  				}
  
  				return self;
  			}
  
  			if (self._rendered) {
  				return self.getEl().value;
  			}
  
  			return self._value;
  		},
  
  		/**
  		 * Repaints the control after a layout operation.
  		 *
  		 * @method repaint
  		 */
  		repaint: function() {
  			var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
  
  			style = self.getEl().style;
  			rect = self._layoutRect;
  			lastRepaintRect = self._lastRepaintRect || {};
  
  			// Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  			var doc = document;
  			if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  				style.lineHeight = (rect.h - borderH) + 'px';
  			}
  
  			borderBox = self._borderBox;
  			borderW = borderBox.left + borderBox.right + 8;
  			borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
  
  			if (rect.x !== lastRepaintRect.x) {
  				style.left = rect.x + 'px';
  				lastRepaintRect.x = rect.x;
  			}
  
  			if (rect.y !== lastRepaintRect.y) {
  				style.top = rect.y + 'px';
  				lastRepaintRect.y = rect.y;
  			}
  
  			if (rect.w !== lastRepaintRect.w) {
  				style.width = (rect.w - borderW) + 'px';
  				lastRepaintRect.w = rect.w;
  			}
  
  			if (rect.h !== lastRepaintRect.h) {
  				style.height = (rect.h - borderH) + 'px';
  				lastRepaintRect.h = rect.h;
  			}
  
  			self._lastRepaintRect = lastRepaintRect;
  			self.fire('repaint', {}, false);
  
  			return self;
  		},
  
  		/**
  		 * Renders the control as a HTML string.
  		 *
  		 * @method renderHtml
  		 * @return {String} HTML representing the control.
  		 */
  		renderHtml: function() {
  			var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = '';
  
  			if ("spellcheck" in settings) {
  				extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  			}
  
  			if (settings.maxLength) {
  				extraAttrs += ' maxlength="' + settings.maxLength + '"';
  			}
  
  			if (settings.size) {
  				extraAttrs += ' size="' + settings.size + '"';
  			}
  
  			if (settings.subtype) {
  				extraAttrs += ' type="' + settings.subtype + '"';
  			}
  
  			if (self.disabled()) {
  				extraAttrs += ' disabled="disabled"';
  			}
  
  			if (settings.multiline) {
  				return (
  					'<textarea id="' + id + '" class="' + self.classes() + '" ' +
  					(settings.rows ? ' rows="' + settings.rows + '"' : '') +
  					' hidefocus="1"' + extraAttrs + '>' + value +
  					'</textarea>'
  				);
  			}
  
  			return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
  		},
  
  		/**
  		 * Called after the control has been rendered.
  		 *
  		 * @method postRender
  		 */
  		postRender: function() {
  			var self = this;
  
  			DomUtils.on(self.getEl(), 'change', function(e) {
  				self.fire('change', e);
  			});
  
  			return self._super();
  		},
  
  		remove: function() {
  			DomUtils.off(this.getEl());
  			this._super();
  		}
  	});
  });
  
  // Included from: js/maya/classes/ui/Throbber.js
  
  /**
   * Throbber.js
   *
   * Copyright, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://www.maya.com/license
   * Contributing: http://www.maya.com/contributing
   */
  
  /**
   * This class enables you to display a Throbber for any element.
   *
   * @-x-less Throbber.less
   * @class maya.ui.Throbber
   */
  define("maya/ui/Throbber", [
  	"maya/ui/DomUtils",
  	"maya/ui/Control"
  ], function(DomUtils, Control) {
  	"use strict";
  
  	/**
  	 * Constructs a new throbber.
  	 *
  	 * @constructor
  	 * @param {Element} elm DOM Html element to display throbber in.
  	 * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
  	 */
  	return function(elm, inline) {
  		var self = this, state, classPrefix = Control.classPrefix;
  
  		/**
  		 * Shows the throbber.
  		 *
  		 * @method show
  		 * @param {Number} [time] Time to wait before showing.
  		 * @return {maya.ui.Throbber} Current throbber instance.
  		 */
  		self.show = function(time) {
  			self.hide();
  
  			state = true;
  
  			window.setTimeout(function() {
  				if (state) {
  					elm.appendChild(DomUtils.createFragment(
  						'<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
  					));
  				}
  			}, time || 0);
  
  			return self;
  		};
  
  		/**
  		 * Hides the throbber.
  		 *
  		 * @method hide
  		 * @return {maya.ui.Throbber} Current throbber instance.
  		 */
  		self.hide = function() {
  			var child = elm.lastChild;
  
  			if (child && child.className.indexOf('throbber') != -1) {
  				child.parentNode.removeChild(child);
  			}
  
  			state = false;
  
  			return self;
  		};
  	};
  });
  
  expose(["maya/dom/EventUtils","maya/dom/Sizzle","maya/Env","maya/util/Tools","maya/dom/DomQuery","maya/html/Styles","maya/dom/TreeWalker","maya/dom/Range","maya/html/Entities","maya/dom/DOMUtils","maya/dom/ScriptLoader","maya/AddOnManager","maya/dom/RangeUtils","maya/html/Node","maya/html/Schema","maya/html/SaxParser","maya/html/DomParser","maya/html/Writer","maya/html/Serializer","maya/dom/Serializer","maya/dom/TridentSelection","maya/util/VK","maya/dom/ControlSelection","maya/dom/BookmarkManager","maya/dom/Selection","maya/dom/ElementUtils","maya/Formatter","maya/UndoManager","maya/EnterKey","maya/ForceBlocks","maya/EditorCommands","maya/util/URI","maya/util/Class","maya/util/EventDispatcher","maya/ui/Selector","maya/ui/Collection","maya/ui/DomUtils","maya/ui/Control","maya/ui/Factory","maya/ui/KeyboardNavigation","maya/ui/Container","maya/ui/DragHelper","maya/ui/Scrollable","maya/ui/Panel","maya/ui/Movable","maya/ui/Resizable","maya/ui/FloatPanel","maya/ui/Window","maya/ui/MessageBox","maya/WindowManager","maya/util/Quirks","maya/util/Observable","maya/EditorObservable","maya/Shortcuts","maya/Editor","maya/util/I18n","maya/FocusManager","maya/EditorManager","maya/LegacyInput","maya/util/XHR","maya/util/JSON","maya/util/JSONRequest","maya/util/JSONP","maya/util/LocalStorage","maya/Compat","maya/ui/Layout","maya/ui/AbsoluteLayout","maya/ui/Tooltip","maya/ui/Widget","maya/ui/Button","maya/ui/ButtonGroup","maya/ui/Checkbox","maya/ui/ComboBox","maya/ui/ColorBox","maya/ui/PanelButton","maya/ui/ColorButton","maya/util/Color","maya/ui/ColorPicker","maya/ui/Path","maya/ui/ElementPath","maya/ui/FormItem","maya/ui/Form","maya/ui/FieldSet","maya/ui/FilePicker","maya/ui/FitLayout","maya/ui/FlexLayout","maya/ui/FlowLayout","maya/ui/FormatControls","maya/ui/GridLayout","maya/ui/Iframe","maya/ui/Label","maya/ui/Toolbar","maya/ui/MenuBar","maya/ui/MenuButton","maya/ui/ListBox","maya/ui/MenuItem","maya/ui/Menu","maya/ui/Radio","maya/ui/ResizeHandle","maya/ui/Spacer","maya/ui/SplitButton","maya/ui/StackLayout","maya/ui/TabPanel","maya/ui/TextBox","maya/ui/Throbber"]);
  })(this);