/*!
 * This file is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */;(function() {
/*
    http://www.JSON.org/json2.js
    2011-02-23

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html


    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.


    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.
*/

/*jslint evil: true, strict: false, regexp: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist.
var JSON;
if (!JSON) {
	JSON = {};
}

(function () {
	

	function f(n) {
		// Format integers to have at least two digits.
		return n < 10 ? '0' + n : n;
	}

	if (typeof Date.prototype.toJSON !== 'function') {

		Date.prototype.toJSON = function (key) {

			return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null;
		};

		String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) {
			return this.valueOf();
		};
	}

	var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		gap,
		indent,
		meta = { // table of character substitutions
			'\b': '\\b',
			'\t': '\\t',
			'\n': '\\n',
			'\f': '\\f',
			'\r': '\\r',
			'"': '\\"',
			'\\': '\\\\'
		},
		rep;


	function quote(string) {

		// If the string contains no control characters, no quote characters, and no
		// backslash characters, then we can safely slap some quotes around it.
		// Otherwise we must also replace the offending characters with safe escape
		// sequences.

		escapable.lastIndex = 0;
		return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
			var c = meta[a];
			return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
		}) + '"' : '"' + string + '"';
	}


	function str(key, holder) {

		// Produce a string from holder[key].

		var i, // The loop counter.
		k, // The member key.
		v, // The member value.
		length,
		mind = gap,
			partial,
			value = holder[key];

		// If the value has a toJSON method, call it to obtain a replacement value.

		if (value && typeof value === 'object' && typeof value.toJSON === 'function') {
			value = value.toJSON(key);
		}

		// If we were called with a replacer function, then call the replacer to
		// obtain a replacement value.

		if (typeof rep === 'function') {
			value = rep.call(holder, key, value);
		}

		// What happens next depends on the value's type.

		switch (typeof value) {
		case 'string':
			return quote(value);

		case 'number':

			// JSON numbers must be finite. Encode non-finite numbers as null.

			return isFinite(value) ? String(value) : 'null';

		case 'boolean':
		case 'null':

			// If the value is a boolean or null, convert it to a string. Note:
			// typeof null does not produce 'null'. The case is included here in
			// the remote chance that this gets fixed someday.

			return String(value);

			// If the type is 'object', we might be dealing with an object or an array or
			// null.

		case 'object':

			// Due to a specification blunder in ECMAScript, typeof null is 'object',
			// so watch out for that case.

			if (!value) {
				return 'null';
			}

			// Make an array to hold the partial results of stringifying this object value.

			gap += indent;
			partial = [];

			// Is the value an array?

			if (Object.prototype.toString.apply(value) === '[object Array]') {

				// The value is an array. Stringify every element. Use null as a placeholder
				// for non-JSON values.

				length = value.length;
				for (i = 0; i < length; i += 1) {
					partial[i] = str(i, value) || 'null';
				}

				// Join all of the elements together, separated with commas, and wrap them in
				// brackets.

				v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']';
				gap = mind;
				return v;
			}

			// If the replacer is an array, use it to select the members to be stringified.

			if (rep && typeof rep === 'object') {
				length = rep.length;
				for (i = 0; i < length; i += 1) {
					if (typeof rep[i] === 'string') {
						k = rep[i];
						v = str(k, value);
						if (v) {
							partial.push(quote(k) + (gap ? ': ' : ':') + v);
						}
					}
				}
			} else {

				// Otherwise, iterate through all of the keys in the object.

				for (k in value) {
					if (Object.prototype.hasOwnProperty.call(value, k)) {
						v = str(k, value);
						if (v) {
							partial.push(quote(k) + (gap ? ': ' : ':') + v);
						}
					}
				}
			}

			// Join all of the member texts together, separated with commas,
			// and wrap them in braces.

			v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}';
			gap = mind;
			return v;
		}
	}

	// If the JSON object does not yet have a stringify method, give it one.

	if (typeof JSON.stringify !== 'function') {
		JSON.stringify = function (value, replacer, space) {

			// The stringify method takes a value and an optional replacer, and an optional
			// space parameter, and returns a JSON text. The replacer can be a function
			// that can replace values, or an array of strings that will select the keys.
			// A default replacer method can be provided. Use of the space parameter can
			// produce text that is more easily readable.

			var i;
			gap = '';
			indent = '';

			// If the space parameter is a number, make an indent string containing that
			// many spaces.

			if (typeof space === 'number') {
				for (i = 0; i < space; i += 1) {
					indent += ' ';
				}

				// If the space parameter is a string, it will be used as the indent string.

			} else if (typeof space === 'string') {
				indent = space;
			}

			// If there is a replacer, it must be a function or an array.
			// Otherwise, throw an error.

			rep = replacer;
			if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) {
				throw new Error('JSON.stringify');
			}

			// Make a fake root object containing our value under the key of ''.
			// Return the result of stringifying the value.

			return str('', {
				'': value
			});
		};
	}


	// If the JSON object does not yet have a parse method, give it one.

	if (typeof JSON.parse !== 'function') {
		JSON.parse = function (text, reviver) {

			// The parse method takes a text and an optional reviver function, and returns
			// a JavaScript value if the text is a valid JSON text.

			var j;

			function walk(holder, key) {

				// The walk method is used to recursively walk the resulting structure so
				// that modifications can be made.

				var k, v, value = holder[key];
				if (value && typeof value === 'object') {
					for (k in value) {
						if (Object.prototype.hasOwnProperty.call(value, k)) {
							v = walk(value, k);
							if (v !== undefined) {
								value[k] = v;
							} else {
								delete value[k];
							}
						}
					}
				}
				return reviver.call(holder, key, value);
			}


			// Parsing happens in four stages. In the first stage, we replace certain
			// Unicode characters with escape sequences. JavaScript handles many characters
			// incorrectly, either silently deleting them, or treating them as line endings.

			text = String(text);
			cx.lastIndex = 0;
			if (cx.test(text)) {
				text = text.replace(cx, function (a) {
					return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
				});
			}

			// In the second stage, we run the text against regular expressions that look
			// for non-JSON patterns. We are especially concerned with '()' and 'new'
			// because they can cause invocation, and '=' because it can cause mutation.
			// But just to be safe, we want to reject all unexpected forms.

			// We split the second stage into 4 regexp operations in order to work around
			// crippling inefficiencies in IE's and Safari's regexp engines. First we
			// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
			// replace all simple value tokens with ']' characters. Third, we delete all
			// open brackets that follow a colon or comma or that begin the text. Finally,
			// we look to see that the remaining characters are only whitespace or ']' or
			// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

			if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

				// In the third stage we use the eval function to compile the text into a
				// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
				// in JavaScript: it can begin a block or an object literal. We wrap the text
				// in parens to eliminate the ambiguity.

				j = eval('(' + text + ')');

				// In the optional fourth stage, we recursively walk the new structure, passing
				// each name/value pair to a reviver function for possible transformation.

				return typeof reviver === 'function' ? walk({
					'': j
				}, '') : j;
			}

			// If the text is not JSON parseable, then a SyntaxError is thrown.

			throw new SyntaxError('JSON.parse');
		};
	}
}());

define('util/json2', [], function () {
	return JSON;
});

/**
 * @license Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Copyright 2011, Tim Down
 * Licensed under the MIT license.
 * Version: 1.2.1
 * Build date: 8 October 2011
 */
(function () {
	var rangy = (function () {


		var OBJECT = "object",
			FUNCTION = "function",
			UNDEFINED = "undefined";

		var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"];

		var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];

		var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];

		// Subset of TextRange's full set of methods that we're interested in
		var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark", "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"];

		/*----------------------------------------------------------------------------------------------------------------*/

		// Trio of functions taken from Peter Michaux's article:
		// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
		function isHostMethod(o, p) {
			var t = typeof o[p];
			return t == FUNCTION || ( !! (t == OBJECT && o[p])) || t == "unknown";
		}

		function isHostObject(o, p) {
			return !!(typeof o[p] == OBJECT && o[p]);
		}

		function isHostProperty(o, p) {
			return typeof o[p] != UNDEFINED;
		}

		// Creates a convenience function to save verbose repeated calls to tests functions
		function createMultiplePropertyTest(testFunc) {
			return function (o, props) {
				var i = props.length;
				while (i--) {
					if (!testFunc(o, props[i])) {
						return false;
					}
				}
				return true;
			};
		}

		// Next trio of functions are a convenience to save verbose repeated calls to previous two functions
		var areHostMethods = createMultiplePropertyTest(isHostMethod);
		var areHostObjects = createMultiplePropertyTest(isHostObject);
		var areHostProperties = createMultiplePropertyTest(isHostProperty);

		function isTextRange(range) {
			return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
		}

		var api = {
			version: "1.2.1",
			initialized: false,
			supported: true,

			util: {
				isHostMethod: isHostMethod,
				isHostObject: isHostObject,
				isHostProperty: isHostProperty,
				areHostMethods: areHostMethods,
				areHostObjects: areHostObjects,
				areHostProperties: areHostProperties,
				isTextRange: isTextRange
			},

			features: {},

			modules: {},
			config: {
				alertOnWarn: false,
				preferTextRange: false
			}
		};

		function fail(reason) {
			window.alert("Rangy not supported in your browser. Reason: " + reason);
			api.initialized = true;
			api.supported = false;
		}

		api.fail = fail;

		function warn(msg) {
			var warningMessage = "Rangy warning: " + msg;
			if (api.config.alertOnWarn) {
				window.alert(warningMessage);
			} else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) {
				window.console.log(warningMessage);
			}
		}

		api.warn = warn;

		if ({}.hasOwnProperty) {
			api.util.extend = function (o, props) {
				for (var i in props) {
					if (props.hasOwnProperty(i)) {
						o[i] = props[i];
					}
				}
			};
		} else {
			fail("hasOwnProperty not supported");
		}

		var initListeners = [];
		var moduleInitializers = [];

		// Initialization
		function init() {
			if (api.initialized) {
				return;
			}
			var testRange;
			var implementsDomRange = false,
				implementsTextRange = false;

			// First, perform basic feature tests

			if (isHostMethod(document, "createRange")) {
				testRange = document.createRange();
				if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
					implementsDomRange = true;
				}
				testRange.detach();
			}

			var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];

			if (body && isHostMethod(body, "createTextRange")) {
				testRange = body.createTextRange();
				if (isTextRange(testRange)) {
					implementsTextRange = true;
				}
			}

			if (!implementsDomRange && !implementsTextRange) {
				fail("Neither Range nor TextRange are implemented");
			}

			api.initialized = true;
			api.features = {
				implementsDomRange: implementsDomRange,
				implementsTextRange: implementsTextRange
			};

			// Initialize modules and call init listeners
			var allListeners = moduleInitializers.concat(initListeners);
			for (var i = 0, len = allListeners.length; i < len; ++i) {
				try {
					allListeners[i](api);
				} catch (ex) {
					if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
						window.console.log("Init listener threw an exception. Continuing.", ex);
					}

				}
			}
		}

		// Allow external scripts to initialize this library in case it's loaded after the document has loaded
		api.init = init;

		// Execute listener immediately if already initialized
		api.addInitListener = function (listener) {
			if (api.initialized) {
				listener(api);
			} else {
				initListeners.push(listener);
			}
		};

		var createMissingNativeApiListeners = [];

		api.addCreateMissingNativeApiListener = function (listener) {
			createMissingNativeApiListeners.push(listener);
		};

		function createMissingNativeApi(win) {
			win = win || window;
			init();

			// Notify listeners
			for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
				createMissingNativeApiListeners[i](win);
			}
		}

		api.createMissingNativeApi = createMissingNativeApi;

		/**
		 * @constructor
		 */
		function Module(name) {
			this.name = name;
			this.initialized = false;
			this.supported = false;
		}

		Module.prototype.fail = function (reason) {
			this.initialized = true;
			this.supported = false;

			throw new Error("Module '" + this.name + "' failed to load: " + reason);
		};

		Module.prototype.warn = function (msg) {
			api.warn("Module " + this.name + ": " + msg);
		};

		Module.prototype.createError = function (msg) {
			return new Error("Error in Rangy " + this.name + " module: " + msg);
		};

		api.createModule = function (name, initFunc) {
			var module = new Module(name);
			api.modules[name] = module;

			moduleInitializers.push(function (api) {
				initFunc(api, module);
				module.initialized = true;
				module.supported = true;
			});
		};

		api.requireModules = function (modules) {
			for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) {
				moduleName = modules[i];
				module = api.modules[moduleName];
				if (!module || !(module instanceof Module)) {
					throw new Error("Module '" + moduleName + "' not found");
				}
				if (!module.supported) {
					throw new Error("Module '" + moduleName + "' not supported");
				}
			}
		};

		/*----------------------------------------------------------------------------------------------------------------*/

		// Wait for document to load before running tests

		var docReady = false;

		var loadHandler = function (e) {

			if (!docReady) {
				docReady = true;
				if (!api.initialized) {
					init();
				}
			}
		};

		// Test whether we have window and document objects that we will need
		if (typeof window == UNDEFINED) {
			fail("No window found");
			return;
		}
		if (typeof document == UNDEFINED) {
			fail("No document found");
			return;
		}

		if (isHostMethod(document, "addEventListener")) {
			document.addEventListener("DOMContentLoaded", loadHandler, false);
		}

		// Add a fallback in case the DOMContentLoaded event isn't supported
		if (isHostMethod(window, "addEventListener")) {
			window.addEventListener("load", loadHandler, false);
		} else if (isHostMethod(window, "attachEvent")) {
			window.attachEvent("onload", loadHandler);
		} else {
			fail("Window does not have required addEventListener or attachEvent method");
		}

		return api;
	})();
	rangy.createModule("DomUtil", function (api, module) {

		var UNDEF = "undefined";
		var util = api.util;

		// Perform feature tests
		if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
			module.fail("document missing a Node creation method");
		}

		if (!util.isHostMethod(document, "getElementsByTagName")) {
			module.fail("document missing getElementsByTagName method");
		}

		var el = document.createElement("div");
		if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
			module.fail("Incomplete Element implementation");
		}

		// innerHTML is required for Range's createContextualFragment method
		if (!util.isHostProperty(el, "innerHTML")) {
			module.fail("Element is missing innerHTML property");
		}

		var textNode = document.createTextNode("test");
		if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || !util.areHostProperties(textNode, ["data"]))) {
			module.fail("Incomplete Text Node implementation");
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
		// able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
		// contains just the document as a single element and the value searched for is the document.
		var arrayContains =
		/*Array.prototype.indexOf ?
        function(arr, val) {
            return arr.indexOf(val) > -1;
        }:*/

			function (arr, val) {
				var i = arr.length;
				while (i--) {
					if (arr[i] === val) {
						return true;
					}
				}
				return false;
			};

		// Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
		function isHtmlNamespace(node) {
			var ns;
			return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
		}

		function parentElement(node) {
			var parent = node.parentNode;
			return (parent.nodeType == 1) ? parent : null;
		}

		function getNodeIndex(node) {
			var i = 0;
			while ((node = node.previousSibling)) {
				i++;
			}
			return i;
		}

		function getNodeLength(node) {
			var childNodes;
			return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0);
		}

		function getCommonAncestor(node1, node2) {
			var ancestors = [],
				n;
			for (n = node1; n; n = n.parentNode) {
				ancestors.push(n);
			}

			for (n = node2; n; n = n.parentNode) {
				if (arrayContains(ancestors, n)) {
					return n;
				}
			}

			return null;
		}

		function isAncestorOf(ancestor, descendant, selfIsAncestor) {
			var n = selfIsAncestor ? descendant : descendant.parentNode;
			while (n) {
				if (n === ancestor) {
					return true;
				} else {
					n = n.parentNode;
				}
			}
			return false;
		}

		function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
			var p, n = selfIsAncestor ? node : node.parentNode;
			while (n) {
				p = n.parentNode;
				if (p === ancestor) {
					return n;
				}
				n = p;
			}
			return null;
		}

		function isCharacterDataNode(node) {
			var t = node.nodeType;
			return t == 3 || t == 4 || t == 8; // Text, CDataSection or Comment
		}

		function insertAfter(node, precedingNode) {
			var nextNode = precedingNode.nextSibling,
				parent = precedingNode.parentNode;
			if (nextNode) {
				parent.insertBefore(node, nextNode);
			} else {
				parent.appendChild(node);
			}
			return node;
		}

		// Note that we cannot use splitText() because it is bugridden in IE 9.
		function splitDataNode(node, index) {
			var newNode = node.cloneNode(false);
			newNode.deleteData(0, index);
			node.deleteData(index, node.length - index);
			insertAfter(newNode, node);
			return newNode;
		}

		function getDocument(node) {
			if (node.nodeType == 9) {
				return node;
			} else if (typeof node.ownerDocument != UNDEF) {
				return node.ownerDocument;
			} else if (typeof node.document != UNDEF) {
				return node.document;
			} else if (node.parentNode) {
				return getDocument(node.parentNode);
			} else {
				throw new Error("getDocument: no document found for node");
			}
		}

		function getWindow(node) {
			var doc = getDocument(node);
			if (typeof doc.defaultView != UNDEF) {
				return doc.defaultView;
			} else if (typeof doc.parentWindow != UNDEF) {
				return doc.parentWindow;
			} else {
				throw new Error("Cannot get a window object for node");
			}
		}

		function getIframeDocument(iframeEl) {
			if (typeof iframeEl.contentDocument != UNDEF) {
				return iframeEl.contentDocument;
			} else if (typeof iframeEl.contentWindow != UNDEF) {
				return iframeEl.contentWindow.document;
			} else {
				throw new Error("getIframeWindow: No Document object found for iframe element");
			}
		}

		function getIframeWindow(iframeEl) {
			if (typeof iframeEl.contentWindow != UNDEF) {
				return iframeEl.contentWindow;
			} else if (typeof iframeEl.contentDocument != UNDEF) {
				return iframeEl.contentDocument.defaultView;
			} else {
				throw new Error("getIframeWindow: No Window object found for iframe element");
			}
		}

		function getBody(doc) {
			return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
		}

		function getRootContainer(node) {
			var parent;
			while ((parent = node.parentNode)) {
				node = parent;
			}
			return node;
		}

		/**
		 * This is a very ugly workaround for an IE9 issue Before comparing DOM
		 * elements "normalize" them. There are cases, where anchorNode and
		 * focusNode in a nativeselection point to DOM elements with same
		 * parentNode, same previousSibling and same nextSibling, but the nodes
		 * themselves are not the same
		 * If such nodes are compared in the comparePoints method, an error occurs.
		 * To fix this, we move to the previousSibling/nextSibling/parentNode and back, to hopefully get
		 * the "correct" node in the DOM
		 * @param node node to fix
		 * @return normalized node
		 */
		function fixNode(node) {
			if (!node) {
				return;
			}
			if (node.previousSibling) {
				return node.previousSibling.nextSibling;
			} else if (node.nextSibling) {
				return node.nextSibling.previousSibling;
			} else if (node.parentNode) {
				return node.parentNode.firstChild;
			} else {
				return node;
			}
		}

		function comparePoints(nodeA, offsetA, nodeB, offsetB) {
			// fix the nodes before comparing them
			nodeA = fixNode(nodeA);
			nodeB = fixNode(nodeB);
			// See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
			var nodeC, root, childA, childB, n;
			if (nodeA == nodeB) {

				// Case 1: nodes are the same
				return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
			} else if ((nodeC = getClosestAncestorIn(nodeB, nodeA, true))) {

				// Case 2: node C (container B or an ancestor) is a child node of A
				return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
			} else if ((nodeC = getClosestAncestorIn(nodeA, nodeB, true))) {

				// Case 3: node C (container A or an ancestor) is a child node of B
				return getNodeIndex(nodeC) < offsetB ? -1 : 1;
			} else {

				// Case 4: containers are siblings or descendants of siblings
				root = getCommonAncestor(nodeA, nodeB);
				childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
				childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);

				if (childA === childB) {
					// This shouldn't be possible

					throw new Error("comparePoints got to case 4 and childA and childB are the same!");
				} else {
					n = root.firstChild;
					while (n) {
						if (n === childA) {
							return -1;
						} else if (n === childB) {
							return 1;
						}
						n = n.nextSibling;
					}
					throw new Error("Should not be here!");
				}
			}
		}

		function fragmentFromNodeChildren(node) {
			var fragment = getDocument(node).createDocumentFragment(),
				child;
			while ((child = node.firstChild)) {
				fragment.appendChild(child);
			}
			return fragment;
		}

		function inspectNode(node) {
			if (!node) {
				return "[No node]";
			}
			if (isCharacterDataNode(node)) {
				return '"' + node.data + '"';
			} else if (node.nodeType == 1) {
				var idAttr = node.id ? ' id="' + node.id + '"' : "";
				return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]";
			} else {
				return node.nodeName;
			}
		}

		/**
		 * @constructor
		 */
		function NodeIterator(root) {
			this.root = root;
			this._next = root;
		}

		NodeIterator.prototype = {
			_current: null,

			hasNext: function () {
				return !!this._next;
			},

			next: function () {
				var n = this._current = this._next;
				var child, next;
				if (this._current) {
					child = n.firstChild;
					if (child) {
						this._next = child;
					} else {
						next = null;
						while ((n !== this.root) && !(next = n.nextSibling)) {
							n = n.parentNode;
						}
						this._next = next;
					}
				}
				return this._current;
			},

			detach: function () {
				this._current = this._next = this.root = null;
			}
		};

		function createIterator(root) {
			return new NodeIterator(root);
		}

		/**
		 * @constructor
		 */
		function DomPosition(node, offset) {
			this.node = node;
			this.offset = offset;
		}

		DomPosition.prototype = {
			equals: function (pos) {
				return this.node === pos.node & this.offset == pos.offset;
			},

			inspect: function () {
				return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
			}
		};

		/**
		 * @constructor
		 */
		function DOMException(codeName) {
			this.code = this[codeName];
			this.codeName = codeName;
			this.message = "DOMException: " + this.codeName;
		}

		DOMException.prototype = {
			INDEX_SIZE_ERR: 1,
			HIERARCHY_REQUEST_ERR: 3,
			WRONG_DOCUMENT_ERR: 4,
			NO_MODIFICATION_ALLOWED_ERR: 7,
			NOT_FOUND_ERR: 8,
			NOT_SUPPORTED_ERR: 9,
			INVALID_STATE_ERR: 11
		};

		DOMException.prototype.toString = function () {
			return this.message;
		};

		api.dom = {
			arrayContains: arrayContains,
			isHtmlNamespace: isHtmlNamespace,
			parentElement: parentElement,
			getNodeIndex: getNodeIndex,
			getNodeLength: getNodeLength,
			getCommonAncestor: getCommonAncestor,
			isAncestorOf: isAncestorOf,
			getClosestAncestorIn: getClosestAncestorIn,
			isCharacterDataNode: isCharacterDataNode,
			insertAfter: insertAfter,
			splitDataNode: splitDataNode,
			getDocument: getDocument,
			getWindow: getWindow,
			getIframeWindow: getIframeWindow,
			getIframeDocument: getIframeDocument,
			getBody: getBody,
			getRootContainer: getRootContainer,
			comparePoints: comparePoints,
			inspectNode: inspectNode,
			fragmentFromNodeChildren: fragmentFromNodeChildren,
			createIterator: createIterator,
			DomPosition: DomPosition
		};

		api.DOMException = DOMException;
	});
	rangy.createModule("DomRange", function (api, module) {
		api.requireModules(["DomUtil"]);


		var dom = api.dom;
		var DomPosition = dom.DomPosition;
		var DOMException = api.DOMException;

		/*----------------------------------------------------------------------------------------------------------------*/

		// Utility functions

		function isNonTextPartiallySelected(node, range) {
			return (node.nodeType != 3) && (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true));
		}

		function getRangeDocument(range) {
			return dom.getDocument(range.startContainer);
		}

		function dispatchEvent(range, type, args) {
			var listeners = range._listeners[type];
			if (listeners) {
				for (var i = 0, len = listeners.length; i < len; ++i) {
					listeners[i].call(range, {
						target: range,
						args: args
					});
				}
			}
		}

		function getBoundaryBeforeNode(node) {
			return new DomPosition(node.parentNode, dom.getNodeIndex(node));
		}

		function getBoundaryAfterNode(node) {
			return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1);
		}

		function insertNodeAtPosition(node, n, o) {
			var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
			if (dom.isCharacterDataNode(n)) {
				if (o == n.length) {
					dom.insertAfter(node, n);
				} else {
					n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o));
				}
			} else if (o >= n.childNodes.length) {
				n.appendChild(node);
			} else {
				n.insertBefore(node, n.childNodes[o]);
			}
			return firstNodeInserted;
		}

		function cloneSubtree(iterator) {
			var partiallySelected;
			for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next();) {
				partiallySelected = iterator.isPartiallySelectedSubtree();

				node = node.cloneNode(!partiallySelected);
				if (partiallySelected) {
					subIterator = iterator.getSubtreeIterator();
					node.appendChild(cloneSubtree(subIterator));
					subIterator.detach(true);
				}

				if (node.nodeType == 10) { // DocumentType
					throw new DOMException("HIERARCHY_REQUEST_ERR");
				}
				frag.appendChild(node);
			}
			return frag;
		}

		function iterateSubtree(rangeIterator, func, iteratorState) {
			var it, n;
			iteratorState = iteratorState || {
				stop: false
			};
			for (var node, subRangeIterator; node = rangeIterator.next();) {
				//log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node));
				if (rangeIterator.isPartiallySelectedSubtree()) {
					// The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the
					// node selected by the Range.
					if (func(node) === false) {
						iteratorState.stop = true;
						return;
					} else {
						subRangeIterator = rangeIterator.getSubtreeIterator();
						iterateSubtree(subRangeIterator, func, iteratorState);
						subRangeIterator.detach(true);
						if (iteratorState.stop) {
							return;
						}
					}
				} else {
					// The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
					// descendant
					it = dom.createIterator(node);
					while ((n = it.next())) {
						if (func(n) === false) {
							iteratorState.stop = true;
							return;
						}
					}
				}
			}
		}

		function deleteSubtree(iterator) {
			var subIterator;
			while (iterator.next()) {
				if (iterator.isPartiallySelectedSubtree()) {
					subIterator = iterator.getSubtreeIterator();
					deleteSubtree(subIterator);
					subIterator.detach(true);
				} else {
					iterator.remove();
				}
			}
		}

		function extractSubtree(iterator) {

			for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next();) {


				if (iterator.isPartiallySelectedSubtree()) {
					node = node.cloneNode(false);
					subIterator = iterator.getSubtreeIterator();
					node.appendChild(extractSubtree(subIterator));
					subIterator.detach(true);
				} else {
					iterator.remove();
				}
				if (node.nodeType == 10) { // DocumentType
					throw new DOMException("HIERARCHY_REQUEST_ERR");
				}
				frag.appendChild(node);
			}
			return frag;
		}

		function getNodesInRange(range, nodeTypes, filter) {
			//log.info("getNodesInRange, " + nodeTypes.join(","));
			var filterNodeTypes = !! (nodeTypes && nodeTypes.length),
				regex;
			var filterExists = !! filter;
			if (filterNodeTypes) {
				regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
			}

			var nodes = [];
			iterateSubtree(new RangeIterator(range, false), function (node) {
				if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
					nodes.push(node);
				}
			});
			return nodes;
		}

		function inspect(range) {
			var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
			return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)

		/**
		 * @constructor
		 */
		function RangeIterator(range, clonePartiallySelectedTextNodes) {
			this.range = range;
			this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;



			if (!range.collapsed) {
				this.sc = range.startContainer;
				this.so = range.startOffset;
				this.ec = range.endContainer;
				this.eo = range.endOffset;
				var root = range.commonAncestorContainer;

				if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) {
					this.isSingleCharacterDataNode = true;
					this._first = this._last = this._next = this.sc;
				} else {
					this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ? this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true);
					this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ? this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true);
				}

			}
		}

		RangeIterator.prototype = {
			_current: null,
			_next: null,
			_first: null,
			_last: null,
			isSingleCharacterDataNode: false,

			reset: function () {
				this._current = null;
				this._next = this._first;
			},

			hasNext: function () {
				return !!this._next;
			},

			next: function () {
				// Move to next node
				var current = this._current = this._next;
				if (current) {
					this._next = (current !== this._last) ? current.nextSibling : null;

					// Check for partially selected text nodes
					if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
						if (current === this.ec) {

							(current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
						}
						if (this._current === this.sc) {

							(current = current.cloneNode(true)).deleteData(0, this.so);
						}
					}
				}

				return current;
			},

			remove: function () {
				var current = this._current,
					start, end;

				if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
					start = (current === this.sc) ? this.so : 0;
					end = (current === this.ec) ? this.eo : current.length;
					if (start != end) {
						current.deleteData(start, end - start);
					}
				} else {
					if (current.parentNode) {
						current.parentNode.removeChild(current);
					} else {

					}
				}
			},

			// Checks if the current node is partially selected
			isPartiallySelectedSubtree: function () {
				var current = this._current;
				return isNonTextPartiallySelected(current, this.range);
			},

			getSubtreeIterator: function () {
				var subRange;
				if (this.isSingleCharacterDataNode) {
					subRange = this.range.cloneRange();
					subRange.collapse();
				} else {
					subRange = new Range(getRangeDocument(this.range));
					var current = this._current;
					var startContainer = current,
						startOffset = 0,
						endContainer = current,
						endOffset = dom.getNodeLength(current);

					if (dom.isAncestorOf(current, this.sc, true)) {
						startContainer = this.sc;
						startOffset = this.so;
					}
					if (dom.isAncestorOf(current, this.ec, true)) {
						endContainer = this.ec;
						endOffset = this.eo;
					}

					updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
				}
				return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
			},

			detach: function (detachRange) {
				if (detachRange) {
					this.range.detach();
				}
				this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
			}
		};

		/*----------------------------------------------------------------------------------------------------------------*/

		// Exceptions

		/**
		 * @constructor
		 */
		function RangeException(codeName) {
			this.code = this[codeName];
			this.codeName = codeName;
			this.message = "RangeException: " + this.codeName;
		}

		RangeException.prototype = {
			BAD_BOUNDARYPOINTS_ERR: 1,
			INVALID_NODE_TYPE_ERR: 2
		};

		RangeException.prototype.toString = function () {
			return this.message;
		};

		/*----------------------------------------------------------------------------------------------------------------*/

		/**
		 * Currently iterates through all nodes in the range on creation until I think of a decent way to do it
		 * TODO: Look into making this a proper iterator, not requiring preloading everything first
		 * @constructor
		 */
		function RangeNodeIterator(range, nodeTypes, filter) {
			this.nodes = getNodesInRange(range, nodeTypes, filter);
			this._next = this.nodes[0];
			this._position = 0;
		}

		RangeNodeIterator.prototype = {
			_current: null,

			hasNext: function () {
				return !!this._next;
			},

			next: function () {
				this._current = this._next;
				this._next = this.nodes[++this._position];
				return this._current;
			},

			detach: function () {
				this._current = this._next = this.nodes = null;
			}
		};

		var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
		var rootContainerNodeTypes = [2, 9, 11];
		var readonlyNodeTypes = [5, 6, 10, 12];
		var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
		var surroundNodeTypes = [1, 3, 4, 5, 7, 8];

		function createAncestorFinder(nodeTypes) {
			return function (node, selfIsAncestor) {
				var t, n = selfIsAncestor ? node : node.parentNode;
				while (n) {
					t = n.nodeType;
					if (dom.arrayContains(nodeTypes, t)) {
						return n;
					}
					n = n.parentNode;
				}
				return null;
			};
		}

		var getRootContainer = dom.getRootContainer;
		var getDocumentOrFragmentContainer = createAncestorFinder([9, 11]);
		var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
		var getDocTypeNotationEntityAncestor = createAncestorFinder([6, 10, 12]);

		function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
			if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
				throw new RangeException("INVALID_NODE_TYPE_ERR");
			}
		}

		function assertNotDetached(range) {
			if (!range.startContainer) {
				throw new DOMException("INVALID_STATE_ERR");
			}
		}

		function assertValidNodeType(node, invalidTypes) {
			if (!dom.arrayContains(invalidTypes, node.nodeType)) {
				throw new RangeException("INVALID_NODE_TYPE_ERR");
			}
		}

		function assertValidOffset(node, offset) {
			if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
				throw new DOMException("INDEX_SIZE_ERR");
			}
		}

		function assertSameDocumentOrFragment(node1, node2) {
			if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
				throw new DOMException("WRONG_DOCUMENT_ERR");
			}
		}

		function assertNodeNotReadOnly(node) {
			if (getReadonlyAncestor(node, true)) {
				throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
			}
		}

		function assertNode(node, codeName) {
			if (!node) {
				throw new DOMException(codeName);
			}
		}

		function isOrphan(node) {
			return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
		}

		function isValidOffset(node, offset) {
			return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length);
		}

		function assertRangeValid(range) {
			assertNotDetached(range);
			if (isOrphan(range.startContainer) || isOrphan(range.endContainer) || !isValidOffset(range.startContainer, range.startOffset) || !isValidOffset(range.endContainer, range.endOffset)) {
				throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
			}
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// Test the browser's innerHTML support to decide how to implement createContextualFragment
		var styleEl = document.createElement("style");
		var htmlParsingConforms = false;
		try {
			styleEl.innerHTML = "<b>x</b>";
			htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
		} catch (e) {
			// IE 6 and 7 throw
		}

		api.features.htmlParsingConforms = htmlParsingConforms;

		var createContextualFragment = htmlParsingConforms ?

		// Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
		// discussion and base code for this implementation at issue 67.
		// Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
		// Thanks to Aleks Williams.
			function (fragmentStr) {
				// "Let node the context object's start's node."
				var node = this.startContainer;
				var doc = dom.getDocument(node);

				// "If the context object's start's node is null, raise an INVALID_STATE_ERR
				// exception and abort these steps."
				if (!node) {
					throw new DOMException("INVALID_STATE_ERR");
				}

				// "Let element be as follows, depending on node's interface:"
				// Document, Document Fragment: null
				var el = null;

				// "Element: node"
				if (node.nodeType == 1) {
					el = node;

					// "Text, Comment: node's parentElement"
				} else if (dom.isCharacterDataNode(node)) {
					el = dom.parentElement(node);
				}

				// "If either element is null or element's ownerDocument is an HTML document
				// and element's local name is "html" and element's namespace is the HTML
				// namespace"
				if (el === null || (
				el.nodeName == "HTML" && dom.isHtmlNamespace(dom.getDocument(el).documentElement) && dom.isHtmlNamespace(el))) {

					// "let element be a new Element with "body" as its local name and the HTML
					// namespace as its namespace.""
					el = doc.createElement("body");
				} else {
					el = el.cloneNode(false);
				}

				// "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
				// "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
				// "In either case, the algorithm must be invoked with fragment as the input
				// and element as the context element."
				el.innerHTML = fragmentStr;

				// "If this raises an exception, then abort these steps. Otherwise, let new
				// children be the nodes returned."

				// "Let fragment be a new DocumentFragment."
				// "Append all new children to fragment."
				// "Return fragment."
				return dom.fragmentFromNodeChildren(el);
			} :

			// In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
			// previous versions of Rangy used (with the exception of using a body element rather than a div)
			function (fragmentStr) {
				assertNotDetached(this);
				var doc = getRangeDocument(this);
				var el = doc.createElement("body");
				el.innerHTML = fragmentStr;

				return dom.fragmentFromNodeChildren(el);
			};

		/*----------------------------------------------------------------------------------------------------------------*/

		var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer"];

		var s2s = 0,
			s2e = 1,
			e2e = 2,
			e2s = 3;
		var n_b = 0,
			n_a = 1,
			n_b_a = 2,
			n_i = 3;

		function RangePrototype() {}

		RangePrototype.prototype = {
			attachListener: function (type, listener) {
				this._listeners[type].push(listener);
			},

			compareBoundaryPoints: function (how, range) {
				assertRangeValid(this);
				assertSameDocumentOrFragment(this.startContainer, range.startContainer);

				var nodeA, offsetA, nodeB, offsetB;
				var prefixA = (how == e2s || how == s2s) ? "start" : "end";
				var prefixB = (how == s2e || how == s2s) ? "start" : "end";
				nodeA = this[prefixA + "Container"];
				offsetA = this[prefixA + "Offset"];
				nodeB = range[prefixB + "Container"];
				offsetB = range[prefixB + "Offset"];
				return dom.comparePoints(nodeA, offsetA, nodeB, offsetB);
			},

			insertNode: function (node) {
				assertRangeValid(this);
				assertValidNodeType(node, insertableNodeTypes);
				assertNodeNotReadOnly(this.startContainer);

				if (dom.isAncestorOf(node, this.startContainer, true)) {
					throw new DOMException("HIERARCHY_REQUEST_ERR");
				}

				// No check for whether the container of the start of the Range is of a type that does not allow
				// children of the type of node: the browser's DOM implementation should do this for us when we attempt
				// to add the node

				var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
				this.setStartBefore(firstNodeInserted);
			},

			cloneContents: function () {
				assertRangeValid(this);

				var clone, frag;
				if (this.collapsed) {
					return getRangeDocument(this).createDocumentFragment();
				} else {
					if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) {
						clone = this.startContainer.cloneNode(true);
						clone.data = clone.data.slice(this.startOffset, this.endOffset);
						frag = getRangeDocument(this).createDocumentFragment();
						frag.appendChild(clone);
						return frag;
					} else {
						var iterator = new RangeIterator(this, true);
						clone = cloneSubtree(iterator);
						iterator.detach();
					}
					return clone;
				}
			},

			canSurroundContents: function () {
				assertRangeValid(this);
				assertNodeNotReadOnly(this.startContainer);
				assertNodeNotReadOnly(this.endContainer);

				// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
				// no non-text nodes.
				var iterator = new RangeIterator(this, true);
				var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
				iterator.detach();
				return !boundariesInvalid;
			},

			surroundContents: function (node) {
				assertValidNodeType(node, surroundNodeTypes);

				if (!this.canSurroundContents()) {
					throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
				}

				// Extract the contents
				var content = this.extractContents();

				// Clear the children of the node
				if (node.hasChildNodes()) {
					while (node.lastChild) {
						node.removeChild(node.lastChild);
					}
				}

				// Insert the new node and add the extracted contents
				insertNodeAtPosition(node, this.startContainer, this.startOffset);
				node.appendChild(content);

				this.selectNode(node);
			},

			cloneRange: function () {
				assertRangeValid(this);
				var range = new Range(getRangeDocument(this));
				var i = rangeProperties.length,
					prop;
				while (i--) {
					prop = rangeProperties[i];
					range[prop] = this[prop];
				}
				return range;
			},

			toString: function () {
				assertRangeValid(this);
				var sc = this.startContainer;
				if (sc === this.endContainer && dom.isCharacterDataNode(sc)) {
					return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
				} else {
					var textBits = [],
						iterator = new RangeIterator(this, true);

					iterateSubtree(iterator, function (node) {
						// Accept only text or CDATA nodes, not comments

						if (node.nodeType == 3 || node.nodeType == 4) {
							textBits.push(node.data);
						}
					});
					iterator.detach();
					return textBits.join("");
				}
			},

			// The methods below are all non-standard. The following batch were introduced by Mozilla but have since
			// been removed from Mozilla.

			compareNode: function (node) {
				assertRangeValid(this);

				var parent = node.parentNode;
				var nodeIndex = dom.getNodeIndex(node);

				if (!parent) {
					throw new DOMException("NOT_FOUND_ERR");
				}

				var startComparison = this.comparePoint(parent, nodeIndex),
					endComparison = this.comparePoint(parent, nodeIndex + 1);

				if (startComparison < 0) { // Node starts before
					return (endComparison > 0) ? n_b_a : n_b;
				} else {
					return (endComparison > 0) ? n_a : n_i;
				}
			},

			comparePoint: function (node, offset) {
				assertRangeValid(this);
				assertNode(node, "HIERARCHY_REQUEST_ERR");
				assertSameDocumentOrFragment(node, this.startContainer);

				if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
					return -1;
				} else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
					return 1;
				}
				return 0;
			},

			createContextualFragment: createContextualFragment,

			toHtml: function () {
				assertRangeValid(this);
				var container = getRangeDocument(this).createElement("div");
				container.appendChild(this.cloneContents());
				return container.innerHTML;
			},

			// touchingIsIntersecting determines whether this method considers a node that borders a range intersects
			// with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
			intersectsNode: function (node, touchingIsIntersecting) {
				assertRangeValid(this);
				assertNode(node, "NOT_FOUND_ERR");
				if (dom.getDocument(node) !== getRangeDocument(this)) {
					return false;
				}

				var parent = node.parentNode,
					offset = dom.getNodeIndex(node);
				assertNode(parent, "NOT_FOUND_ERR");

				var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset),
					endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset);

				return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
			},


			isPointInRange: function (node, offset) {
				assertRangeValid(this);
				assertNode(node, "HIERARCHY_REQUEST_ERR");
				assertSameDocumentOrFragment(node, this.startContainer);

				return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
			},

			// The methods below are non-standard and invented by me.

			// Sharing a boundary start-to-end or end-to-start does not count as intersection.
			intersectsRange: function (range, touchingIsIntersecting) {
				assertRangeValid(this);

				if (getRangeDocument(range) != getRangeDocument(this)) {
					throw new DOMException("WRONG_DOCUMENT_ERR");
				}

				var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset),
					endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset);

				return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
			},

			intersection: function (range) {
				if (this.intersectsRange(range)) {
					var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
						endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);

					var intersectionRange = this.cloneRange();

					if (startComparison == -1) {
						intersectionRange.setStart(range.startContainer, range.startOffset);
					}
					if (endComparison == 1) {
						intersectionRange.setEnd(range.endContainer, range.endOffset);
					}
					return intersectionRange;
				}
				return null;
			},

			union: function (range) {
				if (this.intersectsRange(range, true)) {
					var unionRange = this.cloneRange();
					if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
						unionRange.setStart(range.startContainer, range.startOffset);
					}
					if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
						unionRange.setEnd(range.endContainer, range.endOffset);
					}
					return unionRange;
				} else {
					throw new RangeException("Ranges do not intersect");
				}
			},

			containsNode: function (node, allowPartial) {
				if (allowPartial) {
					return this.intersectsNode(node, false);
				} else {
					return this.compareNode(node) == n_i;
				}
			},

			containsNodeContents: function (node) {
				return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0;
			},

			containsRange: function (range) {
				return this.intersection(range).equals(range);
			},

			containsNodeText: function (node) {
				var nodeRange = this.cloneRange();
				nodeRange.selectNode(node);
				var textNodes = nodeRange.getNodes([3]);
				if (textNodes.length > 0) {
					nodeRange.setStart(textNodes[0], 0);
					var lastTextNode = textNodes.pop();
					nodeRange.setEnd(lastTextNode, lastTextNode.length);
					var contains = this.containsRange(nodeRange);
					nodeRange.detach();
					return contains;
				} else {
					return this.containsNodeContents(node);
				}
			},

			createNodeIterator: function (nodeTypes, filter) {
				assertRangeValid(this);
				return new RangeNodeIterator(this, nodeTypes, filter);
			},

			getNodes: function (nodeTypes, filter) {
				assertRangeValid(this);
				return getNodesInRange(this, nodeTypes, filter);
			},

			getDocument: function () {
				return getRangeDocument(this);
			},

			collapseBefore: function (node) {
				assertNotDetached(this);

				this.setEndBefore(node);
				this.collapse(false);
			},

			collapseAfter: function (node) {
				assertNotDetached(this);

				this.setStartAfter(node);
				this.collapse(true);
			},

			getName: function () {
				return "DomRange";
			},

			equals: function (range) {
				return Range.rangesEqual(this, range);
			},

			inspect: function () {
				return inspect(this);
			}
		};

		function copyComparisonConstantsToObject(obj) {
			obj.START_TO_START = s2s;
			obj.START_TO_END = s2e;
			obj.END_TO_END = e2e;
			obj.END_TO_START = e2s;

			obj.NODE_BEFORE = n_b;
			obj.NODE_AFTER = n_a;
			obj.NODE_BEFORE_AND_AFTER = n_b_a;
			obj.NODE_INSIDE = n_i;
		}

		function copyComparisonConstants(constructor) {
			copyComparisonConstantsToObject(constructor);
			copyComparisonConstantsToObject(constructor.prototype);
		}

		function createRangeContentRemover(remover, boundaryUpdater) {
			return function () {
				assertRangeValid(this);

				var sc = this.startContainer,
					so = this.startOffset,
					root = this.commonAncestorContainer;

				var iterator = new RangeIterator(this, true);

				// Work out where to position the range after content removal
				var node, boundary;
				if (sc !== root) {
					node = dom.getClosestAncestorIn(sc, root, true);
					boundary = getBoundaryAfterNode(node);
					sc = boundary.node;
					so = boundary.offset;
				}

				// Check none of the range is read-only
				iterateSubtree(iterator, assertNodeNotReadOnly);

				iterator.reset();

				// Remove the content
				var returnValue = remover(iterator);
				iterator.detach();

				// Move to the new position
				boundaryUpdater(this, sc, so, sc, so);

				return returnValue;
			};
		}

		function createPrototypeRange(constructor, boundaryUpdater, detacher) {
			function createBeforeAfterNodeSetter(isBefore, isStart) {
				return function (node) {
					assertNotDetached(this);
					assertValidNodeType(node, beforeAfterNodeTypes);
					assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);

					var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
					(isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
				};
			}

			function setRangeStart(range, node, offset) {
				var ec = range.endContainer,
					eo = range.endOffset;
				if (node !== range.startContainer || offset !== this.startOffset) {
					// Check the root containers of the range and the new boundary, and also check whether the new boundary
					// is after the current end. In either case, collapse the range to the new position
					if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) {
						ec = node;
						eo = offset;
					}
					boundaryUpdater(range, node, offset, ec, eo);
				}
			}

			function setRangeEnd(range, node, offset) {
				var sc = range.startContainer,
					so = range.startOffset;
				if (node !== range.endContainer || offset !== this.endOffset) {
					// Check the root containers of the range and the new boundary, and also check whether the new boundary
					// is after the current end. In either case, collapse the range to the new position
					if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) {
						sc = node;
						so = offset;
					}
					boundaryUpdater(range, sc, so, node, offset);
				}
			}

			function setRangeStartAndEnd(range, node, offset) {
				if (node !== range.startContainer || offset !== this.startOffset || node !== range.endContainer || offset !== this.endOffset) {
					boundaryUpdater(range, node, offset, node, offset);
				}
			}

			constructor.prototype = new RangePrototype();

			api.util.extend(constructor.prototype, {
				setStart: function (node, offset) {
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, true);
					assertValidOffset(node, offset);

					setRangeStart(this, node, offset);
				},

				setEnd: function (node, offset) {
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, true);
					assertValidOffset(node, offset);

					setRangeEnd(this, node, offset);
				},

				setStartBefore: createBeforeAfterNodeSetter(true, true),
				setStartAfter: createBeforeAfterNodeSetter(false, true),
				setEndBefore: createBeforeAfterNodeSetter(true, false),
				setEndAfter: createBeforeAfterNodeSetter(false, false),

				collapse: function (isStart) {
					assertRangeValid(this);
					if (isStart) {
						boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
					} else {
						boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
					}
				},

				selectNodeContents: function (node) {
					// This doesn't seem well specified: the spec talks only about selecting the node's contents, which
					// could be taken to mean only its children. However, browsers implement this the same as selectNode for
					// text nodes, so I shall do likewise
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, true);

					boundaryUpdater(this, node, 0, node, dom.getNodeLength(node));
				},

				selectNode: function (node) {
					assertNotDetached(this);
					assertNoDocTypeNotationEntityAncestor(node, false);
					assertValidNodeType(node, beforeAfterNodeTypes);

					var start = getBoundaryBeforeNode(node),
						end = getBoundaryAfterNode(node);
					boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
				},

				extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),

				deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),

				canSurroundContents: function () {
					assertRangeValid(this);
					assertNodeNotReadOnly(this.startContainer);
					assertNodeNotReadOnly(this.endContainer);

					// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
					// no non-text nodes.
					var iterator = new RangeIterator(this, true);
					var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
					iterator.detach();
					return !boundariesInvalid;
				},

				detach: function () {
					detacher(this);
				},

				splitBoundaries: function () {
					assertRangeValid(this);


					var sc = this.startContainer,
						so = this.startOffset,
						ec = this.endContainer,
						eo = this.endOffset;
					var startEndSame = (sc === ec);

					if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
						dom.splitDataNode(ec, eo);

					}

					if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) {

						sc = dom.splitDataNode(sc, so);
						if (startEndSame) {
							eo -= so;
							ec = sc;
						} else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) {
							eo++;
						}
						so = 0;

					}
					boundaryUpdater(this, sc, so, ec, eo);
				},

				normalizeBoundaries: function () {
					assertRangeValid(this);

					var sc = this.startContainer,
						so = this.startOffset,
						ec = this.endContainer,
						eo = this.endOffset;

					var mergeForward = function (node) {
						var sibling = node.nextSibling;
						if (sibling && sibling.nodeType == node.nodeType) {
							ec = node;
							eo = node.length;
							node.appendData(sibling.data);
							sibling.parentNode.removeChild(sibling);
						}
					};

					var mergeBackward = function (node) {
						var sibling = node.previousSibling;
						if (sibling && sibling.nodeType == node.nodeType) {
							sc = node;
							var nodeLength = node.length;
							so = sibling.length;
							node.insertData(0, sibling.data);
							sibling.parentNode.removeChild(sibling);
							if (sc == ec) {
								eo += so;
								ec = sc;
							} else if (ec == node.parentNode) {
								var nodeIndex = dom.getNodeIndex(node);
								if (eo == nodeIndex) {
									ec = node;
									eo = nodeLength;
								} else if (eo > nodeIndex) {
									eo--;
								}
							}
						}
					};

					var normalizeStart = true;

					if (dom.isCharacterDataNode(ec)) {
						if (ec.length == eo) {
							mergeForward(ec);
						}
					} else {
						if (eo > 0) {
							var endNode = ec.childNodes[eo - 1];
							if (endNode && dom.isCharacterDataNode(endNode)) {
								mergeForward(endNode);
							}
						}
						normalizeStart = !this.collapsed;
					}

					if (normalizeStart) {
						if (dom.isCharacterDataNode(sc)) {
							if (so == 0) {
								mergeBackward(sc);
							}
						} else {
							if (so < sc.childNodes.length) {
								var startNode = sc.childNodes[so];
								if (startNode && dom.isCharacterDataNode(startNode)) {
									mergeBackward(startNode);
								}
							}
						}
					} else {
						sc = ec;
						so = eo;
					}

					boundaryUpdater(this, sc, so, ec, eo);
				},

				collapseToPoint: function (node, offset) {
					assertNotDetached(this);

					assertNoDocTypeNotationEntityAncestor(node, true);
					assertValidOffset(node, offset);

					setRangeStartAndEnd(this, node, offset);
				}
			});

			copyComparisonConstants(constructor);
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		// Updates commonAncestorContainer and collapsed after boundary change
		function updateCollapsedAndCommonAncestor(range) {
			range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
			range.commonAncestorContainer = range.collapsed ? range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
		}

		function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
			var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset);
			var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset);

			range.startContainer = startContainer;
			range.startOffset = startOffset;
			range.endContainer = endContainer;
			range.endOffset = endOffset;

			updateCollapsedAndCommonAncestor(range);
			dispatchEvent(range, "boundarychange", {
				startMoved: startMoved,
				endMoved: endMoved
			});
		}

		function detach(range) {
			assertNotDetached(range);
			range.startContainer = range.startOffset = range.endContainer = range.endOffset = null;
			range.collapsed = range.commonAncestorContainer = null;
			dispatchEvent(range, "detach", null);
			range._listeners = null;
		}

		/**
		 * @constructor
		 */
		function Range(doc) {
			this.startContainer = doc;
			this.startOffset = 0;
			this.endContainer = doc;
			this.endOffset = 0;
			this._listeners = {
				boundarychange: [],
				detach: []
			};
			updateCollapsedAndCommonAncestor(this);
		}

		createPrototypeRange(Range, updateBoundaries, detach);

		api.rangePrototype = RangePrototype.prototype;

		Range.rangeProperties = rangeProperties;
		Range.RangeIterator = RangeIterator;
		Range.copyComparisonConstants = copyComparisonConstants;
		Range.createPrototypeRange = createPrototypeRange;
		Range.inspect = inspect;
		Range.getRangeDocument = getRangeDocument;
		Range.rangesEqual = function (r1, r2) {
			return r1.startContainer === r2.startContainer && r1.startOffset === r2.startOffset && r1.endContainer === r2.endContainer && r1.endOffset === r2.endOffset;
		};

		api.DomRange = Range;
		api.RangeException = RangeException;
	});
	rangy.createModule("WrappedRange", function (api, module) {
		api.requireModules(["DomUtil", "DomRange"]);

		/**
		 * @constructor
		 */
		var WrappedRange;
		var dom = api.dom;
		var DomPosition = dom.DomPosition;
		var DomRange = api.DomRange;



		/*----------------------------------------------------------------------------------------------------------------*/

		/*
    This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
    method. For example, in the following (where pipes denote the selection boundaries):

    <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>

    var range = document.selection.createRange();
    alert(range.parentElement().id); // Should alert "ul" but alerts "b"

    This method returns the common ancestor node of the following:
    - the parentElement() of the textRange
    - the parentElement() of the textRange after calling collapse(true)
    - the parentElement() of the textRange after calling collapse(false)
     */
		function getTextRangeContainerElement(textRange) {
			var parentEl = textRange.parentElement();

			var range = textRange.duplicate();
			range.collapse(true);
			var startEl = range.parentElement();
			range = textRange.duplicate();
			range.collapse(false);
			var endEl = range.parentElement();
			var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);

			return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
		}

		function textRangeIsCollapsed(textRange) {
			return textRange.compareEndPoints("StartToEnd", textRange) == 0;
		}

		// Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
		// an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
		// grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
		// for inputs and images, plus optimizations.
		function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) {
			var workingRange = textRange.duplicate();

			workingRange.collapse(isStart);
			var containerElement = workingRange.parentElement();

			// Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
			// check for that
			// TODO: Find out when. Workaround for wholeRangeContainerElement may break this
			if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) {
				containerElement = wholeRangeContainerElement;

			}



			// Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
			// similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
			if (!containerElement.canHaveHTML) {
				return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
			}

			var workingNode = dom.getDocument(containerElement).createElement("span");

			// Workaround for HTML5 Shiv's insane violation of
			// document.createElement(). See Rangy issue 104 and HTML 5 Shiv issue
			// 64: https://github.com/aFarkas/html5shiv/issues/64
			if (workingNode.parentNode) {
				workingNode.parentNode.removeChild(workingNode);
			}

			var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
			var previousNode, nextNode, boundaryPosition, boundaryNode;

			// Move the working range through the container's children, starting at the end and working backwards, until the
			// working range reaches or goes past the boundary we're interested in
			do {
				containerElement.insertBefore(workingNode, workingNode.previousSibling);
				workingRange.moveToElementText(workingNode);
			} while ((comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 && workingNode.previousSibling);

			// We've now reached or gone past the boundary of the text range we're interested in
			// so have identified the node we want
			boundaryNode = workingNode.nextSibling;

			if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) {
				// This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
				// node containing the text range's boundary, so we move the end of the working range to the boundary point
				// and measure the length of its text to get the boundary's offset within the node.
				workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);


				var offset;

				if (/[\r\n]/.test(boundaryNode.data)) {
					/*
                For the particular case of a boundary within a text node containing line breaks (within a <pre> element,
                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:

                - Each line break is represented as \r in the text node's data/nodeValue properties
                - Each line break is represented as \r\n in the TextRange's 'text' property
                - The 'text' property of the TextRange does not contain trailing line breaks

                To get round the problem presented by the final fact above, we can use the fact that TextRange's
                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
                the same as the number of characters it was instructed to move. The simplest approach is to use this to
                store the characters moved when moving both the start and end of the range to the start of the document
                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
                problem.

                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
                the range within the document).

                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
                text of the TextRange, so the start of the range is moved that length initially and then a character at
                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
                performance in most situations compared to the previous two methods.
                */
					var tempRange = workingRange.duplicate();
					var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;

					offset = tempRange.moveStart("character", rangeLength);
					while ((comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
						offset++;
						tempRange.moveStart("character", 1);
					}
				} else {
					// IE7 sometimes has weird workingranges that apparently do not start in the workingNode any more, but in
					// some kind of phantom paragraph, that cannot be found in the DOM.
					// in such situations, the workingRange.text no longer is a substring at the start of the boundaryNode.data
					// If we find such a situation, we skip all characters at the start of the workingRange.data, that are not
					// at the start of the boundaryNode.data.
					// Before comparing, we have to replace all nbsp with normal spaces
					var wrText = workingRange.text.replace(/\u00a0/g, " ");
					var bnText = boundaryNode.data.replace(/\u00a0/g, " ");
					if (bnText.indexOf(wrText) !== 0) {
						while (wrText.length > 0 && bnText.indexOf(wrText) !== 0) {
							wrText = wrText.substr(1);
						}
						offset = wrText.length;
					} else {
						offset = workingRange.text.length;
					}
				}
				boundaryPosition = new DomPosition(boundaryNode, offset);
			} else {


				// If the boundary immediately follows a character data node and this is the end boundary, we should favour
				// a position within that, and likewise for a start boundary preceding a character data node
				previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
				nextNode = (isCollapsed || isStart) && workingNode.nextSibling;



				if (nextNode && dom.isCharacterDataNode(nextNode)) {
					boundaryPosition = new DomPosition(nextNode, 0);
				} else if (previousNode && dom.isCharacterDataNode(previousNode)) {
					boundaryPosition = new DomPosition(previousNode, previousNode.length);
				} else {
					boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
				}
			}

			// Clean up
			workingNode.parentNode.removeChild(workingNode);

			return boundaryPosition;
		}

		// Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
		// This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
		// (http://code.google.com/p/ierange/)
		function createBoundaryTextRange(boundaryPosition, isStart) {
			var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
			var doc = dom.getDocument(boundaryPosition.node);
			var workingNode, childNodes, workingRange = doc.body.createTextRange();
			var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);

			if (nodeIsDataNode) {
				boundaryNode = boundaryPosition.node;
				boundaryParent = boundaryNode.parentNode;
			} else {
				childNodes = boundaryPosition.node.childNodes;
				boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
				boundaryParent = boundaryPosition.node;
			}

			// Position the range immediately before the node containing the boundary
			workingNode = doc.createElement("span");

			// Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
			// element rather than immediately before or after it, which is what we want
			workingNode.innerHTML = "&#feff;";

			// insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
			// for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
			if (boundaryNode) {
				boundaryParent.insertBefore(workingNode, boundaryNode);
			} else {
				boundaryParent.appendChild(workingNode);
			}

			try {
				workingRange.moveToElementText(workingNode);
				workingRange.collapse(!isStart);
			} catch (err) {
				// @todo window.console.log('problem with moveToElementText');
				//return false;
			}

			// Clean up
			boundaryParent.removeChild(workingNode);

			// Move the working range to the text offset, if required
			if (nodeIsDataNode) {
				workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
			}

			return workingRange;
		}

		/*----------------------------------------------------------------------------------------------------------------*/

		if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
			// This is a wrapper around the browser's native DOM Range. It has two aims:
			// - Provide workarounds for specific browser bugs
			// - provide convenient extensions, which are inherited from Rangy's DomRange

			(function () {
				var rangeProto;
				var rangeProperties = DomRange.rangeProperties;
				var canSetRangeStartAfterEnd;

				function updateRangeProperties(range) {
					var i = rangeProperties.length,
						prop;
					while (i--) {
						prop = rangeProperties[i];
						range[prop] = range.nativeRange[prop];
					}
				}

				function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
					var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
					var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);

					// Always set both boundaries for the benefit of IE9 (see issue 35)
					if (startMoved || endMoved) {
						range.setEnd(endContainer, endOffset);
						range.setStart(startContainer, startOffset);
					}
				}

				function detach(range) {
					range.nativeRange.detach();
					range.detached = true;
					var i = rangeProperties.length,
						prop;
					while (i--) {
						prop = rangeProperties[i];
						range[prop] = null;
					}
				}

				var createBeforeAfterNodeSetter;

				WrappedRange = function (range) {
					if (!range) {
						throw new Error("Range must be specified");
					}
					this.nativeRange = range;
					updateRangeProperties(this);
				};

				DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);

				rangeProto = WrappedRange.prototype;

				rangeProto.selectNode = function (node) {
					this.nativeRange.selectNode(node);
					updateRangeProperties(this);
				};

				rangeProto.deleteContents = function () {
					this.nativeRange.deleteContents();
					updateRangeProperties(this);
				};

				rangeProto.extractContents = function () {
					var frag = this.nativeRange.extractContents();
					updateRangeProperties(this);
					return frag;
				};

				rangeProto.cloneContents = function () {
					return this.nativeRange.cloneContents();
				};

				// TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
				// present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
				// insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
				// insertNode, which works but is almost certainly slower than the native implementation.
				/*
            rangeProto.insertNode = function(node) {
                this.nativeRange.insertNode(node);
                updateRangeProperties(this);
            };
*/

				rangeProto.surroundContents = function (node) {
					this.nativeRange.surroundContents(node);
					updateRangeProperties(this);
				};

				rangeProto.collapse = function (isStart) {
					this.nativeRange.collapse(isStart);
					updateRangeProperties(this);
				};

				rangeProto.cloneRange = function () {
					return new WrappedRange(this.nativeRange.cloneRange());
				};

				rangeProto.refresh = function () {
					updateRangeProperties(this);
				};

				rangeProto.toString = function () {
					return this.nativeRange.toString();
				};

				// Create test range and node for feature detection

				var testTextNode = document.createTextNode("test");
				dom.getBody(document).appendChild(testTextNode);
				var range = document.createRange();

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
				// correct for it

				range.setStart(testTextNode, 0);
				range.setEnd(testTextNode, 0);

				try {
					range.setStart(testTextNode, 1);
					canSetRangeStartAfterEnd = true;

					rangeProto.setStart = function (node, offset) {
						this.nativeRange.setStart(node, offset);
						updateRangeProperties(this);
					};

					rangeProto.setEnd = function (node, offset) {
						this.nativeRange.setEnd(node, offset);
						updateRangeProperties(this);
					};

					createBeforeAfterNodeSetter = function (name) {
						return function (node) {
							this.nativeRange[name](node);
							updateRangeProperties(this);
						};
					};

				} catch (ex) {


					canSetRangeStartAfterEnd = false;

					rangeProto.setStart = function (node, offset) {
						try {
							this.nativeRange.setStart(node, offset);
						} catch (ex) {
							this.nativeRange.setEnd(node, offset);
							this.nativeRange.setStart(node, offset);
						}
						updateRangeProperties(this);
					};

					rangeProto.setEnd = function (node, offset) {
						try {
							this.nativeRange.setEnd(node, offset);
						} catch (ex) {
							this.nativeRange.setStart(node, offset);
							this.nativeRange.setEnd(node, offset);
						}
						updateRangeProperties(this);
					};

					createBeforeAfterNodeSetter = function (name, oppositeName) {
						return function (node) {
							try {
								this.nativeRange[name](node);
							} catch (ex) {
								this.nativeRange[oppositeName](node);
								this.nativeRange[name](node);
							}
							updateRangeProperties(this);
						};
					};
				}

				rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
				rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
				rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
				rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
				// the 0th character of the text node
				range.selectNodeContents(testTextNode);
				if (range.startContainer == testTextNode && range.endContainer == testTextNode && range.startOffset == 0 && range.endOffset == testTextNode.length) {
					rangeProto.selectNodeContents = function (node) {
						this.nativeRange.selectNodeContents(node);
						updateRangeProperties(this);
					};
				} else {
					rangeProto.selectNodeContents = function (node) {
						this.setStart(node, 0);
						this.setEnd(node, DomRange.getEndOffset(node));
					};
				}

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
				// START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738

				range.selectNodeContents(testTextNode);
				range.setEnd(testTextNode, 3);

				var range2 = document.createRange();
				range2.selectNodeContents(testTextNode);
				range2.setEnd(testTextNode, 4);
				range2.setStart(testTextNode, 2);

				if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 & range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
					// This is the wrong way round, so correct for it


					rangeProto.compareBoundaryPoints = function (type, range) {
						range = range.nativeRange || range;
						if (type == range.START_TO_END) {
							type = range.END_TO_START;
						} else if (type == range.END_TO_START) {
							type = range.START_TO_END;
						}
						return this.nativeRange.compareBoundaryPoints(type, range);
					};
				} else {
					rangeProto.compareBoundaryPoints = function (type, range) {
						return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
					};
				}

				/*--------------------------------------------------------------------------------------------------------*/

				// Test for existence of createContextualFragment and delegate to it if it exists
				if (api.util.isHostMethod(range, "createContextualFragment")) {
					rangeProto.createContextualFragment = function (fragmentStr) {
						return this.nativeRange.createContextualFragment(fragmentStr);
					};
				}

				/*--------------------------------------------------------------------------------------------------------*/

				// Clean up
				dom.getBody(document).removeChild(testTextNode);
				range.detach();
				range2.detach();
			})();

			api.createNativeRange = function (doc) {
				doc = doc || document;
				return doc.createRange();
			};
		} else if (api.features.implementsTextRange) {
			// This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
			// prototype

			WrappedRange = function (textRange) {
				this.textRange = textRange;
				this.refresh();
			};

			WrappedRange.prototype = new DomRange(document);

			WrappedRange.prototype.refresh = function () {
				var start, end;

				// TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
				var rangeContainerElement = getTextRangeContainerElement(this.textRange);

				if (textRangeIsCollapsed(this.textRange)) {
					end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
				} else {

					start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
					end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
				}

				this.setStart(start.node, start.offset);
				this.setEnd(end.node, end.offset);
			};

			DomRange.copyComparisonConstants(WrappedRange);

			// Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
			var globalObj = (function () {
				return this;
			})();
			if (typeof globalObj.Range == "undefined") {
				globalObj.Range = WrappedRange;
			}

			api.createNativeRange = function (doc) {
				doc = doc || document;
				return doc.body.createTextRange();
			};
		}

		if (api.features.implementsTextRange) {
			WrappedRange.rangeToTextRange = function (range) {
				if (range.collapsed) {
					var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
					return tr;
					//return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
				} else {
					var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
					var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
					var textRange = dom.getDocument(range.startContainer).body.createTextRange();
					textRange.setEndPoint("StartToStart", startRange);
					textRange.setEndPoint("EndToEnd", endRange);
					return textRange;
				}
			};
		}

		WrappedRange.prototype.getName = function () {
			return "WrappedRange";
		};

		api.WrappedRange = WrappedRange;

		api.createRange = function (doc) {
			doc = doc || document;
			return new WrappedRange(api.createNativeRange(doc));
		};

		api.createRangyRange = function (doc) {
			doc = doc || document;
			return new DomRange(doc);
		};

		api.createIframeRange = function (iframeEl) {
			return api.createRange(dom.getIframeDocument(iframeEl));
		};

		api.createIframeRangyRange = function (iframeEl) {
			return api.createRangyRange(dom.getIframeDocument(iframeEl));
		};

		api.addCreateMissingNativeApiListener(function (win) {
			var doc = win.document;
			if (typeof doc.createRange == "undefined") {
				doc.createRange = function () {
					return api.createRange(this);
				};
			}
			doc = win = null;
		});
	});
	rangy.createModule("WrappedSelection", function (api, module) {
		// This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
		// spec (http://html5.org/specs/dom-range.html)

		api.requireModules(["DomUtil", "DomRange", "WrappedRange"]);

		api.config.checkSelectionRanges = true;

		var BOOLEAN = "boolean",
			windowPropertyName = "_rangySelection",
			dom = api.dom,
			util = api.util,
			DomRange = api.DomRange,
			WrappedRange = api.WrappedRange,
			DOMException = api.DOMException,
			DomPosition = dom.DomPosition,
			getSelection,
			selectionIsCollapsed,
			CONTROL = "Control";



		function getWinSelection(winParam) {
			return (winParam || window).getSelection();
		}

		function getDocSelection(winParam) {
			return (winParam || window).document.selection;
		}

		// Test for the Range/TextRange and Selection features required
		// Test for ability to retrieve selection
		var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
			implementsDocSelection = api.util.isHostObject(document, "selection");

		var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);

		if (useDocumentSelection) {
			getSelection = getDocSelection;
			api.isSelectionValid = function (winParam) {
				var doc = (winParam || window).document,
					nativeSel = doc.selection;

				// Check whether the selection TextRange is actually contained within the correct document
				return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
			};
		} else if (implementsWinGetSelection) {
			getSelection = getWinSelection;
			api.isSelectionValid = function () {
				return true;
			};
		} else {
			module.fail("Neither document.selection or window.getSelection() detected.");
		}

		api.getNativeSelection = getSelection;

		var testSelection = getSelection();
		var testRange = api.createNativeRange(document);
		var body = dom.getBody(document);

		// Obtaining a range from a selection
		var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] && util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
		api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;

		// Test for existence of native selection extend() method
		var selectionHasExtend = util.isHostMethod(testSelection, "extend");
		api.features.selectionHasExtend = selectionHasExtend;

		// Test if rangeCount exists
		var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
		api.features.selectionHasRangeCount = selectionHasRangeCount;

		var selectionSupportsMultipleRanges = false;
		var collapsedNonEditableSelectionsSupported = true;

		if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) && typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {

			(function () {
				var iframe = document.createElement("iframe");
				body.appendChild(iframe);

				var iframeDoc = dom.getIframeDocument(iframe);
				iframeDoc.open();
				iframeDoc.write("<html><head></head><body>12</body></html>");
				iframeDoc.close();

				var sel = dom.getIframeWindow(iframe).getSelection();
				var docEl = iframeDoc.documentElement;
				var iframeBody = docEl.lastChild,
					textNode = iframeBody.firstChild;

				// Test whether the native selection will allow a collapsed selection within a non-editable element
				var r1 = iframeDoc.createRange();
				r1.setStart(textNode, 1);
				r1.collapse(true);
				sel.addRange(r1);
				collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
				sel.removeAllRanges();

				// Test whether the native selection is capable of supporting multiple ranges
				var r2 = r1.cloneRange();
				r1.setStart(textNode, 0);
				r2.setEnd(textNode, 2);
				sel.addRange(r1);
				sel.addRange(r2);

				selectionSupportsMultipleRanges = (sel.rangeCount == 2);

				// Clean up
				r1.detach();
				r2.detach();

				body.removeChild(iframe);
			})();
		}

		api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
		api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;

		// ControlRanges
		var implementsControlRange = false,
			testControlRange;

		if (body && util.isHostMethod(body, "createControlRange")) {
			testControlRange = body.createControlRange();
			if (util.areHostProperties(testControlRange, ["item", "add"])) {
				implementsControlRange = true;
			}
		}
		api.features.implementsControlRange = implementsControlRange;

		// Selection collapsedness
		if (selectionHasAnchorAndFocus) {
			selectionIsCollapsed = function (sel) {
				return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
			};
		} else {
			selectionIsCollapsed = function (sel) {
				return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
			};
		}

		function updateAnchorAndFocusFromRange(sel, range, backwards) {
			var anchorPrefix = backwards ? "end" : "start",
				focusPrefix = backwards ? "start" : "end";
			sel.anchorNode = range[anchorPrefix + "Container"];
			sel.anchorOffset = range[anchorPrefix + "Offset"];
			sel.focusNode = range[focusPrefix + "Container"];
			sel.focusOffset = range[focusPrefix + "Offset"];
		}

		function updateAnchorAndFocusFromNativeSelection(sel) {
			var nativeSel = sel.nativeSelection;
			sel.anchorNode = nativeSel.anchorNode;
			sel.anchorOffset = nativeSel.anchorOffset;
			sel.focusNode = nativeSel.focusNode;
			sel.focusOffset = nativeSel.focusOffset;
		}

		function updateEmptySelection(sel) {
			sel.anchorNode = sel.focusNode = null;
			sel.anchorOffset = sel.focusOffset = 0;
			sel.rangeCount = 0;
			sel.isCollapsed = true;
			sel._ranges.length = 0;
		}

		function getNativeRange(range) {
			var nativeRange;
			if (range instanceof DomRange) {
				nativeRange = range._selectionNativeRange;
				if (!nativeRange) {
					nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
					nativeRange.setEnd(range.endContainer, range.endOffset);
					nativeRange.setStart(range.startContainer, range.startOffset);
					range._selectionNativeRange = nativeRange;
					range.attachListener("detach", function () {

						this._selectionNativeRange = null;
					});
				}
			} else if (range instanceof WrappedRange) {
				nativeRange = range.nativeRange;
			} else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
				nativeRange = range;
			}
			return nativeRange;
		}

		function rangeContainsSingleElement(rangeNodes) {
			if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
				return false;
			}
			for (var i = 1, len = rangeNodes.length; i < len; ++i) {
				if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
					return false;
				}
			}
			return true;
		}

		function getSingleElementFromRange(range) {
			var nodes = range.getNodes();
			if (!rangeContainsSingleElement(nodes)) {
				throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
			}
			return nodes[0];
		}

		function isTextRange(range) {
			return !!range && typeof range.text != "undefined";
		}

		function updateFromTextRange(sel, range) {
			// Create a Range from the selected TextRange
			var wrappedRange = new WrappedRange(range);
			sel._ranges = [wrappedRange];

			updateAnchorAndFocusFromRange(sel, wrappedRange, false);
			sel.rangeCount = 1;
			sel.isCollapsed = wrappedRange.collapsed;
		}

		function updateControlSelection(sel) {
			// Update the wrapped selection based on what's now in the native selection
			sel._ranges.length = 0;
			if (sel.docSelection.type == "None") {
				updateEmptySelection(sel);
			} else {
				var controlRange = sel.docSelection.createRange();
				if (isTextRange(controlRange)) {
					// This case (where the selection type is "Control" and calling createRange() on the selection returns
					// a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
					// ControlRange have been removed from the ControlRange and removed from the document.
					updateFromTextRange(sel, controlRange);
				} else {
					sel.rangeCount = controlRange.length;
					var range, doc = dom.getDocument(controlRange.item(0));
					for (var i = 0; i < sel.rangeCount; ++i) {
						range = api.createRange(doc);
						range.selectNode(controlRange.item(i));
						sel._ranges.push(range);
					}
					sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
					updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
				}
			}
		}

		function addRangeToControlSelection(sel, range) {
			var controlRange = sel.docSelection.createRange();
			var rangeElement = getSingleElementFromRange(range);

			// Create a new ControlRange containing all the elements in the selected ControlRange plus the element
			// contained by the supplied range
			var doc = dom.getDocument(controlRange.item(0));
			var newControlRange = dom.getBody(doc).createControlRange();
			for (var i = 0, len = controlRange.length; i < len; ++i) {
				newControlRange.add(controlRange.item(i));
			}
			try {
				newControlRange.add(rangeElement);
			} catch (ex) {
				throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
			}
			newControlRange.select();

			// Update the wrapped selection based on what's now in the native selection
			updateControlSelection(sel);
		}

		var getSelectionRangeAt;

		if (util.isHostMethod(testSelection, "getRangeAt")) {
			getSelectionRangeAt = function (sel, index) {
				try {
					return sel.getRangeAt(index);
				} catch (ex) {
					return null;
				}
			};
		} else if (selectionHasAnchorAndFocus) {
			getSelectionRangeAt = function (sel) {
				var doc = dom.getDocument(sel.anchorNode);
				var range = api.createRange(doc);
				range.setStart(sel.anchorNode, sel.anchorOffset);
				range.setEnd(sel.focusNode, sel.focusOffset);

				// Handle the case when the selection was selected backwards (from the end to the start in the
				// document)
				if (range.collapsed !== this.isCollapsed) {
					range.setStart(sel.focusNode, sel.focusOffset);
					range.setEnd(sel.anchorNode, sel.anchorOffset);
				}

				return range;
			};
		}

		/**
		 * @constructor
		 */
		function WrappedSelection(selection, docSelection, win) {
			this.nativeSelection = selection;
			this.docSelection = docSelection;
			this._ranges = [];
			this.win = win;
			this.refresh();
		}

		api.getSelection = function (win) {
			win = win || window;
			var sel = win[windowPropertyName];
			var nativeSel = getSelection(win),
				docSel = implementsDocSelection ? getDocSelection(win) : null;
			if (sel) {
				sel.nativeSelection = nativeSel;
				sel.docSelection = docSel;
				sel.refresh(win);
			} else {
				sel = new WrappedSelection(nativeSel, docSel, win);
				win[windowPropertyName] = sel;
			}
			return sel;
		};

		api.getIframeSelection = function (iframeEl) {
			return api.getSelection(dom.getIframeWindow(iframeEl));
		};

		var selProto = WrappedSelection.prototype;

		function createControlSelection(sel, ranges) {
			// Ensure that the selection becomes of type "Control"
			var doc = dom.getDocument(ranges[0].startContainer);
			var controlRange = dom.getBody(doc).createControlRange();
			for (var i = 0, el; i < rangeCount; ++i) {
				el = getSingleElementFromRange(ranges[i]);
				try {
					controlRange.add(el);
				} catch (ex) {
					throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
				}
			}
			controlRange.select();

			// Update the wrapped selection based on what's now in the native selection
			updateControlSelection(sel);
		}

		// Selecting a range
		if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
			selProto.removeAllRanges = function () {
				this.nativeSelection.removeAllRanges();
				updateEmptySelection(this);
			};

			var addRangeBackwards = function (sel, range) {
				var doc = DomRange.getRangeDocument(range);
				var endRange = api.createRange(doc);
				endRange.collapseToPoint(range.endContainer, range.endOffset);
				sel.nativeSelection.addRange(getNativeRange(endRange));
				sel.nativeSelection.extend(range.startContainer, range.startOffset);
				sel.refresh();
			};

			if (selectionHasRangeCount) {
				selProto.addRange = function (range, backwards) {
					if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
						addRangeToControlSelection(this, range);
					} else {
						if (backwards && selectionHasExtend) {
							addRangeBackwards(this, range);
						} else {
							var previousRangeCount;
							if (selectionSupportsMultipleRanges) {
								previousRangeCount = this.rangeCount;
							} else {
								this.removeAllRanges();
								previousRangeCount = 0;
							}
							this.nativeSelection.addRange(getNativeRange(range));

							// Check whether adding the range was successful
							this.rangeCount = this.nativeSelection.rangeCount;

							if (this.rangeCount == previousRangeCount + 1) {
								// The range was added successfully

								// Check whether the range that we added to the selection is reflected in the last range extracted from
								// the selection
								if (api.config.checkSelectionRanges) {
									var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
									if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
										// Happens in WebKit with, for example, a selection placed at the start of a text node
										range = new WrappedRange(nativeRange);
									}
								}
								this._ranges[this.rangeCount - 1] = range;
								updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
								this.isCollapsed = selectionIsCollapsed(this);
							} else {
								// The range was not added successfully. The simplest thing is to refresh
								this.refresh();
							}
						}
					}
				};
			} else {
				selProto.addRange = function (range, backwards) {
					if (backwards && selectionHasExtend) {
						addRangeBackwards(this, range);
					} else {
						this.nativeSelection.addRange(getNativeRange(range));
						this.refresh();
					}
				};
			}

			selProto.setRanges = function (ranges) {
				if (implementsControlRange && ranges.length > 1) {
					createControlSelection(this, ranges);
				} else {
					this.removeAllRanges();
					for (var i = 0, len = ranges.length; i < len; ++i) {
						this.addRange(ranges[i]);
					}
				}
			};
		} else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") && implementsControlRange && useDocumentSelection) {

			selProto.removeAllRanges = function () {
				// Added try/catch as fix for issue #21
				try {

					var isNativeIE7 = (jQuery.browser.msie && jQuery.browser.version < 8 && (typeof document.documentMode === 'undefined'));
					if (!isNativeIE7) {
						this.docSelection.empty();
					}

					// Check for empty() not working (issue #24)
					if (this.docSelection.type != "None") {

						if (isNativeIE7) {
							this.docSelection.empty();
						}

						// removed workaround of rangy-core implementation
						// for IE to fix issue with strange selection of
						// hole body in some selection change cases
					}

				} catch (ex) {}
				updateEmptySelection(this);
			};

			selProto.addRange = function (range) {
				if (this.docSelection.type == CONTROL) {
					addRangeToControlSelection(this, range);
				} else {
					try {
						WrappedRange.rangeToTextRange(range).select();
						this._ranges[0] = range;
						this.rangeCount = 1;
						this.isCollapsed = this._ranges[0].collapsed;
						updateAnchorAndFocusFromRange(this, range, false);
					} catch (e) {
						// @todo
						// window.console.log('problem at addRange');
					}
				}
			};

			selProto.setRanges = function (ranges) {
				this.removeAllRanges();
				var rangeCount = ranges.length;
				if (rangeCount > 1) {
					createControlSelection(this, ranges);
				} else if (rangeCount) {
					this.addRange(ranges[0]);
				}
			};
		} else {
			module.fail("No means of selecting a Range or TextRange was found");
			return false;
		}

		selProto.getRangeAt = function (index) {
			if (index < 0 || index >= this.rangeCount) {
				throw new DOMException("INDEX_SIZE_ERR");
			} else {
				return this._ranges[index];
			}
		};

		var refreshSelection;

		if (useDocumentSelection) {
			refreshSelection = function (sel) {
				var range;
				if (api.isSelectionValid(sel.win)) {
					range = sel.docSelection.createRange();
				} else {
					range = dom.getBody(sel.win.document).createTextRange();
					range.collapse(true);
				}


				if (sel.docSelection.type == CONTROL) {
					updateControlSelection(sel);
				} else if (isTextRange(range)) {
					updateFromTextRange(sel, range);
				} else {
					updateEmptySelection(sel);
				}
			};
		} else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
			refreshSelection = function (sel) {
				if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
					updateControlSelection(sel);
				} else {
					sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
					if (sel.rangeCount) {
						for (var i = 0, len = sel.rangeCount; i < len; ++i) {
							sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
						}
						updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
						sel.isCollapsed = selectionIsCollapsed(sel);
					} else {
						updateEmptySelection(sel);
					}
				}
			};
		} else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
			refreshSelection = function (sel) {
				var range, nativeSel = sel.nativeSelection;
				if (nativeSel.anchorNode) {
					range = getSelectionRangeAt(nativeSel, 0);
					sel._ranges = [range];
					sel.rangeCount = 1;
					updateAnchorAndFocusFromNativeSelection(sel);
					sel.isCollapsed = selectionIsCollapsed(sel);
				} else {
					updateEmptySelection(sel);
				}
			};
		} else {
			module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
			return false;
		}

		selProto.refresh = function (checkForChanges) {
			var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
			refreshSelection(this);
			if (checkForChanges) {
				var i = oldRanges.length;
				if (i != this._ranges.length) {
					return false;
				}
				while (i--) {
					if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
						return false;
					}
				}
				return true;
			}
		};

		// Removal of a single range
		var removeRangeManually = function (sel, range) {
			var ranges = sel.getAllRanges(),
				removed = false;
			sel.removeAllRanges();
			for (var i = 0, len = ranges.length; i < len; ++i) {
				if (removed || range !== ranges[i]) {
					sel.addRange(ranges[i]);
				} else {
					// According to the draft WHATWG Range spec, the same range may be added to the selection multiple
					// times. removeRange should only remove the first instance, so the following ensures only the first
					// instance is removed
					removed = true;
				}
			}
			if (!sel.rangeCount) {
				updateEmptySelection(sel);
			}
		};

		if (implementsControlRange) {
			selProto.removeRange = function (range) {
				if (this.docSelection.type == CONTROL) {
					var controlRange = this.docSelection.createRange();
					var rangeElement = getSingleElementFromRange(range);

					// Create a new ControlRange containing all the elements in the selected ControlRange minus the
					// element contained by the supplied range
					var doc = dom.getDocument(controlRange.item(0));
					var newControlRange = dom.getBody(doc).createControlRange();
					var el, removed = false;
					for (var i = 0, len = controlRange.length; i < len; ++i) {
						el = controlRange.item(i);
						if (el !== rangeElement || removed) {
							newControlRange.add(controlRange.item(i));
						} else {
							removed = true;
						}
					}
					newControlRange.select();

					// Update the wrapped selection based on what's now in the native selection
					updateControlSelection(this);
				} else {
					removeRangeManually(this, range);
				}
			};
		} else {
			selProto.removeRange = function (range) {
				removeRangeManually(this, range);
			};
		}

		// Detecting if a selection is backwards
		var selectionIsBackwards;
		if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
			selectionIsBackwards = function (sel) {
				var backwards = false;
				if (sel.anchorNode) {
					backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
				}
				return backwards;
			};

			selProto.isBackwards = function () {
				return selectionIsBackwards(this);
			};
		} else {
			selectionIsBackwards = selProto.isBackwards = function () {
				return false;
			};
		}

		// Selection text
		// This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
		selProto.toString = function () {

			var rangeTexts = [];
			for (var i = 0, len = this.rangeCount; i < len; ++i) {
				rangeTexts[i] = "" + this._ranges[i];
			}
			return rangeTexts.join("");
		};

		function assertNodeInSameDocument(sel, node) {
			if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
				throw new DOMException("WRONG_DOCUMENT_ERR");
			}
		}

		// No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
		selProto.collapse = function (node, offset) {
			assertNodeInSameDocument(this, node);
			var range = api.createRange(dom.getDocument(node));
			range.collapseToPoint(node, offset);
			this.removeAllRanges();
			this.addRange(range);
			this.isCollapsed = true;
		};

		selProto.collapseToStart = function () {
			if (this.rangeCount) {
				var range = this._ranges[0];
				this.collapse(range.startContainer, range.startOffset);
			} else {
				throw new DOMException("INVALID_STATE_ERR");
			}
		};

		selProto.collapseToEnd = function () {
			if (this.rangeCount) {
				var range = this._ranges[this.rangeCount - 1];
				this.collapse(range.endContainer, range.endOffset);
			} else {
				throw new DOMException("INVALID_STATE_ERR");
			}
		};

		// The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
		// never used by Rangy.
		selProto.selectAllChildren = function (node) {
			assertNodeInSameDocument(this, node);
			var range = api.createRange(dom.getDocument(node));
			range.selectNodeContents(node);
			this.removeAllRanges();
			this.addRange(range);
		};

		selProto.deleteFromDocument = function () {
			// Sepcial behaviour required for Control selections
			if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
				var controlRange = this.docSelection.createRange();
				var element;
				while (controlRange.length) {
					element = controlRange.item(0);
					controlRange.remove(element);
					element.parentNode.removeChild(element);
				}
				this.refresh();
			} else if (this.rangeCount) {
				var ranges = this.getAllRanges();
				this.removeAllRanges();
				for (var i = 0, len = ranges.length; i < len; ++i) {
					ranges[i].deleteContents();
				}
				// The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
				// range. Firefox moves the selection to where the final selected range was, so we emulate that
				this.addRange(ranges[len - 1]);
			}
		};

		// The following are non-standard extensions
		selProto.getAllRanges = function () {
			return this._ranges.slice(0);
		};

		selProto.setSingleRange = function (range) {
			this.setRanges([range]);
		};

		selProto.containsNode = function (node, allowPartial) {
			for (var i = 0, len = this._ranges.length; i < len; ++i) {
				if (this._ranges[i].containsNode(node, allowPartial)) {
					return true;
				}
			}
			return false;
		};

		selProto.toHtml = function () {
			var html = "";
			if (this.rangeCount) {
				var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
				for (var i = 0, len = this._ranges.length; i < len; ++i) {
					container.appendChild(this._ranges[i].cloneContents());
				}
				html = container.innerHTML;
			}
			return html;
		};

		function inspect(sel) {
			var rangeInspects = [];
			var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
			var focus = new DomPosition(sel.focusNode, sel.focusOffset);
			var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";

			if (typeof sel.rangeCount != "undefined") {
				for (var i = 0, len = sel.rangeCount; i < len; ++i) {
					rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
				}
			}
			return "[" + name + "(Ranges: " + rangeInspects.join(", ") + ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";

		}

		selProto.getName = function () {
			return "WrappedSelection";
		};

		selProto.inspect = function () {
			return inspect(this);
		};

		selProto.detach = function () {
			this.win[windowPropertyName] = null;
			this.win = this.anchorNode = this.focusNode = null;
		};

		WrappedSelection.inspect = inspect;

		api.Selection = WrappedSelection;

		api.selectionPrototype = selProto;

		api.addCreateMissingNativeApiListener(function (win) {
			if (typeof win.getSelection == "undefined") {
				win.getSelection = function () {
					return api.getSelection(this);
				};
			}
			win = null;
		});
	});

	// TODO we should avoid populating the global namespace
	window.rangy = rangy;
}());

define("vendor/rangy-core", function(){});

/* rangy-core.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/rangy-core', ['jquery', 'vendor/rangy-core'], function (jQuery) {
	
	return window.rangy;
});

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
/*
 * MODIFICATIONS: 
 * * The name of the "constructor" method was changed from "init" to "_constructor"
 * * Mixin Support using https://gist.github.com/1006243
 * * Modified to be a require.js module
 */
define('util/class',[], function () {
	var initializing = false,
		fnTest = /xyz/.test(function () {
			xyz;
		}) ? /\b_super\b/ : /.*/;

	// The base Class implementation (does nothing)
	// with doing that Class is available in the global namespace.
	this.Class = function () {};

	// Create a new Class that inherits from this class
	Class.extend = function () {
		var _super = this.prototype;

		// Instantiate a base class (but only create the instance,
		// don't run the init constructor)
		initializing = true;
		var prototype = new this();
		initializing = false;

		// Copy the properties over onto the new prototype
		for (var i = 0; i < arguments.length; i++) {
			var prop = arguments[i];
			for (var name in prop) {
				// Check if we're overwriting an existing function
				prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) {
					return function () {

						var tmp = this._super;

						// Add a new ._super() method that is the same method
						// but on the super-class
						this._super = _super[name];

						// The method only need to be bound temporarily, so we
						// remove it when we're done executing
						var ret = fn.apply(this, arguments);
						this._super = tmp;

						return ret;
					};
				})(name, prop[name]) : prop[name];
			}
		}

		// The dummy class constructor
		function Class() {
			// All construction is actually done in the _constructor method
			if (!initializing && this._constructor) this._constructor.apply(this, arguments);
		}

		// Populate our constructed prototype object
		Class.prototype = prototype;

		// Enforce the constructor to be what we expect
		Class.constructor = Class;

		// And make this class extendable
		Class.extend = arguments.callee;

		return Class;

	};

	return this.Class;

});

/* lang.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};

define('util/lang', [], function () {
	
});

// Start Closure
(function (window) {
	
	var jQuery = window.alohaQuery || window.jQuery,
		$ = jQuery,
		GENTICS = window.GENTICS,
		Class = window.Class,
		console = window.console;

	/**
	 * Takes over all properties from the 'properties' object to the target object.
	 * If a property in 'target' with the same name as a property in 'properties' is already defined it is overridden.
	 *
	 * Example:
	 *
	 * var o1 = {a : 1, b : 'hello'};
	 * var o2 = {a : 3, c : 'world'};
	 *
	 * GENTICS.Utils.applyProperties(o1, o2);
	 *
	 * Will result in an o1 object like this:
	 *
	 * {a : 3, b: 'hello', c: 'world'}
	 *
	 * @static
	 * @return void
	 */
	GENTICS.Utils.applyProperties = function (target, properties) {
		var name;
		for (name in properties) {
			if (properties.hasOwnProperty(name)) {
				target[name] = properties[name];
			}
		}
	};

	/**
	 * Generate a unique hexadecimal string with 4 charachters
	 * @return {string}
	 */
	GENTICS.Utils.uniqeString4 = function () {
		return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
	};

	/**
	 * Generate a unique value represented as a 32 character hexadecimal string,
	 * such as 21EC2020-3AEA-1069-A2DD-08002B30309D
	 * @return {string}
	 */
	GENTICS.Utils.guid = function () {
		var S4 = GENTICS.Utils.uniqeString4;
		return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4());
	};

}(window));

/**
 * ecma5schims.js - Shim for ECMA5 compatibility
 * (http://en.wikipedia.org/wiki/Shim_%28computing%29)
 *
 * A shim library that implements common functions that are missing on some
 * environments in order to complete ECMA5 compatibility across all major
 * browsers.
 *
 * TODO: This code needs to be refactored so as to conform to Aloha coding
 *       standards.  It is also severly lacking in documentation.  Please take
 *       note of: https://github.com/alohaeditor/Aloha-Editor/wiki/Commit-Checklist .
 */

define('aloha/ecma5shims',[], function () {
	
	var $_;

	var shims = {
		// Function bind
		bind: function (owner) {
			var obj = this.obj || this;
			var native_method = Function.prototype.bind;
			var args = Array.prototype.slice.call(arguments, 1);

			if (native_method) {
				return native_method.apply(obj, arguments);
			}
			return function () {
				return obj.apply(owner, arguments.length === 0 ? args : args.concat(Array.prototype.slice.call(arguments)));
			};
		},

		// String trim
		trim: function () {
			var obj = this.obj || this;
			var native_method = String.prototype.trim;

			if (native_method) {
				return native_method.call(obj);
			}
			return obj.replace(/^\s+/, '').replace(/\s+$/, '');
		},

		// Array methods 
		// i optional
		indexOf: function (find, i) {
			var obj = this.obj || this;
			var native_method = Array.prototype.indexOf;

			if (native_method) {
				return native_method.call(obj, find, i);
			}
			if (i === undefined) {
				i = 0;
			}
			if (i < 0) {
				i += obj.length;
			}
			if (i < 0) {
				i = 0;
			}
			var n;
			for (n = obj.length; i < n; i++) {
				if (undefined !== obj[i] && obj[i] === find) {
					return i;
				}
			}
			return -1;
		},

		// that optional
		forEach: function (action, that) {
			var obj = this.obj || this;
			var native_method = Array.prototype.forEach;

			if (native_method) {
				return native_method.call(obj, action, that);
			}
			var i, n;
			for (i = 0, n = obj.length; i < n; i++) {
				if (undefined !== obj[i]) {
					action.call(that, obj[i], i, obj);
				}
			}
		},

		// that optional
		// chain optional
		map: function (mapper, that, chain) {
			var obj = this.obj || this;
			var native_method = Array.prototype.map;
			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
			var result = [];

			if (native_method) {
				result = native_method.call(obj, mapper, that);
			} else {
				var other = [];
				var i, n;
				for (i = 0, n = obj.length; i < n; i++) {
					if (undefined !== obj[i]) {
						other[i] = mapper.call(that, obj[i], i, obj);
					}
				}
				result = other;
			}

			return returnWrapper ? $_(result) : result;
		},

		// that optional
		// chain optional
		filter: function (filterFunc, that, chain) {
			var obj = this.obj || this;
			var native_method = Array.prototype.filter;
			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
			var result = [];

			if (native_method) {
				result = native_method.call(obj, filterFunc, that);
			} else {
				var other = [],
				    v,
				    i,
				    n;
				for (i = 0, n = obj.length; i < n; i++) {
					if (undefined !== obj[i] && filterFunc.call(that, v = obj[i], i, obj)) {
						other.push(v);
					}
				}
				result = other;
			}

			return returnWrapper ? $_(result) : result;
		},

		// that optional
		every: function (tester, that) {
			var obj = this.obj || this;
			var native_method = Array.prototype.every;

			if (native_method) {
				return native_method.call(obj, tester, that);
			}
			var i, n;
			for (i = 0, n = obj.length; i < n; i++) {
				if (undefined !== obj[i] && !tester.call(that, obj[i], i, obj)) {
					return false;
				}
			}
			return true;
		},

		// that optional
		some: function (tester, that) {
			var obj = this.obj || this;
			var native_method = Array.prototype.some;

			if (native_method) {
				return native_method.call(obj, tester, that);
			}
			var i, n;
			for (i = 0, n = obj.length; i < n; i++) {
				if (undefined !== obj[i] && tester.call(that, obj[i], i, obj)) {
					return true;
				}
			}
			return false;
		},

		// Since IE7 doesn't support 'hasAttribute' method on nodes
		// TODO: raise an exception if the object is not an node
		hasAttribute: function (attr) {
			var obj = this.obj || this;
			var native_method = obj.hasAttribute;

			if (native_method) {
				return obj.hasAttribute(attr);
			}
			return !!obj.getAttribute(attr);
		}

	};

	$_ = function (obj) {
		var Wrapper = function () {};
		Wrapper.prototype = shims;

		var wrapper_instance = new Wrapper();
		wrapper_instance.obj = obj;
		return wrapper_instance;
	};

	var shim;
	for (shim in shims) {
		if (shims.hasOwnProperty(shim)) {
			$_[shim] = shims[shim];
		}
	}


	// Node constants
	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
	if (typeof window.Node != 'undefined') {
		$_.Node = window.Node;
	} else {
		$_.Node = {
			'ELEMENT_NODE': 1,
			'ATTRIBUTE_NODE': 2,
			'TEXT_NODE': 3,
			'CDATA_SECTION_NODE': 4,
			'ENTITY_REFERENCE_NODE': 5,
			'ENTITY_NODE': 6,
			'PROCESSING_INSTRUCTION_NODE': 7,
			'COMMENT_NODE': 8,
			'DOCUMENT_NODE': 9,
			'DOCUMENT_TYPE_NODE': 10,
			'DOCUMENT_FRAGMENT_NODE': 11,
			'NOTATION_NODE': 12,
			//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
			'DOCUMENT_POSITION_DISCONNECTED': 0x01,
			//The second node precedes the reference node.
			'DOCUMENT_POSITION_PRECEDING': 0x02,
			//The node follows the reference node.
			'DOCUMENT_POSITION_FOLLOWING': 0x04,
			//The node contains the reference node. A node which contains is always preceding, too.
			'DOCUMENT_POSITION_CONTAINS': 0x08,
			//The node is contained by the reference node. A node which is contained is always following, too.
			'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
			//The determination of preceding versus following is implementation-specific.
			'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
		};
	}

	//node.ownerDocument gives the document object, which isn't the right info for a disconnect
	function getRootParent(node) {
		var parent = null;

		if (node) {
			do {
				parent = node;
			} while (null != (node = node.parentNode));
		}

		return parent;
	}

	//Compare Position - MIT Licensed, John Resig; http://ejohn.org/blog/comparing-document-position/
	//Already checked for equality and disconnect
	function comparePosition(node1, node2) {
		return (node1.contains(node2) && 16) + (node2.contains(node1) && 8) + (node1.sourceIndex >= 0 && node2.sourceIndex >= 0 ? (node1.sourceIndex < node2.sourceIndex && 4) + (node1.sourceIndex > node2.sourceIndex && 2) : 1);
	}

	//get a node with a sourceIndex to use
	function getUseNode(node) {
		//if the node already has a sourceIndex, use that node
		if (null != node.sourceIndex) {
			return node;
		}
		//otherwise, insert a comment (which has a sourceIndex but minimal DOM impact) before the node and use that
		return node.parentNode.insertBefore(document.createComment(""), node);
	}

	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
	// FIXME: Check if the DOMNode prototype can be set.
	$_.compareDocumentPosition = function (node1, node2) {

		if (document.documentElement.compareDocumentPosition) {
			return node1.compareDocumentPosition(node2);
		}

		if (!document.documentElement.contains) {
			throw 'neither compareDocumentPosition nor contains is supported by this browser.';
		}

		if (node1 == node2) {
			return 0;
		}

		//if they don't have the same parent, there's a disconnect
		if (getRootParent(node1) != getRootParent(node2)) {
			return 1;
		}

		//use this if both nodes have a sourceIndex (text nodes don't)
		if (null != node1.sourceIndex && null != node2.sourceIndex) {
			return comparePosition(node1, node2);
		}

		//document will definitely contain the other node
		if (node1 == document) {
			return 20;
		}
		if (node2 == document) {
			return 10;
		}

		//get sourceIndexes to use for both nodes
		var useNode1 = getUseNode(node1),
			useNode2 = getUseNode(node2);

		//call this function again to get the result
		var result = comparePosition(useNode1, useNode2);

		//clean up if needed
		if (node1 != useNode1) {
			useNode1.parentNode.removeChild(useNode1);
		}
		if (node2 != useNode2) {
			useNode2.parentNode.removeChild(useNode2);
		}
		return result;
	};

	$_.getComputedStyle = function (node, style) {
		if (window.getComputedStyle) {
			return window.getComputedStyle(node, style);
		}
		if (node.currentStyle) {
			return node.currentStyle;
		}
		return null;
	};

	return $_;
});

/* dom.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/*jslint eqeq: true */
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};

define('util/dom',['jquery', 'util/class', 'aloha/ecma5shims'], function (jQuery, Class, $_) {
	

	var	GENTICS = window.GENTICS,
		//		Class = window.Class,
		// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
		Node = {
			'ELEMENT_NODE': 1,
			'ATTRIBUTE_NODE': 2,
			'TEXT_NODE': 3,
			'CDATA_SECTION_NODE': 4,
			'ENTITY_REFERENCE_NODE': 5,
			'ENTITY_NODE': 6,
			'PROCESSING_INSTRUCTION_NODE': 7,
			'COMMENT_NODE': 8,
			'DOCUMENT_NODE': 9,
			'DOCUMENT_TYPE_NODE': 10,
			'DOCUMENT_FRAGMENT_NODE': 11,
			'NOTATION_NODE': 12,
			//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
			'DOCUMENT_POSITION_DISCONNECTED': 0x01,
			//The second node precedes the reference node.
			'DOCUMENT_POSITION_PRECEDING': 0x02,
			//The node follows the reference node.
			'DOCUMENT_POSITION_FOLLOWING': 0x04,
			//The node contains the reference node. A node which contains is always preceding, too.
			'DOCUMENT_POSITION_CONTAINS': 0x08,
			//The node is contained by the reference node. A node which is contained is always following, too.
			'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
			//The determination of preceding versus following is implementation-specific.
			'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
		},
		blockElementNames = {
			'P': true,
			'H1': true,
			'H2': true,
			'H3': true,
			'H4': true,
			'H5': true,
			'H6': true,
			'LI': true
		};

	/**
	 * Can't use elem.childNodes.length because
	 * http://www.quirksmode.org/dom/w3c_core.html
	 * "IE up to 8 does not count empty text nodes."
	 *
	 * Taken from Dom2.js
	 */
	function numChildren(elem) {
		var count = 0;
		var child = elem.firstChild;
		while (child) {
			count += 1;
			child = child.nextSibling;
		}
		return count;
	}

	/**
	 * Gets the index of the given node within its parent element.
	 * @param {Element} node
	 * @return {number}
	 *         Index in the parent node or -1 if no node given or node has no
	 *         parent.
	 */
	function getIndexInParent(node) {
		if (!node) {
			return -1;
		}

		var i,
		    childNodes = node.parentNode.childNodes,
			len = childNodes.length;
		for (i = 0; i < len; i++) {
			if (childNodes[i] === node) {
				return i;
			}
		}

		return -1;
	}

	/**
	 * Taken from Dom2.js
	 */
	function nodeLength(node) {
		if (1 === node.nodeType) {
			return numChildren(node);
		}
		if (3 === node.nodeType) {
			return node.length;
		}
		return 0;
	}

	/**
	 * Checks if the element given is an aloha-editing-p helper, added by split.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if the given element is an
	 *                   aloha-editing-paragraph.
	 */
	function isAlohaEditingP(node) {
		return (
			node.className === 'aloha-editing-p'
				&& nodeLength(node) === 1
					&& node.children[0].nodeName === 'BR'
						&& node.children[0].className === 'aloha-end-br'
		);
	}

	/**
	 * Starting from the given node, will walk forward (right-ward) through the
	 * node until an element is found that matches the predicate `match` or we
	 * reach the last element in the tree inside the editing host.
	 *
	 * @param {HTMLElement} node An element that must be inside an editable.
	 * @param {function(HTMLElement):Boolean} match A prediate function to
	 *                                              determine wether or not the
	 *                                              node matches one we are
	 *                                              looking for.
	 * @return {HTMLElement} The matched node that is forward in the DOM tree
	 *                       from `node`; null if nothing can be found that
	 *                       matches `match`.
	 */
	function findNodeForward(node, match) {
		if (!node) {
			return null;
		}
		if (match(node)) {
			return node;
		}
		var next = node.firstChild
		        || node.nextSibling
		        || (
		            node.parentNode
		            && !GENTICS.Utils.Dom.isEditingHost(node.parentNode)
		            && node.parentNode.nextSibling
		        );
		return next ? findNodeForward(next, match) : null;
	}

	function isVisiblyEmpty(node) {
		if (!node) {
			return true;
		}
		// TODO: use isChildlessElement()
		if ('BR' === node.nodeName) {
			return false;
		}
		if (node.nodeType === Node.TEXT_NODE) {
			// TODO: would prefer to use
			// (Html.isWhitespaces(node) || Html.isZeroWidthCharacters(node))
			// but cannot because of circular dependency
			if (node.data.search(/\S/) === -1) {
				return true;
			}
			// Fix for IE with zero-width characters
			if (1 === node.data.length && node.data.charCodeAt(0) >= 0x2000) {
				return true;
			}
			return false;
		}
		var numChildren = nodeLength(node);
		if (0 === numChildren) {
			return true;
		}
		var children = node.childNodes;
		var i;
		for (i = 0; i < numChildren; i++) {
			if (!isVisiblyEmpty(children[i])) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks for the opposite condition of isVisiblyEmpty().
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if the given node is visible not empty.
	 */
	function isNotVisiblyEmpty(node) {
		return !isVisiblyEmpty(node);
	}

	/**
	 * Checks whether the given element is a "phantom" element-- ie: an element
	 * that is either invisible or an aloha-editing-paragraph element.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if the element is a "phantom" element.
	 */
	function isPhantomNode(node) {
		return isVisiblyEmpty(node) || isAlohaEditingP(node);
	}

	/**
	 * Inserts the DOM node `element` appropriately during a split operation.
	 *
	 * The element `head` is used to reference where the element should be
	 * inserted.  Depending on the structure of this `head` node, the `element`
	 * will either replace/overwrite `head` or be appending immediately after
	 * `head`.
	 *
	 * @param {HTMLElement} head The "head" element of the "head and tail"
	 *                           nodes resulting from splitting a DOM element.
	 * @param {HTMLElement} element The element to be inserted between the head
	 *                              and tail split parts.
	 */
	function insertAfterSplit(head, element) {
		if (head.nodeType !== Node.TEXT_NODE && isPhantomNode(head)) {
			jQuery(head).replaceWith(element);
		} else {
			jQuery(head).after(element);
		}
	}

	/**
	 * @namespace GENTICS.Utils
	 * @class Dom provides methods to get information about the DOM and to manipulate it
	 * @singleton
	 */
	var Dom = Class.extend({
		getIndexInParent: getIndexInParent,
		/**
		 * Regex to find word characters.
		 */
		wordRegex: /[\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,

		/**
		 * Regex to find non-word characters.
		 */
		nonWordRegex: /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/,

		/**
		 * Tags which can safely be merged
		 * @hide
		 */
		mergeableTags: ['b', 'code', 'del', 'em', 'i', 'ins', 'strong', 'sub', 'sup', '#text'],

		/**
		 * Tags which do not mark word boundaries
		 * @hide
		 */
		nonWordBoundaryTags: ['a', 'b', 'code', 'del', 'em', 'i', 'ins', 'span', 'strong', 'sub', 'sup', '#text'],

		/**
		 * Tags which are considered 'nonempty', even if they have no children (or not data)
		 * TODO: finish this list
		 * @hide
		 */
		nonEmptyTags: ['br'],

		/**
		 * Tags which make up Flow Content or Phrasing Content, according to the HTML 5 specification,
		 * @see http://dev.w3.org/html5/spec/Overview.html#flow-content
		 * @see http://dev.w3.org/html5/spec/Overview.html#phrasing-content
		 * @hide
		 */
		tags: {
			'flow': ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'blockquote', 'br', 'button', 'canvas', 'cite', 'code', 'command', 'datalist', 'del', 'details', 'dfn', 'div', 'dl', 'em', 'embed', 'fieldset', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'menu', 'meter', 'nav', 'noscript', 'object', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'span', 'strong', 'style', 'sub', 'sup', 'svg', 'table', 'textarea', 'time', 'u', 'ul', 'var', 'video', 'wbr', '#text'],
			'phrasing': ['a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite', 'code', 'command', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript', 'object', 'output', 'progress', 'q', 'ruby', 'samp', 'script', 'select', 'small', 'span', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'u', 'var', 'video', 'wbr', '#text']
		},

		/**
		 * Possible children of tags, according to the HTML 5
		 * specification.
		 * See http://dev.w3.org/html5/spec/Overview.html#elements-1
		 * Moved to http://www.whatwg.org/specs/web-apps/current-work/#elements-1
		 * @hide
		 */
		children: {
			'a': 'phrasing', // transparent
			'abbr': 'phrasing',
			'address': 'flow',
			'area': 'empty',
			'article': 'flow',
			'aside': 'flow',
			'audio': 'source', // transparent
			'b': 'phrasing',
			'base': 'empty',
			'bdo': 'phrasing',
			'blockquote': 'phrasing',
			'body': 'flow',
			'br': 'empty',
			'button': 'phrasing',
			'canvas': 'phrasing', // transparent
			'caption': 'flow',
			'cite': 'phrasing',
			'code': 'phrasing',
			'col': 'empty',
			'colgroup': 'col',
			'command': 'empty',
			'datalist': ['phrasing', 'option'],
			'dd': 'flow',
			'del': 'phrasing',
			'div': 'flow',
			'details': ['summary', 'flow'],
			'dfn': 'flow',
			'dl': ['dt', 'dd'],
			'dt': 'phrasing', // varies
			'em': 'phrasing',
			'embed': 'empty',
			'fieldset': ['legend', 'flow'],
			'figcaption': 'flow',
			'figure': ['figcaption', 'flow'],
			'footer': 'flow',
			'form': 'flow',
			'h1': 'phrasing',
			'h2': 'phrasing',
			'h3': 'phrasing',
			'h4': 'phrasing',
			'h5': 'phrasing',
			'h6': 'phrasing',
			//head
			'header': 'flow',
			'hgroup': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
			'hr': 'empty',
			//html :)
			'i': 'phrasing',
			'iframe': '#text',
			'img': 'empty',
			'input': 'empty',
			'ins': 'phrasing', // transparent
			'kbd': 'phrasing',
			'keygen': 'empty',
			'label': 'phrasing',
			'legend': 'phrasing',
			'li': 'flow',
			'link': 'empty',
			'map': 'area', // transparent
			'mark': 'phrasing',
			'menu': ['li', 'flow'],
			'meta': 'empty',
			'meter': 'phrasing',
			'nav': 'flow',
			'noscript': 'phrasing', // varies
			'object': 'param', // transparent
			'ol': 'li',
			'optgroup': 'option',
			'option': '#text',
			'output': 'phrasing',
			'p': 'phrasing',
			'param': 'empty',
			'pre': 'phrasing',
			'progress': 'phrasing',
			'q': 'phrasing',
			'rp': 'phrasing',
			'rt': 'phrasing',
			'ruby': ['phrasing', 'rt', 'rp'],
			's': 'phrasing',
			'samp': 'pharsing',
			'script': '#script', //script
			'section': 'flow',
			'select': ['option', 'optgroup'],
			'small': 'phrasing',
			'source': 'empty',
			'span': 'phrasing',
			'strong': 'phrasing',
			'style': 'phrasing', // varies
			'sub': 'phrasing',
			'summary': 'phrasing',
			'sup': 'phrasing',
			'table': ['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr'],
			'tbody': 'tr',
			'td': 'flow',
			'textarea': '#text',
			'tfoot': 'tr',
			'th': 'phrasing',
			'thead': 'tr',
			'time': 'phrasing',
			'title': '#text',
			'tr': ['th', 'td'],
			'track': 'empty',
			'u': 'phrasing',
			'ul': 'li',
			'var': 'phrasing',
			'video': 'source', // transparent
			'wbr': 'empty'
		},

		/**
		 * List of nodenames of blocklevel elements
		 * TODO: finish this list
		 * @hide
		 */
		blockLevelElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'div', 'pre'],

		/**
		 * List of nodenames of list elements
		 * @hide
		 */
		listElements: ['li', 'ol', 'ul'],

		/**
		 * Splits a DOM element at the given position up until the limiting object(s), so that it is valid HTML again afterwards.
		 * @param {RangeObject} range Range object that indicates the position of the splitting.
		 *				This range will be updated, so that it represents the same range as before the split.
		 * @param {jQuery} limit Limiting node(s) for the split.
		 *				The limiting node will not be included in the split itself.
		 *				If no limiting object is set, the document body will be the limiting object.
		 * @param {boolean} atEnd If set to true, the DOM will be splitted at the end of the range otherwise at the start.
		 * @return {object} jQuery object containing the two root DOM objects of the split, true if the DOM did not need to be split or false if the DOM could not be split
		 * @method
		 */
		split: function (range, limit, atEnd) {
			var splitElement = jQuery(range.startContainer),
				splitPosition = range.startOffset,
				updateRange,
			    path,
			    parents,
				newDom,
			    insertElement,
			    secondPart,
				i,
			    pathLength,
			    element,
			    jqelement,
			    children,
			    newElement,
				next,
			    prev,
			    offset;

			if (atEnd) {
				splitElement = jQuery(range.endContainer);
				splitPosition = range.endOffset;
			}

			if (limit.length < 1) {
				limit = jQuery(document.body);
			}

			// we may have to update the range if it is not collapsed and we are splitting at the start
			updateRange = (!range.isCollapsed() && !atEnd);

			// find the path up to the highest object that will be splitted
			parents = splitElement.parents().get();
			parents.unshift(splitElement.get(0));

			jQuery.each(parents, function (index, element) {
				var isLimit = limit.filter(
					function () {
						return this == element;
					}
				).length;
				if (isLimit) {
					if (index > 0) {
						path = parents.slice(0, index);
					}
					return false;
				}
			});

			// nothing found to split -> return here
			if (!path) {
				return true;
			}

			path = path.reverse();

			// iterate over the path, create new dom nodes for every element and move
			// the contents right of the split to the new element
			for (i = 0, pathLength = path.length; i < pathLength; ++i) {
				element = path[i];
				if (i === pathLength - 1) {
					// last element in the path -> we have to split it

					// split the last part into two parts
					if (element.nodeType === 3) {
						// text node
						secondPart = document.createTextNode(element.data.substring(splitPosition, element.data.length));
						element.data = element.data.substring(0, splitPosition);
						if (this.isEmpty(secondPart) && jQuery('br', newDom).length === 0) {
							secondPart = jQuery('<br/>').addClass('aloha-end-br');
						}
					} else {
						// other nodes
						jqelement = jQuery(element);
						children = jqelement.contents();
						newElement = jqelement.clone(false).empty();
						secondPart = newElement.append(children.slice(splitPosition, children.length)).get(0);
						jQuery(secondPart).addClass('aloha-editing-p');
						if (secondPart.childNodes.length === 1 && secondPart.childNodes.item(0).nodeName.toLowerCase() === 'br') {
							jQuery(secondPart.childNodes.item(0)).addClass('aloha-end-br');
						}
					}

					// update the range if necessary
					if (updateRange && range.endContainer === element) {
						range.endContainer = secondPart;
						range.endOffset -= splitPosition;
						range.clearCaches();
					}

					// add the second part
					if (insertElement) {
						insertElement.prepend(secondPart);
					} else {
						jQuery(element).after(secondPart);
					}
				} else {
					// create the new element of the same type and prepend it to the previously created element
					newElement = jQuery(element).clone(false).empty();

					if (!newDom) {
						newDom = newElement;
					} else {
						insertElement.prepend(newElement);
					}
					insertElement = newElement;

					// move all contents right of the split to the new element
					while (true) {
						next = path[i + 1].nextSibling;
						if (!next) {
							break;
						}
						insertElement.append(next);
					}

					// update the range if necessary
					if (updateRange && range.endContainer === element) {
						range.endContainer = newElement.get(0);
						prev = path[i + 1];
						offset = 0;
						while (true) {
							prev = prev.previousSibling;
							if (!prev) {
								break;
							}
							offset++;
						}
						range.endOffset -= offset;
						range.clearCaches();
					}
				}
			}

			// append the new dom
			jQuery(path[0]).after(newDom);

			return jQuery([path[0], newDom ? newDom.get(0) : secondPart]);
		},

		/**
		 * Check whether the HTML 5 specification allows direct nesting of the given DOM
		 * objects.
		 * @param {object} outerDOMObject
		 *            outer (nesting) DOM Object
		 * @param {object} innerDOMObject
		 *            inner (nested) DOM Object
		 * @return {boolean} true when the nesting is allowed, false if not
		 * @method
		 */
		allowsNesting: function (outerDOMObject, innerDOMObject) {
			if (!outerDOMObject || !outerDOMObject.nodeName || !innerDOMObject || !innerDOMObject.nodeName) {
				return false;
			}

			var outerNodeName = outerDOMObject.nodeName.toLowerCase(),
				innerNodeName = innerDOMObject.nodeName.toLowerCase();

			if (!this.children[outerNodeName]) {
				return false;
			}

			// check whether the nesting is configured by node names (like for table)
			if (this.children[outerNodeName] == innerNodeName) {
				return true;
			}
			if (jQuery.isArray(this.children[outerNodeName]) && jQuery.inArray(innerNodeName, this.children[outerNodeName]) >= 0) {
				return true;
			}

			if (jQuery.isArray(this.tags[this.children[outerNodeName]])
				    && jQuery.inArray(innerNodeName, this.tags[this.children[outerNodeName]]) >= 0) {
				return true;
			}

			return false;
		},

		/**
		 * Apply the given markup additively to the given range. The given rangeObject will be modified if necessary
		 * @param {GENTICS.Utils.RangeObject} rangeObject range to which the markup shall be added
		 * @param {jQuery} markup markup to be applied as jQuery object
		 * @param {boolean} allownesting true when nesting of the added markup is allowed, false if not (default: false)
		 * @method
		 */
		addMarkup: function (rangeObject, markup, nesting) {
			// split partially contained text nodes at the start and end of the range
			if (rangeObject.startContainer.nodeType === 3
				    && rangeObject.startOffset > 0
				    && rangeObject.startOffset < rangeObject.startContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.startContainer).parent(), false);
			}
			if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset > 0 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.endContainer).parent(), true);
			}

			// get the range tree
			var rangeTree = rangeObject.getRangeTree();
			this.recursiveAddMarkup(rangeTree, markup, rangeObject, nesting);

			// cleanup DOM
			this.doCleanup({
				'merge': true,
				'removeempty': true
			}, rangeObject);
		},

		/**
		 * Recursive helper method to add the given markup to the range
		 * @param rangeTree rangetree at the current level
		 * @param markup markup to be applied
		 * @param rangeObject range object, which eventually is updated
		 * @param nesting true when nesting of the added markup is allowed, false if not
		 * @hide
		 */
		recursiveAddMarkup: function (rangeTree, markup, rangeObject, nesting) {
			var i, innerRange, rangeLength;

			// iterate through all rangetree objects of that level
			for (i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
				// check whether the rangetree object is fully contained and the markup may be wrapped around the object
				if (rangeTree[i].type == 'full' && this.allowsNesting(markup.get(0), rangeTree[i].domobj)) {
					// we wrap the object, when
					// 1. nesting of markup is allowed or the node is not of the markup to be added
					// 2. the node an element node or a non-empty text node
					if ((nesting || rangeTree[i].domobj.nodeName != markup.get(0).nodeName) && (rangeTree[i].domobj.nodeType !== 3 || jQuery.trim(rangeTree[i].domobj.data).length !== 0)) {
						// wrap the object
						jQuery(rangeTree[i].domobj).wrap(markup);

						// TODO eventually update the range (if it changed)

						// when nesting is not allowed, we remove the markup from the inner element
						if (!nesting && rangeTree[i].domobj.nodeType !== 3) {
							innerRange = new GENTICS.Utils.RangeObject();
							innerRange.startContainer = innerRange.endContainer = rangeTree[i].domobj.parentNode;
							innerRange.startOffset = 0;
							innerRange.endOffset = innerRange.endContainer.childNodes.length;
							this.removeMarkup(innerRange, markup, jQuery(rangeTree[i].domobj.parentNode));
						}
					}
				} else {
					// TODO check whether the object may be replaced by the given markup
					//if (false) {
					// TODO replace
					//} else {
					// recurse into the children (if any), but not if nesting is not
					// allowed and the object is of the markup to be added
					if ((nesting || (rangeTree[i].domobj && rangeTree[i].domobj.nodeName !== markup.get(0).nodeName)) && rangeTree[i].children && rangeTree[i].children.length > 0) {
						this.recursiveAddMarkup(rangeTree[i].children, markup);
					}
				}
			}
		},

		/**
		 * Find the highest occurrence of a node with given nodename within the parents
		 * of the start. When limit objects are given, the search stops there.
		 * The limiting object is of the found type, it won't be considered
		 * @param {DOMObject} start start object
		 * @param {String} nodeName name of the node to search for (case-insensitive)
		 * @param {jQuery} limit Limiting node(s) as jQuery object (if none given, the search will stop when there are no more parents)
		 * @return {DOMObject} the found DOM object or undefined
		 * @method
		 */
		findHighestElement: function (start, nodeName, limit) {
			nodeName = nodeName.toLowerCase();

			// this will be the highest found markup object (up to a limit object)
			var highestObject,
			// blah
			    testObject = start,
				// helper function to stop when we reach a limit object
				isLimit = limit ? function () {
					return limit.filter(
						function () {
							return testObject == this;
						}
					).length;
				} : function () {
					return false;
				};

			// now get the highest parent that has the given markup (until we reached
			// one of the limit objects or there are no more parent nodes)
			while (!isLimit() && testObject) {
				if (testObject.nodeName.toLowerCase() === nodeName) {
					highestObject = testObject;
				}
				testObject = testObject.parentNode;
			}

			return highestObject;
		},

		/**
		 * Remove the given markup from the given range. The given rangeObject will be modified if necessary
		 * TODO: add parameter deep/shallow
		 * @param {GENTICS.Utils.RangeObject} rangeObject range from which the markup shall be removed
		 * @param {jQuery} markup markup to be removed as jQuery object
		 * @param {jQuery} limit Limiting node(s) as jQuery object
		 * @method
		 */
		removeMarkup: function (rangeObject, markup, limit) {
			var nodeName = markup.get(0).nodeName,
				startSplitLimit = this.findHighestElement(rangeObject.startContainer, nodeName, limit),
				endSplitLimit = this.findHighestElement(rangeObject.endContainer, nodeName, limit),
				didSplit = false,
				highestObject,
			    root,
			    rangeTree;

			if (startSplitLimit && rangeObject.startOffset > 0) {
				// when the start is in the start of its container, we don't split
				this.split(rangeObject, jQuery(startSplitLimit).parent(), false);
				didSplit = true;
			}

			if (endSplitLimit) {
				// when the end is in the end of its container, we don't split
				if (rangeObject.endContainer.nodeType === 3 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
					this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
					didSplit = true;
				}
				if (rangeObject.endContainer.nodeType === 1 && rangeObject.endOffset < rangeObject.endContainer.childNodes.length) {
					this.split(rangeObject, jQuery(endSplitLimit).parent(), true);
					didSplit = true;
				}
			}

			// when we split the DOM, we maybe need to correct the range
			if (didSplit) {
				rangeObject.correctRange();
			}

			// find the highest occurrence of the markup
			highestObject = this.findHighestElement(rangeObject.getCommonAncestorContainer(), nodeName, limit);
			root = highestObject ? highestObject.parentNode : rangeObject.getCommonAncestorContainer();

			if (root) {
				// construct the range tree
				rangeTree = rangeObject.getRangeTree(root);

				// remove the markup from the range tree
				this.recursiveRemoveMarkup(rangeTree, markup);

				// cleanup DOM
				this.doCleanup({
					'merge': true,
					'removeempty': true
				}, rangeObject, root);
			}
		},

		/**
		 * TODO: pass the range itself and eventually update it if necessary
		 * Recursive helper method to remove the given markup from the range
		 * @param rangeTree rangetree at the current level
		 * @param markup markup to be applied
		 * @hide
		 */
		recursiveRemoveMarkup: function (rangeTree, markup) {
			var i, rangeLength, content;
			// iterate over the rangetree objects of this level
			for (i = 0, rangeLength = rangeTree.length; i < rangeLength; ++i) {
				// check whether the object is the markup to be removed and is fully into the range
				if (rangeTree[i].type == 'full' && rangeTree[i].domobj.nodeName == markup.get(0).nodeName) {
					// found the markup, so remove it
					content = jQuery(rangeTree[i].domobj).contents();
					if (content.length > 0) {
						// when the object has children, we unwrap them
						content.first().unwrap();
					} else {
						// obj has no children, so just remove it
						jQuery(rangeTree[i].domobj).remove();
					}
				}

				// if the object has children, we do the recursion now
				if (rangeTree[i].children) {
					this.recursiveRemoveMarkup(rangeTree[i].children, markup);
				}
			}
		},

		/**
		 * Cleanup the DOM, starting with the given startobject (or the common ancestor container of the given range)
		 * ATTENTION: If range is a selection you need to update the selection after doCleanup
		 * Cleanup modes (given as properties in 'cleanup'):
		 * <pre>
		 * - merge: merges multiple successive nodes of same type, if this is allowed, starting at the children of the given node (defaults to false)
		 * - removeempty: removes empty element nodes (defaults to false)
		 * - mergeable: Custom function to predicate whether or not a given
		 *              element should be mergeable. Overrides `mergeableTags'.
		 * </pre>
		 * Example for calling this method:<br/>
		 * <code>GENTICS.Utils.Dom.doCleanup({merge:true,removeempty:false}, range)</code>
		 * @param {object} cleanup type of cleanup to be done
		 * @param {GENTICS.Utils.RangeObject} rangeObject range which is eventually updated
		 * @param {DOMObject} start start object, if not given, the commonancestorcontainer is used as startobject insted
		 * @return {boolean} true when the range (startContainer/startOffset/endContainer/endOffset) was modified, false if not
		 * @method
		 */
		doCleanup: function (cleanup, rangeObject, start) {
			var that = this,
				prevNode,
			    modifiedRange,
			    startObject,
			    startOffset,
			    endOffset;

			if (typeof cleanup === 'undefined') {
				cleanup = {};
			}
			if (typeof cleanup.merge === 'undefined') {
				cleanup.merge = false;
			}
			if (typeof cleanup.removeempty === 'undefined') {
				cleanup.removeempty = false;
			}

			if (typeof start === 'undefined' && rangeObject) {
				start = rangeObject.getCommonAncestorContainer();
			}
			// remember the previous node here (successive nodes of same type will be merged into this)
			prevNode = false;
			// check whether the range needed to be modified during merging
			modifiedRange = false;
			// get the start object
			startObject = jQuery(start);
			startOffset = rangeObject.startOffset;
			endOffset = rangeObject.endOffset;

			// iterate through all sub nodes
			startObject.contents().each(function () {
				var nodeType;

				// Try to read the nodeType property and return if we do not have permission
				// ie.: frame document to an external URL
				try {
					nodeType = this.nodeType;
				} catch (e) {
					return;
				}

				// decide further actions by node type
				switch (nodeType) {
				// found a non-text node
				case 1:
					var thisNodeName = this.nodeName;
					if (prevNode && prevNode.nodeName === thisNodeName) {
						// found a successive node of same type

						// now we check whether the selection starts or ends in the mother node after the current node
						if (rangeObject.startContainer === startObject && startOffset > getIndexInParent(this)) {
							// there will be one less object, so reduce the startOffset by one
							rangeObject.startOffset -= 1;
							// set the flag for range modification
							modifiedRange = true;
						}
						if (rangeObject.endContainer === startObject && endOffset > getIndexInParent(this)) {
							// there will be one less object, so reduce the endOffset by one
							rangeObject.endOffset -= 1;
							// set the flag for range modification
							modifiedRange = true;
						}

						// merge the contents of this node into the previous one
						jQuery(prevNode).append(jQuery(this).contents());

						// after merging, we eventually need to cleanup the prevNode again
						modifiedRange |= that.doCleanup(cleanup, rangeObject, prevNode);

						// remove this node
						jQuery(this).remove();

					} else {
						// do the recursion step here
						modifiedRange |= that.doCleanup(cleanup, rangeObject, this);

						// eventually remove empty elements
						var removed = false;
						if (cleanup.removeempty) {
							if (GENTICS.Utils.Dom.isBlockLevelElement(this) && this.childNodes.length === 0) {
								//							jQuery(this).remove();
								removed = true;
							}
							if (jQuery.inArray(thisNodeName.toLowerCase(), that.mergeableTags) >= 0 && jQuery(this).text().length === 0 && this.childNodes.length === 0) {
								//							jQuery(this).remove();
								removed = true;
							}
						}

						// when the current node was not removed, we eventually store it as previous (mergeable) tag
						if (!removed) {
							if (cleanup.mergeable ? cleanup.mergeable(this) : jQuery.inArray(thisNodeName.toLowerCase(), that.mergeableTags) >= 0) {
								prevNode = this;
							} else {
								prevNode = false;
							}
						} else {
							// now we check whether the selection starts or ends in the mother node of this
							if (rangeObject.startContainer === this.parentNode && startOffset > getIndexInParent(this)) {
								// there will be one less object, so reduce the startOffset by one
								rangeObject.startOffset = rangeObject.startOffset - 1;
								// set the flag for range modification
								modifiedRange = true;
							}
							if (rangeObject.endContainer === this.parentNode && endOffset > getIndexInParent(this)) {
								// there will be one less object, so reduce the endOffset by one
								rangeObject.endOffset = rangeObject.endOffset - 1;
								// set the flag for range modification
								modifiedRange = true;
							}

							// remove this text node
							jQuery(this).remove();

						}
					}

					break;
				// found a text node
				case 3:
					// found a text node
					if (prevNode && prevNode.nodeType === 3 && cleanup.merge) {
						// the current text node will be merged into the last one, so
						// check whether the selection starts or ends in the current
						// text node
						var prevNodeValue = prevNode.nodeValue,
							prevNodeValueLength = prevNodeValue.length;

						if (rangeObject.startContainer === this) {
							// selection starts in the current text node

							// update the start container to the last node
							rangeObject.startContainer = prevNode;

							// update the start offset
							rangeObject.startOffset += prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;

						} else if (rangeObject.startContainer === prevNode.parentNode && rangeObject.startOffset === getIndexInParent(prevNode) + 1) {
							// selection starts right between the previous and current text nodes (which will be merged)

							// update the start container to the previous node
							rangeObject.startContainer = prevNode;

							// set the start offset
							rangeObject.startOffset = prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;
						}

						if (rangeObject.endContainer === this) {
							// selection ends in the current text node

							// update the end container to be the last node
							rangeObject.endContainer = prevNode;

							// update the end offset
							rangeObject.endOffset += prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;

						} else if (rangeObject.endContainer === prevNode.parentNode && rangeObject.endOffset === getIndexInParent(prevNode) + 1) {
							// selection ends right between the previous and current text nodes (which will be merged)

							// update the end container to the previous node
							rangeObject.endContainer = prevNode;

							// set the end offset
							rangeObject.endOffset = prevNodeValueLength;

							// set the flag for range modification
							modifiedRange = true;
						}

						// now append the contents of the current text node into the previous
						prevNode.data += this.data;

						// remove empty text nodes
					} else if (!(this.nodeValue === '' && cleanup.removeempty)) {
						prevNode = this;
						// we are finish here don't delete this node
						break;
					}

					// now we check whether the selection starts or ends in the mother node of this
					if (rangeObject.startContainer === this.parentNode && rangeObject.startOffset > getIndexInParent(this)) {
						// there will be one less object, so reduce the startOffset by one
						rangeObject.startOffset = rangeObject.startOffset - 1;
						// set the flag for range modification
						modifiedRange = true;
					}
					if (rangeObject.endContainer === this.parentNode && rangeObject.endOffset > getIndexInParent(this)) {
						// there will be one less object, so reduce the endOffset by one
						rangeObject.endOffset = rangeObject.endOffset - 1;
						// set the flag for range modification
						modifiedRange = true;
					}

					// remove this text node
					jQuery(this).remove();

					// if this is the last text node in a sequence, we remove any zero-width spaces in the text node,
					// unless it is the only character
					var prevNodeNextSibling = prevNode.nextSibling;
					if (prevNode && (!prevNodeNextSibling || prevNodeNextSibling.nodeType !== 3)) {
						var pos;
						var prevNodeData = prevNode.data;
						var prevNodeDataLength = prevNodeData.length;
						for (pos = prevNodeDataLength - 1; pos >= 0 && prevNodeDataLength > 1; pos--) {
							if (prevNodeData.charAt(pos) === '\u200b') {
								prevNode.deleteData(pos, 1);
								if (rangeObject.startContainer === prevNode && rangeObject.startOffset > pos) {
									rangeObject.startOffset--;
									modifiedRange = true;
								}
								if (rangeObject.endContainer === prevNode && rangeObject.endOffset > pos) {
									rangeObject.endOffset--;
									modifiedRange = true;
								}
							}
						}
					}

					break;
				}
			});

			// eventually remove the startnode itself
			//		if (cleanup.removeempty
			//				&& GENTICS.Utils.Dom.isBlockLevelElement(start)
			//				&& (!start.childNodes || start.childNodes.length === 0)) {
			//			if (rangeObject.startContainer == start) {
			//				rangeObject.startContainer = start.parentNode;
			//				rangeObject.startOffset = GENTICS.Utils.Dom.getIndexInParent(start);
			//			}
			//			if (rangeObject.endContainer == start) {
			//				rangeObject.endContainer = start.parentNode;
			//				rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(start);
			//			}
			//			startObject.remove();
			//			modifiedRange = true;
			//		}

			if (modifiedRange) {
				rangeObject.clearCaches();
			}

			return modifiedRange;
		},

		/**
		 * Check whether the given node is a blocklevel element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true if yes, false if not (or null)
		 * @method
		 */
		isBlockLevelElement: function (node) {
			if (!node) {
				return false;
			}
			if (node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.blockLevelElements) >= 0) {
				return true;
			}
			return false;
		},

		/**
		 * Check whether the given node is a linebreak element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true for linebreak elements, false for everything else
		 * @method
		 */
		isLineBreakElement: function (node) {
			if (!node) {
				return false;
			}
			return node.nodeType === 1 && node.nodeName.toLowerCase() == 'br';
		},

		/**
		 * Check whether the given node is a list element
		 * @param {DOMObject} node node to check
		 * @return {boolean} true for list elements (li, ul, ol), false for everything else
		 * @method
		 */
		isListElement: function (node) {
			if (!node) {
				return false;
			}
			return node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), this.listElements) >= 0;
		},

		/**
		 * This method checks, whether the passed dom object is a dom object, that would
		 * be split in cases of pressing enter. This currently is true for paragraphs
		 * and headings
		 * @param {DOMObject} el
		 *            dom object to check
		 * @return {boolean} true for split objects, false for other
		 * @method
		 */
		isSplitObject: function (el) {
			return el.nodeType === 1 && blockElementNames.hasOwnProperty(el.nodeName);
		},

		/**
		 * Starting with the given position (between nodes), search in the given direction to an adjacent notempty text node
		 * @param {DOMObject} parent parent node containing the position
		 * @param {Integer} index index of the position within the parent node
		 * @param {boolean} searchleft true when search direction is 'left' (default), false for 'right'
		 * @param {object} stopat define at which types of element we shall stop, may contain the following properties
		 * <pre>
		 * - blocklevel (default: true)
		 * - list (default: true)
		 * - linebreak (default: true)
		 * </pre>
		 * @return {DOMObject} the found text node or false if none found
		 * @method
		 */
		searchAdjacentTextNode: function (parent, index, searchleft, stopat) {
			if (!parent || parent.nodeType !== 1 || index < 0 || index > parent.childNodes.length) {
				return false;
			}

			if (typeof stopat === 'undefined') {
				stopat = {
					'blocklevel': true,
					'list': true,
					'linebreak': true
				};
			}

			if (typeof stopat.blocklevel === 'undefined') {
				stopat.blocklevel = true;
			}
			if (typeof stopat.list === 'undefined') {
				stopat.list = true;
			}
			if (typeof stopat.linebreak === 'undefined') {
				stopat.linebreak = true;
			}

			if (typeof searchleft === 'undefined') {
				searchleft = true;
			}

			var nextNode,
			    currentParent = parent;

			// start at the node left/right of the given position
			if (searchleft && index > 0) {
				nextNode = parent.childNodes[index - 1];
			}
			if (!searchleft && index < parent.childNodes.length) {
				nextNode = parent.childNodes[index];
			}

			//currentParent is not a number therefore it is sufficient to directly test for it with while(currentParent)
			//otherwise there would be an error if the object is null
			while (currentParent) {
				//while (typeof currentParent !== 'undefined') {
				if (!nextNode) {
					// no next node found, check whether the parent is a blocklevel element
					if (stopat.blocklevel && this.isBlockLevelElement(currentParent)) {
						// do not leave block level elements
						return false;
					}
					if (stopat.list && this.isListElement(currentParent)) {
						// do not leave list elements
						return false;
					}
					// continue with the parent
					nextNode = searchleft ? currentParent.previousSibling : currentParent.nextSibling;
					currentParent = currentParent.parentNode;
					continue;
				} else if (nextNode.nodeType === 3 && jQuery.trim(nextNode.data).length > 0) {
					// we are lucky and found a notempty text node
					return nextNode;
				}
				if (stopat.blocklevel && this.isBlockLevelElement(nextNode)) {
					// we found a blocklevel element, stop here
					return false;
				}
				if (stopat.linebreak && this.isLineBreakElement(nextNode)) {
					// we found a linebreak, stop here
					return false;
				}
				if (stopat.list && this.isListElement(nextNode)) {
					// we found a linebreak, stop here
					return false;
				}
				if (nextNode.nodeType === 3) {
					// we found an empty text node, so step to the next
					nextNode = searchleft ? nextNode.previousSibling : nextNode.nextSibling;
				} else {
					// we found a non-blocklevel element, step into
					currentParent = nextNode;
					nextNode = searchleft ? nextNode.lastChild : nextNode.firstChild;
				}
			}
		},

		/**
		 * Insert the given DOM Object into the start/end of the given range. The method
		 * will find the appropriate place in the DOM tree for inserting the given
		 * object, and will eventually split elements in between. The given range will
		 * be updated if necessary. The updated range will NOT embrace the inserted
		 * object, which means that the object is actually inserted before or after the
		 * given range (depending on the atEnd parameter)
		 *
		 * @param {jQuery}
		 *				object object to insert into the DOM
		 * @param {GENTICS.Utils.RangeObject}
		 *				range range where to insert the object (at start or end)
		 * @param {jQuery}
		 *				limit limiting object(s) of the DOM modification
		 * @param {boolean}
		 *				atEnd true when the object shall be inserted at the end, false for
		 *				insertion at the start (default)
		 * @param {boolean}
		 *				true when the insertion shall be done, even if inserting the element
		 *				would not be allowed, false to deny inserting unallowed elements (default)
		 * @return true if the object could be inserted, false if not.
		 * @method
		 */
		insertIntoDOM: function (object, range, limit, atEnd, force) {
			// first find the appropriate place to insert the given object
			var parentElements = range.getContainerParents(limit, atEnd),
				that = this,
				newParent,
				container,
			    offset,
			    splitParts,
			    contents;

			if (!limit) {
				limit = jQuery(document.body);
			}

			// if no parent elements exist (up to the limit), the new parent will be the
			// limiter itself
			if (parentElements.length === 0) {
				newParent = limit.get(0);
			} else {
				jQuery.each(parentElements, function (index, parent) {
					if (that.allowsNesting(parent, object.get(0))) {
						newParent = parent;
						return false;
					}
				});
			}

			if (typeof newParent === 'undefined' && limit.length > 0) {
				// found no possible new parent, so split up to the limit object
				newParent = limit.get(0);
			}

			// check whether it is allowed to insert the element at all
			if (!this.allowsNesting(newParent, object.get(0)) && !force) {
				return false;
			}

			if (typeof newParent !== 'undefined') {
				// we found a possible new parent, so we split the DOM up to the new parent
				splitParts = this.split(range, jQuery(newParent), atEnd);
				if (splitParts === true) {
					// DOM was not split (there was no need to split it), insert the new object anyway
					container = range.startContainer;
					offset = range.startOffset;
					if (atEnd) {
						container = range.endContainer;
						offset = range.endOffset;
					}
					if (offset === 0) {
						// insert right before the first element in the container
						contents = jQuery(container).contents();
						if (contents.length > 0) {
							contents.eq(0).before(object);
						} else {
							jQuery(container).append(object);
						}
						return true;
					}
					// insert right after the element at offset-1
					jQuery(container).contents().eq(offset - 1).after(object);
					return true;
				}
				if (splitParts) {
					// ASSERT(splitParts.length === 2)
					var head = splitParts[0];
					var tail = splitParts[1];
					insertAfterSplit(head, object);
					if (isPhantomNode(tail)) {
						var afterTail = tail.nextSibling
						             || (tail.parentNode && tail.parentNode.nextSibling);
						if (findNodeForward(afterTail, isNotVisiblyEmpty)) {
							// Because the tail element that is generated from
							// the splitting is superfluous since there is
							// already a visible element in which to place the
							// selection.
							jQuery(tail).remove();
						}
					}
					return true;
				}
				// could not split, so could not insert
				return false;
			}
			// found no possible new parent, so we shall not insert
			return false;
		},

		/**
		 * Remove the given DOM object from the DOM and modify the given range to reflect the user expected range after the object was removed
		 * TODO: finish this
		 * @param {DOMObject} object DOM object to remove
		 * @param {GENTICS.Utils.RangeObject} range range which eventually be modified
		 * @param {boolean} preserveContent true if the contents of the removed DOM object shall be preserved, false if not (default: false)
		 * @return true if the DOM object could be removed, false if not
		 * @hide
		 */
		removeFromDOM: function (object, range, preserveContent) {
			if (preserveContent) {
				// check whether the range will need modification
				var indexInParent = getIndexInParent(object),
					numChildren = jQuery(object).contents().length,
					parent = object.parentNode;

				if (range.startContainer == parent && range.startOffset > indexInParent) {
					range.startOffset += numChildren - 1;
				} else if (range.startContainer == object) {
					range.startContainer = parent;
					range.startOffset = indexInParent + range.startOffset;
				}

				if (range.endContainer == parent && range.endOffset > indexInParent) {
					range.endOffset += numChildren - 1;
				} else if (range.endContainer == object) {
					range.endContainer = parent;
					range.endOffset = indexInParent + range.endOffset;
				}

				// we simply unwrap the children of the object
				jQuery(object).contents().unwrap();

				// optionally do cleanup
				this.doCleanup({
					'merge': true
				}, range, parent);
			}
		},

		/**
		 * Remove the content defined by the given range from the DOM. Update the given
		 * range object to be a collapsed selection at the place of the previous
		 * selection.
		 * @param rangeObject range object
		 * @return true if the range could be removed, false if not
		 */
		removeRange: function (rangeObject) {
			if (!rangeObject) {
				// no range given
				return false;
			}
			if (rangeObject.isCollapsed()) {
				// the range is collapsed, nothing to delete
				return false;
			}

			// split partially contained text nodes at the start and end of the range
			if (rangeObject.startContainer.nodeType == 3
				    && rangeObject.startOffset > 0
				    && rangeObject.startOffset < rangeObject.startContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.startContainer).parent(), false);
			}
			if (rangeObject.endContainer.nodeType == 3 && rangeObject.endOffset > 0 && rangeObject.endOffset < rangeObject.endContainer.data.length) {
				this.split(rangeObject, jQuery(rangeObject.endContainer).parent(), true);
			}

			// construct the range tree
			var rangeTree = rangeObject.getRangeTree();

			// collapse the range
			rangeObject.endContainer = rangeObject.startContainer;
			rangeObject.endOffset = rangeObject.startOffset;

			// remove the markup from the range tree
			this.recursiveRemoveRange(rangeTree, rangeObject);

			// do some cleanup
			this.doCleanup({
				'merge': true
			}, rangeObject);
			//		this.doCleanup({'merge' : true, 'removeempty' : true}, rangeObject);

			// clear the caches of the range object
			rangeObject.clearCaches();
		},

		recursiveRemoveRange: function (rangeTree, rangeObject) {
			// iterate over the rangetree objects of this level
			var i;
			for (i = 0; i < rangeTree.length; ++i) {
				// check for nodes fully in the range
				if (rangeTree[i].type == 'full') {
					// if the domobj is the startcontainer, or the startcontainer is inside the domobj, we need to update the rangeObject
					if (jQuery(rangeObject.startContainer).parents().andSelf().filter(rangeTree[i].domobj).length > 0) {
						rangeObject.startContainer = rangeObject.endContainer = rangeTree[i].domobj.parentNode;
						rangeObject.startOffset = rangeObject.endOffset = getIndexInParent(rangeTree[i].domobj);
					}

					// remove the object from the DOM
					jQuery(rangeTree[i].domobj).remove();
				} else if (rangeTree[i].type == 'partial' && rangeTree[i].children) {
					// node partially selected and has children, so do recursion
					this.recursiveRemoveRange(rangeTree[i].children, rangeObject);
				}
			}
		},

		/**
		 * Extend the given range to have start and end at the nearest word boundaries to the left (start) and right (end)
		 * @param {GENTICS.Utils.RangeObject} range range to be extended
		 * @param {boolean} fromBoundaries true if extending will also be done, if one or both ends of the range already are at a word boundary, false if not, default: false
		 * @method
		 */
		extendToWord: function (range, fromBoundaries) {
			// search the word boundaries to the left and right
			var leftBoundary = this.searchWordBoundary(range.startContainer, range.startOffset, true),
				rightBoundary = this.searchWordBoundary(range.endContainer, range.endOffset, false);

			// check whether we must not extend the range from word boundaries
			if (!fromBoundaries) {
				// we only extend the range if both ends would be different
				if (range.startContainer == leftBoundary.container && range.startOffset == leftBoundary.offset) {
					return;
				}
				if (range.endContainer == rightBoundary.container && range.endOffset == rightBoundary.offset) {
					return;
				}
			}

			// set the new boundaries
			range.startContainer = leftBoundary.container;
			range.startOffset = leftBoundary.offset;
			range.endContainer = rightBoundary.container;
			range.endOffset = rightBoundary.offset;

			// correct the range
			range.correctRange();

			// clear caches
			range.clearCaches();
		},

		/**
		 * Helper method to check whether the given DOM object is a word boundary.
		 * @param {DOMObject} object DOM object in question
		 * @return {boolean} true when the DOM object is a word boundary, false if not
		 * @hide
		 */
		isWordBoundaryElement: function (object) {
			if (!object || !object.nodeName) {
				return false;
			}
			return jQuery.inArray(object.nodeName.toLowerCase(), this.nonWordBoundaryTags) == -1;
		},

		/**
		 * Search for the next word boundary, starting at the given position
		 * @param {DOMObject} container container of the start position
		 * @param {Integer} offset offset of the start position
		 * @param {boolean} searchleft true for searching to the left, false for searching to the right (default: true)
		 * @return {object} object with properties 'container' and 'offset' marking the found word boundary
		 * @method
		 */
		searchWordBoundary: function (container, offset, searchleft) {
			if (typeof searchleft === 'undefined') {
				searchleft = true;
			}
			var boundaryFound = false,
				wordBoundaryPos,
			    tempWordBoundaryPos,
			    textNode;
			while (!boundaryFound) {
				// check the node type
				var containerIndex = getIndexInParent(container);
				if (container.nodeType === 3) {
					// we are currently in a text node

					// find the nearest word boundary character
					if (!searchleft) {
						// search right
						wordBoundaryPos = container.data.substring(offset).search(this.nonWordRegex);
						if (wordBoundaryPos != -1) {
							// found a word boundary
							offset = offset + wordBoundaryPos;
							boundaryFound = true;
						} else {
							// found no word boundary, so we set the position after the container
							offset = containerIndex + 1;
							container = container.parentNode;
						}
					} else {
						// search left
						wordBoundaryPos = container.data.substring(0, offset).search(this.nonWordRegex);
						tempWordBoundaryPos = wordBoundaryPos;
						while (tempWordBoundaryPos != -1) {
							wordBoundaryPos = tempWordBoundaryPos;
							tempWordBoundaryPos = container.data.substring(wordBoundaryPos + 1, offset).search(this.nonWordRegex);
							if (tempWordBoundaryPos != -1) {
								tempWordBoundaryPos = tempWordBoundaryPos + wordBoundaryPos + 1;
							}
						}

						if (wordBoundaryPos != -1) {
							// found a word boundary
							offset = wordBoundaryPos + 1;
							boundaryFound = true;
						} else {
							// found no word boundary, so we set the position before the container
							offset = containerIndex;
							container = container.parentNode;
						}
					}
				} else if (container.nodeType === 1) {
					// we are currently in an element node (between nodes)

					if (!searchleft) {
						// check whether there is an element to the right
						if (offset < container.childNodes.length) {
							// there is an element to the right, check whether it is a word boundary element
							if (this.isWordBoundaryElement(container.childNodes[offset])) {
								// we are done
								boundaryFound = true;
							} else {
								// element to the right is no word boundary, so enter it
								container = container.childNodes[offset];
								offset = 0;
							}
						} else {
							// no element to the right, check whether the element itself is a boundary element
							if (this.isWordBoundaryElement(container)) {
								// we are done
								boundaryFound = true;
							} else {
								// element itself is no boundary element, so go to parent
								offset = containerIndex + 1;
								container = container.parentNode;
							}
						}
					} else {
						// check whether there is an element to the left
						if (offset > 0) {
							// there is an element to the left, check whether it is a word boundary element
							if (this.isWordBoundaryElement(container.childNodes[offset - 1])) {
								// we are done
								boundaryFound = true;
							} else {
								// element to the left is no word boundary, so enter it
								container = container.childNodes[offset - 1];
								offset = container.nodeType === 3 ? container.data.length : container.childNodes.length;
							}
						} else {
							// no element to the left, check whether the element itself is a boundary element
							if (this.isWordBoundaryElement(container)) {
								// we are done
								boundaryFound = true;
							} else {
								// element itself is no boundary element, so go to parent
								offset = containerIndex;
								container = container.parentNode;
							}
						}
					}
				}
			}

			if (container.nodeType !== 3) {
				textNode = this.searchAdjacentTextNode(container, offset, !searchleft);
				if (textNode) {
					container = textNode;
					offset = searchleft ? 0 : container.data.length;
				}
			}

			return {
				'container': container,
				'offset': offset
			};
		},

		/**
		 * Check whether the given dom object is empty
		 * @param {DOMObject} domObject object to check
		 * @return {boolean} true when the object is empty, false if not
		 * @method
		 */
		isEmpty: function (domObject) {
			// a non dom object is considered empty
			if (!domObject) {
				return true;
			}

			// some tags are considered to be non-empty
			if (jQuery.inArray(domObject.nodeName.toLowerCase(), this.nonEmptyTags) != -1) {
				return false;
			}

			// text nodes are not empty, if they contain non-whitespace characters
			if (domObject.nodeType === 3) {
				if (domObject.data.search(/\S/) == -1) {
					return true;
				} else if (domObject.data.length === 1 // Fix FOR IE no width chars
						&& domObject.data.charCodeAt(0) >= 0x2000) {
					return true;
				}

				return false;
			}

			// all other nodes are not empty if they contain at least one child which is not empty
			var i, childNodes;
			for (i = 0, childNodes = domObject.childNodes.length; i < childNodes; ++i) {
				if (!this.isEmpty(domObject.childNodes[i])) {
					return false;
				}
			}

			// found no contents, so the element is empty
			return true;
		},

		/**
		 * Set the cursor (collapsed selection) right after the given DOM object
		 * @param domObject DOM object
		 * @method
		 */
		setCursorAfter: function (domObject) {
			var newRange = new GENTICS.Utils.RangeObject(),
				index = getIndexInParent(domObject),
				targetNode,
				offset;

			// selection cannot be set between to TEXT_NODEs
			// if domOject is a Text node set selection at last position in that node
			if (domObject.nodeType == 3) {
				targetNode = domObject;
				offset = targetNode.nodeValue.length;

				// if domOject is a Text node set selection at last position in that node
			} else if (domObject.nextSibling && domObject.nextSibling.nodeType == 3) {
				targetNode = domObject.nextSibling;
				offset = 0;
			} else {
				targetNode = domObject.parentNode;
				offset = getIndexInParent(domObject) + 1;
			}

			newRange.startContainer = newRange.endContainer = targetNode;
			newRange.startOffset = newRange.endOffset = offset;

			// select the range
			newRange.select();

			return newRange;
		},

		/**
		 * Select a DOM node
		 * will create a new range which spans the provided dom node and selects it afterwards
		 * @param domObject DOM object
		 * @method
		 */
		selectDomNode: function (domObject) {
			var newRange = new GENTICS.Utils.RangeObject();
			newRange.startContainer = newRange.endContainer = domObject.parentNode;
			newRange.startOffset = getIndexInParent(domObject);
			newRange.endOffset = newRange.startOffset + 1;
			newRange.select();
		},

		/**
		 * Set the cursor (collapsed selection) at the start into the given DOM object
		 * @param domObject DOM object
		 * @method
		 */
		setCursorInto: function (domObject) {
			// set a new range into the given dom object
			var newRange = new GENTICS.Utils.RangeObject();
			newRange.startContainer = newRange.endContainer = domObject;
			newRange.startOffset = newRange.endOffset = 0;

			// select the range
			newRange.select();
		},


		/**
		 * "An editing host is a node that is either an Element with a contenteditable
		 * attribute set to the true state, or the Element child of a Document whose
		 * designMode is enabled."
		 * @param domObject DOM object
		 * @method
		 */
		isEditingHost: function (node) {
			return node
				&& node.nodeType == 1 //ELEMENT_NODE
				&& (node.contentEditable == "true" || (node.parentNode && node.parentNode.nodeType == 9 //DOCUEMENT_NODE
													   && node.parentNode.designMode == "on"));
		},

		/**
		 * "Something is editable if it is a node which is not an editing host, does
		 * not have a contenteditable attribute set to the false state, and whose
		 * parent is an editing host or editable."
		 * @param domObject DOM object
		 * @method
		 */
		isEditable: function (node) {
			// This is slightly a lie, because we're excluding non-HTML elements with
			// contentEditable attributes.
			return node
				&& !this.isEditingHost(node)
				&& (node.nodeType != 1 || node.contentEditable != "false") // ELEMENT_NODE
				&& (this.isEditingHost(node.parentNode) || this.isEditable(node.parentNode));
		},

		/**
		 * "The editing host of node is null if node is neither editable nor an editing
		 * host; node itself, if node is an editing host; or the nearest ancestor of
		 * node that is an editing host, if node is editable."
		 * @param domObject DOM object
		 * @method
		 */
		getEditingHostOf: function (node) {
			if (this.isEditingHost(node)) {
				return node;
			}
			if (this.isEditable(node)) {
				var ancestor = node.parentNode;
				while (!this.isEditingHost(ancestor)) {
					ancestor = ancestor.parentNode;
				}
				return ancestor;
			}
			return null;
		},

		/**
		 *
		 * "Two nodes are in the same editing host if the editing host of the first is
		 * non-null and the same as the editing host of the second."
		 * @param node1 DOM object
		 * @param node2 DOM object
		 * @method
		 */
		inSameEditingHost: function (node1, node2) {
			return this.getEditingHostOf(node1) && this.getEditingHostOf(node1) == this.getEditingHostOf(node2);
		},

		// "A block node is either an Element whose "display" property does not have
		// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
		// Document, or a DocumentFragment."
		isBlockNode: function (node) {
			return node && ((node.nodeType == $_.Node.ELEMENT_NODE && $_(["inline", "inline-block", "inline-table", "none"]).indexOf($_.getComputedStyle(node).display) == -1) || node.nodeType == $_.Node.DOCUMENT_NODE || node.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE);
		},

		/**
		 * Get the first visible child of the given node.
		 * @param node node
		 * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
		 * @return first visible child or null if none found
		 */
		getFirstVisibleChild: function (node, includeNode) {
			// no node -> no child
			if (!node) {
				return null;
			}

			// check whether the node itself is visible
			if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node)) || (node.nodeType == $_.Node.ELEMENT_NODE && node.offsetHeight == 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
				return null;
			}

			// if the node is a text node, or does not have children, or is not editable, it is the first visible child
			if (node.nodeType == $_.Node.TEXT_NODE || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0) || !jQuery(node).contentEditable()) {
				return includeNode ? node : null;
			}

			// otherwise traverse through the children
			var i;
			for (i = 0; i < node.childNodes.length; ++i) {
				var visibleChild = this.getFirstVisibleChild(node.childNodes[i], true);
				if (visibleChild != null) {
					return visibleChild;
				}
			}

			return null;
		},

		/**
		 * Get the last visible child of the given node.
		 * @param node node
		 * @param includeNode when set to true, the node itself may be returned, otherwise only children are allowed
		 * @return last visible child or null if none found
		 */
		getLastVisibleChild: function (node, includeNode) {
			// no node -> no child
			if (!node) {
				return null;
			}

			// check whether the node itself is visible
			if ((node.nodeType == $_.Node.TEXT_NODE && this.isEmpty(node)) || (node.nodeType == $_.Node.ELEMENT_NODE && node.offsetHeight == 0 && jQuery.inArray(node.nodeName.toLowerCase(), this.nonEmptyTags) === -1)) {
				return null;
			}

			// if the node is a text node, or does not have children, or is not editable, it is the first visible child
			if (node.nodeType == $_.Node.TEXT_NODE || (node.nodeType == $_.Node.ELEMENT_NODE && node.childNodes.length == 0) || !jQuery(node).contentEditable()) {
				return includeNode ? node : null;
			}

			// otherwise traverse through the children
			var i;
			for (i = node.childNodes.length - 1; i >= 0; --i) {
				var visibleChild = this.getLastVisibleChild(node.childNodes[i], true);
				if (visibleChild != null) {
					return visibleChild;
				}
			}

			return null;
		}
	});


	/**
	 * Create the singleton object
	 * @hide
	 */
	GENTICS.Utils.Dom = new Dom();

	return GENTICS.Utils.Dom;

});

/* pluginmanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Do not add dependencies that require depend on aloha/core
define('aloha/pluginmanager',[
	'jquery',
	'util/class'
], function (
	$,
	Class
) {
	

	var Aloha = window.Aloha;

	/**
	 * For each plugin setting, assigns it into its corresponding plugin.
	 *
	 * @param {Array.<Plugin>} plugins
	 * @param {object<string, object>} settings
	 */
	function mapSettingsIntoPlugins(plugins, settings) {
		var plugin;
		for (plugin in settings) {
			if (settings.hasOwnProperty(plugin) && plugins[plugin]) {
				plugins[plugin].settings = settings[plugin] || {};
			}
		}
	}

	/**
	 * Retrieves a set of plugins or the given `names' list, from among those
	 * specified in `plugins'.
	 *
	 * @param {object<string, object>} plugins
	 * @param {Array.<string>} names List of plugins names.
	 * @return {Array.<Plugins>} List of available plugins.
	 */
	function getPlugins(plugins, names) {
		var available = [];
		var plugin;
		var i;
		for (i = 0; i < names.length; i++) {
			plugin = plugins[names[i]];
			if (plugin) {
				available.push(plugin);
			}
		}
		return available;
	}

	/**
	 * Initializes the plugins in the given list.
	 *
	 * @param {Array.<Plugins>} plugins Plugins to initialize.
	 * @param {function} callback Function to invoke once all plugins have been
	 *                            successfully initialized.
	 */
	function initializePlugins(plugins, callback) {
		if (0 === plugins.length) {
			if (callback) {
				callback();
			}
			return;
		}
		var numToEnable = plugins.length;
		var onInit = function () {
			if (0 === --numToEnable && callback) {
				callback();
			}
		};
		var i;
		var ret;
		var plugin;
		for (i = 0; i < plugins.length; i++) {
			plugin = plugins[i];
			plugin.settings = plugin.settings || {};
			if (typeof plugin.settings.enabled === 'undefined') {
				plugin.settings.enabled = true;
			}
			if (plugin.settings.enabled && plugin.checkDependencies()) {
				ret = plugin.init();
				if (ret && typeof ret.done === 'function') {
					ret.done(onInit);
				} else {
					onInit();
				}
			} else {
				onInit();
			}
		}
	}

	/**
	 * The Plugin Manager controls the lifecycle of all Aloha Plugins.
	 *
	 * @namespace Aloha
	 * @class PluginManager
	 * @singleton
	 */
	return new (Class.extend({

		plugins: {},

		/**
		 * Initialize all registered plugins.
		 *
		 * @param {function} next Callback to invoke after plugins have
		 *                        succefully initialized.
		 * @param {Array.<string>} enabled A list of plugin names which are to
		 *                                 be enable.
		 */
		init: function (next, enabled) {
			var manager = this;
			var plugins = manager.plugins;

			mapSettingsIntoPlugins(plugins,
					Aloha && Aloha.settings && Aloha.settings.plugins);

			// Because all plugins are enabled by default if specific plugins
			// are not specified.
			var plugin;
			if (plugins && 0 === enabled.length) {
				enabled = [];
				for (plugin in plugins) {
					if (plugins.hasOwnProperty(plugin)) {
						enabled.push(plugin);
					}
				}
			}

			initializePlugins(getPlugins(plugins, enabled), next);
		},

		/**
		 * Register a plugin
		 * @param {Plugin} plugin plugin to register
		 */
		register: function (plugin) {

			if (!plugin.name) {
				throw new Error('Plugin does not have an name.');
			}

			if (this.plugins[plugin.name]) {
				throw new Error('Already registered the plugin "' + plugin.name + '"!');
			}

			this.plugins[plugin.name] = plugin;
		},

		/**
		 * Pass the given jQuery object, which represents an editable to all plugins, so that they can make the content clean (prepare for saving)
		 * @param obj jQuery object representing an editable
		 * @return void
		 * @hide
		 */
		makeClean: function (obj) {
			var i, plugin;
			// iterate through all registered plugins
			for (plugin in this.plugins) {
				if (this.plugins.hasOwnProperty(plugin)) {
					if (Aloha.Log.isDebugEnabled()) {
						Aloha.Log.debug(this, 'Passing contents of HTML Element with id { ' + obj.attr('id') + ' } for cleaning to plugin { ' + plugin + ' }');
					}
					this.plugins[plugin].makeClean(obj);
				}
			}
		},

		/**
		 * Expose a nice name for the Plugin Manager
		 * @hide
		 */
		toString: function () {
			return 'pluginmanager';
		}

	}))();
});

/*core.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/core',[
	'jquery',
	'aloha/pluginmanager'
], function (
	$,
	PluginManager
) {
	

	var Aloha = window.Aloha;

	/**
	 * Checks whether the current user agent is supported.
	 *
	 * @return {boolean} True if Aloha supports the current browser.
	 */
	function isBrowserSupported() {
		var browser = $.browser;
		var version = browser.version;
		return !(
			// Chrome/Safari 4
			(browser.webkit && parseFloat(version) < 532.5) ||
			// FF 3.5
			(browser.mozilla && parseFloat(version) < 1.9) ||
			// IE 7
			(browser.msie && version < 7) ||
			// Right now Opera needs some work
			(browser.opera && version < 11)
		);
	}

	/**
	 * Checks whether the given jQuery event originates from an Aloha dialog
	 * element.
	 *
	 * This is used to facilitate a hackish way of preventing blurring
	 * editables when interacting with Aloha UI modals.
	 *
	 * @param {jQuery<Event>} $event
	 * @return {boolean} True if $event is initiated from within an Aloha
	 *                   dialog element.
	 */
	function originatesFromDialog($event) {
		var $target = $($event.target);
		return $target.is('.aloha-dialog')
			|| $target.closest('.aloha').length;
	}

	/**
	 * Registers events on the documents to cause editables to be blurred when
	 * clicking outside of editables.
	 *
	 * Hack: Except when the click originates from a modal dialog.
	 */
	function registerEvents() {
		$('html').mousedown(function ($event) {
			if (Aloha.activeEditable && !Aloha.eventHandled
					&& !originatesFromDialog($event)) {
				Aloha.deactivateEditable();
			}
		}).mouseup(function () {
			Aloha.eventHandled = false;
		});
	}

	/**
	 * Initialize Aloha.
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Function to call after completing tasks.
	 */
	function initAloha(event, next) {
		if (!isBrowserSupported()) {
			var console = window.console;
			if (console) {
				var fn = console.error ? 'error' : console.log ? 'log' : null;
				if (fn) {
					console[fn]('This browser is not supported');
				}
			}
			return;
		}

		// Because different css is to be applied based on what the user-agent
		// supports.  For example: outlines do not render in IE7.
		if ($.browser.webkit) {
			$('html').addClass('aloha-webkit');
		} else if ($.browser.opera) {
			$('html').addClass('aloha-opera');
		} else if ($.browser.msie) {
			$('html').addClass('aloha-ie' + parseInt($.browser.version, 10));
		} else if ($.browser.mozilla) {
			$('html').addClass('aloha-mozilla');
		}

		if (navigator.appVersion.indexOf('Win') !== -1) {
			Aloha.OSName = 'Win';
		} else if (navigator.appVersion.indexOf('Mac') !== -1) {
			Aloha.OSName = 'Mac';
		} else if (navigator.appVersion.indexOf('X11') !== -1) {
			Aloha.OSName = 'Unix';
		} else if (navigator.appVersion.indexOf('Linux') !== -1) {
			Aloha.OSName = 'Linux';
		}

		registerEvents();
		Aloha.settings.base = Aloha.getAlohaUrl();
		Aloha.Log.init();

		// Initialize error handler for general javascript errors.
		if (Aloha.settings.errorhandling) {
			window.onerror = function (msg, url, line) {
				Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' +
				                       url + '\nLine Number: ' + line);
				return true;
			};
		}

		event();
		next();
	}

	/**
	 * Initialize managers
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Function to call after completing tasks.
	 */
	function initRepositoryManager(event, next) {
		Aloha.RepositoryManager.init();
		event();
		next();
	}

	/**
	 * Initialize Aloha plugins.
	 *
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Callback that will be invoked after all plugins
	 *                        have been initialized.  Whereas plugins are loaded
	 *                        synchronously, plugins may initialize
	 *                        asynchronously.
	 */
	function initPluginManager(event, next) {
		// Because if there are no loadedPlugins specified, then the default is
		// to initialized all available plugins.
		if (0 === Aloha.loadedPlugins.length) {
			var plugins = PluginManager.plugins;
			var plugin;
			for (plugin in plugins) {
				if (plugins.hasOwnProperty(plugin)) {
					Aloha.loadedPlugins.push(plugin);
				}
			}
		}

		var fired = false;

		PluginManager.init(function () {
			if (!fired) {
				event();
				fired = true;
			}
			next();
		}, Aloha.loadedPlugins);

		if (!fired) {
			event();
			fired = true;
		}
	}

	/**
	 * Begin initialize editables.
	 *
	 * @param {function} event Event to trigger after completing tasks.
	 * @param {function} next Function to call after completing tasks.
	 */
	function initEditables(event, next) {
		var i;
		for (i = 0; i < Aloha.editables.length; i++) {
			if (!Aloha.editables[i].ready) {
				Aloha.editables[i].init();
			}
		}
		event();
		next();
	}

	/**
	 * Initialization phases.
	 *
	 * These stages denote the initialization states which Aloha will go
	 * through from loading to ready.
	 *
	 * Each phase object contains the following properties:
	 *        fn : The process that is to be invoked during this phase.
	 *             Will take two functions: event() and next().
	 *     event : The event name, which if provided, will be fired after the
	 *             phase has been started (optional).
	 *  deferred : A $.Deferred() object to hold event handlers until that
	 *             initialization phase has been done (optional).
	 *
	 * @type {Array.<phase>}
	 */
	var phases = [
		// Phase 0: Waiting for initialization to begin.  This is the state at
		//          load-time.
		{
			fn: null,
			event: null,
			deferred: null
		},

		// Phase 1: DOM is ready; performing compatibility checks, registering
		//          basic events, and initializing logging.
		{
			fn: initAloha,
			event: null,
			deferred: null
		},

		// Phase 2: Initial checks have passed; Initializing repository manger.
		{
			fn: initRepositoryManager,
			event: null,
			deferred: null
		},

		// Phase 3: Repository manager is ready for use; commence
		//          initialization of configured or default plugins.
		{
			fn: initPluginManager,
			event: 'aloha-plugins-loaded',
			deferred: null
		},

		// Phase 4: Plugins have all begun their initialization process, but it
		//          is not necessary that they have completed.  Start
		//          initializing editable, along with their scaffolding UI.
		//          Editables will not be fully initialized however, until
		//          plugins have finished initialization.
		{
			fn: initEditables,
			event: null,
			deferred: null
		},

		// Phase 5: The "ready" state.  Notify the system that Aloha is fully
		//          loaded, and ready for use.
		{
			fn: null,
			event: 'aloha-ready',
			deferred: null
		}
	];


	/**
	 * Base Aloha Object
	 * @namespace Aloha
	 * @class Aloha The Aloha base object, which contains all the core functionality
	 * @singleton
	 */
	$.extend(true, Aloha, {

		/**
		 * The Aloha Editor Version we are using
		 * It should be set by us and updated for the particular branch
		 * @property
		 */
		version: '0.23.26',

		/**
		 * Array of editables that are managed by Aloha
		 * @property
		 * @type Array
		 */
		editables: [],

		/**
		 * The currently active editable is referenced here
		 * @property
		 * @type Aloha.Editable
		 */
		activeEditable: null,

		/**
		 * settings object, which will contain all Aloha settings
		 * @cfg {Object} object Aloha's settings
		 */
		settings: {},

		/**
		 * defaults object, which will contain all Aloha defaults
		 * @cfg {Object} object Aloha's settings
		 */
		defaults: {},

		/**
		 * Namespace for ui components
		 */
		ui: {},

		/**
		 * This represents the name of the users OS. Could be:
		 * 'Mac', 'Linux', 'Win', 'Unix', 'Unknown'
		 * @property
		 * @type string
		 */
		OSName: 'Unknown',

		/**
		 * A list of loaded plugin names, available after the STAGES.PLUGINS
		 * initialization phase.
		 *
		 * @type {Array.<string>}
		 * @internal
		 */
		loadedPlugins: [],

		/**
		 * Maps names of plugins (link) to the base URL (../plugins/common/link).
		 */
		_pluginBaseUrlByName: {},

		/**
		 * Start the initialization process.
		 */
		init: function () {
			Aloha.initialize(phases);
		},

		/**
		 * Returns list of loaded plugins (without Bundle name)
		 *
		 * @return array
		 */
		getLoadedPlugins: function () {
			return this.loadedPlugins;
		},

		/**
		 * Returns true if a certain plugin is loaded, false otherwise.
		 *
		 * @param {string} plugin Name of plugin
		 * @return {boolean} True if plugin with given name is load.
		 */
		isPluginLoaded: function (name) {
			var loaded = false;
			$.each(this.loadedPlugins, function (i, plugin) {
				if (name === plugin.toString()) {
					loaded = true;
					return false;
				}
			});
			return loaded;
		},

		/**
		 * Activates editable and deactivates all other Editables.
		 *
		 * @param {Editable} editable the Editable to be activated
		 */
		activateEditable: function (editable) {
			// Because editables may be removed on blur, Aloha.editables.length
			// is not cached.
			var editables = Aloha.editables;
			var i;
			for (i = 0; i < editables.length; i++) {
				if (editables[i] !== editable && editables[i].isActive) {
					editables[i].blur();
				}
			}
			Aloha.activeEditable = editable;
		},

		/**
		 * Returns the current Editable
		 * @return {Editable} returns the active Editable
		 */
		getActiveEditable: function () {
			return Aloha.activeEditable;
		},

		/**
		 * Deactivates the active Editable.
		 *
		 * TODO: Would be better named "deactivateActiveEditable".
		 */
		deactivateEditable: function () {
			if (Aloha.activeEditable) {
				Aloha.activeEditable.blur();
				Aloha.activeEditable = null;
			}
		},

		/**
		 * Gets an editable by an ID or null if no Editable with that ID
		 * registered.
		 *
		 * @param {string} id The element id to look for.
		 * @return {Aloha.Editable|null} An editable, or null if none if found
		 *                               for the given id.
		 */
		getEditableById: function (id) {
			// Because if the element is a textarea, then it's necessary to
			// route to the editable div.
			var $editable = $('#' + id);
			if ($editable.length
					&& 'textarea' === $editable[0].nodeName.toLowerCase()) {
				id = id + '-aloha';
			}
			var i;
			for (i = 0; i < Aloha.editables.length; i++) {
				if (Aloha.editables[i].getId() === id) {
					return Aloha.editables[i];
				}
			}
			return null;
		},

		/**
		 * Checks whether an object is a registered Aloha Editable.
		 * @param {jQuery} obj the jQuery object to be checked.
		 * @return {boolean}
		 */
		isEditable: function (obj) {
			var i, editablesLength;

			for (i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
				if (Aloha.editables[i].originalObj.get(0) === obj) {
					return true;
				}
			}
			return false;
		},

		/**
		 * Gets the nearest editable parent of the DOM element contained in the
		 * given jQuery object.
		 *
		 * @param {jQuery} $element jQuery unit set containing DOM element.
		 * @return {Aloha.Editable} Editable, or null if none found.
		 */
		getEditableHost: (function () {
			var getEditableOf = function (editable) {
				var i;
				for (i = 0; i < Aloha.editables.length; i++) {
					if (Aloha.editables[i].originalObj[0] === editable) {
						return Aloha.editables[i];
					}
				}
				return null;
			};

			return function ($element) {
				if (!$element || 0 === $element.length) {
					return null;
				}
				var editable = getEditableOf($element[0]);
				if (!editable) {
					$element.parents().each(function (__unused__, node) {
						editable = getEditableOf(node);
						if (editable) {
							return false;
						}
					});
				}
				return editable;
			};
		}()),

		/**
		 * Logs a message to the console.
		 *
		 * @param {string} level Level of the log
		 *                       ("error", "warn" or "info", "debug").
		 * @param {object} component Component that calls the log.
		 * @param {string} message Log message.
		 * @hide
		 */
		log: function (level, component, message) {
			if (typeof Aloha.Log !== 'undefined') {
				Aloha.Log.log(level, component, message);
			}
		},

		/**
		 * Register the given editable.
		 *
		 * @param {Editable} editable to register.
		 * @hide
		 */
		registerEditable: function (editable) {
			Aloha.editables.push(editable);
		},

		/**
		 * Unregister the given editable. It will be deactivated and removed
		 * from editables.
		 *
		 * @param {Editable} editable The editable to unregister.
		 * @hide
		 */
		unregisterEditable: function (editable) {
			var index = $.inArray(editable, Aloha.editables);
			if (index !== -1) {
				Aloha.editables.splice(index, 1);
			}
		},

		/**
		 * Check whether at least one editable was modified.
		 *
		 * @return {boolean} True when at least one editable was modified,
		 *                   false otherwise.
		 */
		isModified: function () {
			var i;
			for (i = 0; i < Aloha.editables.length; i++) {
				if (Aloha.editables[i].isModified
						&& Aloha.editables[i].isModified()) {
					return true;
				}
			}
			return false;
		},

		/**
		 * Determines the Aloha Url.
		 *
		 * @return {String} Aloha's baseUrl setting or "" if not set.
		 */
		getAlohaUrl: function (suffix) {
			return Aloha.settings.baseUrl || '';
		},

		/**
		 * Gets the plugin's url.
		 *
		 * @param {string} name The name with which the plugin was registered
		 *                      with.
		 * @return {string} The fully qualified url of this plugin.
		 */
		getPluginUrl: function (name) {
			if (!name) {
				return null;
			}
			var url = Aloha.settings._pluginBaseUrlByName[name];
			if (url) {
				// Check if url is absolute and attach base url if it is not.
				if (!url.match("^(\/|http[s]?:).*")) {
					url = Aloha.getAlohaUrl() + '/' + url;
				}
			}
			return url;
		},

		/**
		 * Disable object resizing by executing command 'enableObjectResizing',
		 * if the browser supports this.
		 */
		disableObjectResizing: function () {
			try {
				// This will disable browsers image resizing facilities in
				// order disable resize handles.
				var supported;
				try {
					supported = document.queryCommandSupported('enableObjectResizing');
				} catch (e) {
					supported = false;
					Aloha.Log.log('enableObjectResizing is not supported.');
				}
				if (supported) {
					document.execCommand('enableObjectResizing', false, false);
					Aloha.Log.log('enableObjectResizing disabled.');
				}
			} catch (e2) {
				Aloha.Log.error(e2, 'Could not disable enableObjectResizing');
				// this is just for others, who will not support disabling enableObjectResizing
			}
		},

		/**
		 * Human-readable string representation of this.
		 *
		 * @hide
		 */
		toString: function () {
			return 'Aloha';
		}
	});

	return Aloha;
});

/* console.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/console',[
	'aloha/core',
	'util/class',
	'jquery'
], function (
	Aloha,
	Class,
	jQuery
) {
	

	/**
	 * This is the aloha Log
	 * @namespace Aloha
	 * @class Log
	 * @singleton
	 */
	var AlohaConsole = Class.extend({
		/**
		 * Initialize the logging
		 * @hide
		 */
		init: function () {

			// initialize the logging settings (if not present)
			if (typeof Aloha.settings.logLevels === 'undefined' || !Aloha.settings.logLevels) {
				Aloha.settings.logLevels = {
					'error': true,
					'warn': true
				};
			}

			// initialize the logHistory settings (if not present)
			if (typeof Aloha.settings.logHistory === 'undefined' || !Aloha.settings.logHistory) {
				Aloha.settings.logHistory = {};
			}
			// set the default values for the loghistory
			if (!Aloha.settings.logHistory.maxEntries) {
				Aloha.settings.logHistory.maxEntries = 100;
			}
			if (!Aloha.settings.logHistory.highWaterMark) {
				Aloha.settings.logHistory.highWaterMark = 90;
			}
			if (!Aloha.settings.logHistory.levels) {
				Aloha.settings.logHistory.levels = {
					'error': true,
					'warn': true
				};
			}
			this.flushLogHistory();

			Aloha.trigger('aloha-logger-ready');
		},

		/**
		 * Log History as array of Message Objects. Every object has the properties
		 * 'level', 'component' and 'message'
		 * @property
		 * @type Array
		 * @hide
		 */
		logHistory: [],

		/**
		 * Flag, which is set as soon as the highWaterMark for the log history is reached.
		 * This flag is reset on every call of flushLogHistory()
		 * @hide
		 */
		highWaterMarkReached: false,

		/**
		 * Logs a message to the console
		 * @method
		 * @param {String} level Level of the log ('error', 'warn' or 'info', 'debug')
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		log: function (level, component, message) {


			// log ('Logging message');
			if (typeof component === 'undefined') {
				message = level;
			}
			if (typeof component !== 'string' && component && component.toString) {
				component = component.toString();
			}

			// log ('warn', 'Warning message');
			if (typeof message === 'undefined') {
				message = component;
				component = undefined;
			}

			if (typeof level === 'undefined' || !level) {
				level = 'log';
			}

			level = level.toLowerCase();

			if (typeof Aloha.settings.logLevels === "undefined") {
				return;
			}

			// now check whether the log level is activated
			if (!Aloha.settings.logLevels[level]) {
				return;
			}

			component = component || "Unkown Aloha Component";

			this.addToLogHistory({
				'level': level,
				'component': component,
				'message': message,
				'date': new Date()
			});

			var console = window.console;

			switch (level) {
			case 'error':
				if (window.console && console.error) {
					// FIXME:
					// Using console.error rather than throwing an error is very
					// problematic because we get not stack.
					// We ought to consider doing the following:
					// throw component + ': ' + message;
					if (!component && !message) {
						console.error("Error occured without message and component");
					} else {
						console.error(component + ': ' + message);
					}
				}
				break;
			case 'warn':
				if (window.console && console.warn) {
					console.warn(component + ': ' + message);
				}
				break;
			case 'info':
				if (window.console && console.info) {
					console.info(component + ': ' + message);
				}
				break;
			case 'debug':
				if (window.console && console.log) {
					console.log(component + ' [' + level + ']: ' + message);
				}
				break;
			default:
				if (window.console && console.log) {
					console.log(component + ' [' + level + ']: ' + message);
				}
				break;
			}
		},

		/**
		 * Log a message of log level 'error'
		 * @method
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		error: function (component, message) {
			this.log('error', component, message);
		},

		/**
		 * Log a message of log level 'warn'
		 * @method
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		warn: function (component, message) {
			this.log('warn', component, message);
		},

		/**
		 * Log a message of log level 'info'
		 * @method
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		info: function (component, message) {
			this.log('info', component, message);
		},

		/**
		 * Log a message of log level 'debug'
		 * @param {String} component Component that calls the log
		 * @param {String} message log message
		 */
		debug: function (component, message) {
			this.log('debug', component, message);
		},

		/**
		 * Methods to mark function as deprecated for developers.
		 * @param {String} component String that calls the log
		 * @param {String} message log message
		 */
		deprecated: function (component, message) {
			this.log('warn', component, message);
			// help the developer to locate the call.
			if (Aloha.settings.logLevels.deprecated) {
				throw new Error(message);
			}
		},

		/**
		 * Check whether the given log level is currently enabled
		 * @param {String} level
		 * @return true when log level is enabled, false if not
		 */
		isLogLevelEnabled: function (level) {
			return Aloha.settings && Aloha.settings.logLevels && Aloha.settings.logLevels[level];
		},

		/**
		 * Check whether error logging is enabled
		 * @return true if error logging is enabled, false if not
		 */
		isErrorEnabled: function () {
			return this.isLogLevelEnabled('error');
		},

		/**
		 * Check whether warn logging is enabled
		 * @return true if warn logging is enabled, false if not
		 */
		isWarnEnabled: function () {
			return this.isLogLevelEnabled('warn');
		},

		/**
		 * Check whether info logging is enabled
		 * @return true if info logging is enabled, false if not
		 */
		isInfoEnabled: function () {
			return this.isLogLevelEnabled('info');
		},

		/**
		 * Check whether debug logging is enabled
		 * @return true if debug logging is enabled, false if not
		 */
		isDebugEnabled: function () {
			return this.isLogLevelEnabled('debug');
		},

		/**
		 * Add the given entry to the log history. Check whether the highWaterMark has been reached, and fire an event if yes.
		 * @param {Object} entry entry to be added to the log history
		 * @hide
		 */
		addToLogHistory: function (entry) {

			if (!Aloha.settings.logHistory) {
				this.init();
			}

			// when maxEntries is set to something illegal, we do nothing (log history is disabled)
			// check whether the level is one we like to have logged
			if (Aloha.settings.logHistory.maxEntries <= 0 || !Aloha.settings.logHistory.levels[entry.level]) {

				return;
			}

			// first add the entry as last element to the history array
			this.logHistory.push(entry);

			// check whether the highWaterMark was reached, if so, fire an event
			if (!this.highWaterMarkReached) {

				if (this.logHistory.length >= Aloha.settings.logHistory.maxEntries * Aloha.settings.logHistory.highWaterMark / 100) {

					// fire the event
					Aloha.trigger('aloha-log-full');
					// set the flag (so we will not fire the event again until the logHistory is flushed)
					this.highWaterMarkReached = true;
				}
			}

			// check whether the log is full and eventually remove the oldest entries
			// @todo remove old entries when aloha-log-full event is triggered
			while (this.logHistory.length > Aloha.settings.logHistory.maxEntries) {
				this.logHistory.shift();
			}
		},

		/**
		 * Get the log history
		 * @return log history as array of objects
		 * @hide
		 */
		getLogHistory: function () {
			return this.logHistory;
		},

		/**
		 * Flush the log history. Remove all log entries and reset the flag for the highWaterMark
		 * @return void
		 * @hide
		 */
		flushLogHistory: function () {
			this.logHistory = [];
			this.highWaterMarkReached = false;
		}
	});

	/**
	 * Create the Log object
	 * @hide
	 */
	AlohaConsole = new AlohaConsole();

	// add to log namespace for compatiblility.
	Aloha.Log = Aloha.Console = AlohaConsole;
	return AlohaConsole;
});

/* range.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};

define('util/range',[
	'jquery',
	'util/dom',
	'util/class',
	'aloha/console',
	'aloha/rangy-core'
], function (jQuery, Dom, Class, console, rangy) {
	

	var GENTICS = window.GENTICS;
	var Aloha = window.Aloha;

	function selfAndParentsUntil(container, limit) {
		var parents = [],
			cur;
		if (1 === container.nodeType) {
			cur = container;
		} else {
			cur = container.parentNode;
		}
		for (;;) {
			if (!cur || cur === limit || 9 === cur.nodeType) {
				break;
			}
			if (1 === cur.nodeType) {
				parents.push(cur);
			}
			cur = cur.parentNode;
		}
		return parents;
	}

	/**
	 * Find tag with tag name 'TagName' by recursion in the RangeTree
	 * of a Range Selection
	 *
	 * @param {RangeTree []} array Range Tree class
	 * @param {String} tagName Element Tag Name
	 * @param {DOMObject []} objs arrays containing the elements whidh Tag Name
	 * is 'tagName'
	 */
	function findTagInRangeTreeRecursion(array, tagName, objs) {
		var element,
			i,
			len = array.length;
		for (i = 0; i < len; i++) {
			element = array[i];
			if (element.type !== 'none' && element.type !== 'collapsed') {
				if (element.domobj.nodeName === tagName) {
					objs.push(element.domobj);
				}
				if (element.children.length > 0) {
					findTagInRangeTreeRecursion(element.children, tagName, objs);
				}
			}
		}
	}

	/**
	 * @namespace GENTICS.Utils
	 * @class RangeObject
	 * Represents a selection range in the browser that
	 * has some advanced features like selecting the range.
	 * @param {object} param if boolean true is passed, the range will be deducted from the current browser selection.
	 * If another rangeObject is passed, it will be cloned.
	 * If nothing is passed, the rangeObject will be empty.
	 * @constructor
	 */
	GENTICS.Utils.RangeObject = Class.extend({
		_constructor: function (param) {
			// Take the values from the passed object
			if (typeof param === 'object') {
				if (typeof param.startContainer !== 'undefined') {
					this.startContainer = param.startContainer;
				}
				if (typeof param.startOffset !== 'undefined') {
					this.startOffset = param.startOffset;
				}
				if (typeof param.endContainer !== 'undefined') {
					this.endContainer = param.endContainer;
				}
				if (typeof param.endOffset !== 'undefined') {
					this.endOffset = param.endOffset;
				}
			} else if (param === true) {
				this.initializeFromUserSelection();
			}
		},

		/**
		 * DOM object of the start container of the selection.
		 * This is always has to be a DOM text node.
		 * @property startContainer
		 * @type {DOMObject}
		 */
		startContainer: undefined,

		/**
		 * Offset of the selection in the start container
		 * @property startOffset
		 * @type {Integer}
		 */
		startOffset: undefined,

		/**
		 * DOM object of the end container of the selection.
		 * This is always has to be a DOM text node.
		 * @property endContainer
		 * @type {DOMObject}
		 */
		endContainer: undefined,

		/**
		 * Offset of the selection in the end container
		 * @property endOffset
		 * @type {Integer}
		 */
		endOffset: undefined,

		/**
		 * Delete all contents selected by the current range
		 * @param rangeTree a GENTICS.Utils.RangeTree object may be provided to start from. This parameter is optional
		 */
		deleteContents: function () {

			Dom.removeRange(this);

		},

		/**
		 * Output some log
		 * TODO: move this to Aloha.Log
		 * @param message log message to output
		 * @return void
		 * @deprecated
		 * @hide
		 */
		log: function (message) {
			console.deprecated('Utils.RangeObject', 'log() is deprecated. use ' + 'console.log() from module "aloha/console" instead: ' + message);
		},

		/**
		 * Method to test if a range object is collapsed.
		 * A range is considered collapsed if either no endContainer exists or the endContainer/Offset equal startContainer/Offset
		 * @return {boolean} true if collapsed, false otherwise
		 * @method
		 */
		isCollapsed: function () {
			return (!this.endContainer || (this.startContainer === this.endContainer && this.startOffset === this.endOffset));
		},

		/**
		 * Method to (re-)calculate the common ancestor container and to get it.
		 * The common ancestor container is the DOM Object which encloses the
		 * whole range and is nearest to the start and end container objects.
		 * @return {DOMObject} get the common ancestor container
		 * @method
		 */
		getCommonAncestorContainer: function () {
			if (this.commonAncestorContainer) {
				// sometimes it's cached (or was set)
				return this.commonAncestorContainer;
			}
			// if it's not cached, calculate and then cache it
			this.updateCommonAncestorContainer();

			// now return it anyway
			return this.commonAncestorContainer;
		},

		/**
		 * Get the parent elements of the startContainer/endContainer up to the given limit. When the startContainer/endContainer
		 * is no text element, but a node, the node itself is returned as first element.
		 * @param {jQuery} limit limit object (default: body)
		 * @param {boolean} fromStart true to fetch the parents from the startContainer, false for the endContainer
		 * @return {jQuery} parent elements of the startContainer/endContainer as jQuery objects
		 * @method
		 */
		getContainerParents: function (limit, fromEnd) {
			// TODO cache the calculated parents
			var container = fromEnd ? this.endContainer : this.startContainer;
			if (!container) {
				return false;
			}
			return jQuery(selfAndParentsUntil(container, limit ? limit[0] : null));
		},

		/**
		 * Get the parent elements of the startContainer up to the given limit. When the startContainer
		 * is no text element, but a node, the node itself is returned as first element.
		 * @param {jQuery} limit limit object (default: body)
		 * @return {jQuery} parent elements of the startContainer as jQuery objects
		 * @method
		 */
		getStartContainerParents: function (limit) {
			return this.getContainerParents(limit, false);
		},

		/**
		 * Get the parent elements of the endContainer up to the given limit. When the endContainer is
		 * no text element, but a node, the node itself is returned as first element.
		 * @param {jQuery} limit limit object (default: body)
		 * @return {jQuery} parent elements of the endContainer as jQuery objects
		 * @method
		 */
		getEndContainerParents: function (limit) {
			return this.getContainerParents(limit, true);
		},

		/**
		 * TODO: the commonAncestorContainer is not calculated correctly, if either the start or
		 * the endContainer would be the cac itself (e.g. when the startContainer is a textNode
		 * and the endContainer is the startContainer's parent <p>). in this case the cac will be set
		 * to the parent div
		 * Method to update a range object internally
		 * @param commonAncestorContainer (DOM Object); optional Parameter; if set, the parameter
		 * will be used instead of the automatically calculated CAC
		 * @return void
		 * @hide
		 */
		updateCommonAncestorContainer: function (commonAncestorContainer) {
			// if no parameter was passed, calculate it
			if (!commonAncestorContainer) {
				// this will be needed either right now for finding the CAC or later for the crossing index
				var parentsStartContainer = this.getStartContainerParents(),
					parentsEndContainer = this.getEndContainerParents(),
					i;

				// find the crossing between startContainer and endContainer parents (=commonAncestorContainer)
				if (!(parentsStartContainer.length > 0 && parentsEndContainer.length > 0)) {
					console.warn('aloha/range', 'could not find commonAncestorContainer');
					return false;
				}

				for (i = 0; i < parentsStartContainer.length; i++) {
					if (parentsEndContainer.index(parentsStartContainer[i]) != -1) {
						this.commonAncestorContainer = parentsStartContainer[i];
						break;
					}
				}
			} else {
				this.commonAncestorContainer = commonAncestorContainer;
			}

			// if everything went well, return true :-)
			console.debug(commonAncestorContainer ? 'commonAncestorContainer was set successfully' : 'commonAncestorContainer was calculated successfully');
			return true;
		},

		/**
		 * Helper function for selection in IE. Creates a collapsed text range at the given position
		 * @param container container
		 * @param offset offset
		 * @return collapsed text range at that position
		 * @hide
		 */
		getCollapsedIERange: function (container, offset) {
			// create a text range
			var ieRange = document.body.createTextRange(),
				tmpRange,
			    right,
			    parent,
			    left;

			// search to the left for the next element
			left = this.searchElementToLeft(container, offset);
			if (left.element) {
				// found an element, set the start to the end of that element
				tmpRange = document.body.createTextRange();
				tmpRange.moveToElementText(left.element);
				ieRange.setEndPoint('StartToEnd', tmpRange);

				// and correct the start
				if (left.characters !== 0) {
					ieRange.moveStart('character', left.characters);
				} else {
					// this is a hack, when we are at the start of a text node, move the range anyway
					ieRange.moveStart('character', 1);
					ieRange.moveStart('character', -1);
				}
			} else {
				// found nothing to the left, so search right
				right = this.searchElementToRight(container, offset);
				// also found no element to the right, use the container itself
				parent = container.nodeType == 3 ? container.parentNode : container;
				tmpRange = document.body.createTextRange();
				tmpRange.moveToElementText(parent);
				ieRange.setEndPoint('StartToStart', tmpRange);

				// and correct the start
				if (left.characters !== 0) {
					ieRange.moveStart('character', left.characters);
				}
			}
			ieRange.collapse();

			return ieRange;
		},

		/**
		 * Sets the visible selection in the Browser based on the range object.
		 * If the selection is collapsed, this will result in a blinking cursor,
		 * otherwise in a text selection.
		 * @method
		 */
		select: function () {
			var ieRange, endRange, startRange, range, sel;

			if (typeof this.startContainer === 'undefined' || typeof this.endContainer === 'undefined') {
				console.warn('can not select an empty range');
				return false;
			}

			// create a range
			range = rangy.createRange();
			// set start and endContainer
			range.setStart(this.startContainer, this.startOffset);
			range.setEnd(this.endContainer, this.endOffset);

			// update the selection
			sel = rangy.getSelection();
			sel.setSingleRange(range);
		},

		/**
		 * Starting at the given position, search for the next element to the left and count the number of characters are in between
		 * @param container container of the startpoint
		 * @param offset offset of the startpoint in the container
		 * @return object with 'element' (null if no element found) and 'characters'
		 * @hide
		 */
		searchElementToLeft: function (container, offset) {
			var checkElement,
			    characters = 0;

			if (container.nodeType === 3) {
				// start is in a text node
				characters = offset;
				// begin check at the element to the left (if any)
				checkElement = container.previousSibling;
			} else {
				// start is between nodes, begin check at the element to the left (if any)
				if (offset > 0) {
					checkElement = container.childNodes[offset - 1];
				}
			}

			// move to the right until we find an element
			while (checkElement && checkElement.nodeType === 3) {
				characters += checkElement.data.length;
				checkElement = checkElement.previousSibling;
			}

			return {
				'element': checkElement,
				'characters': characters
			};
		},

		/**
		 * Starting at the given position, search for the next element to the right and count the number of characters that are in between
		 * @param container container of the startpoint
		 * @param offset offset of the startpoint in the container
		 * @return object with 'element' (null if no element found) and 'characters'
		 * @hide
		 */
		searchElementToRight: function (container, offset) {
			var checkElement,
			    characters = 0;

			if (container.nodeType === 3) {
				// start is in a text node
				characters = container.data.length - offset;

				// begin check at the element to the right (if any)
				checkElement = container.nextSibling;
			} else {
				// start is between nodes, begin check at the element to the right (if any)
				if (offset < container.childNodes.length) {
					checkElement = container.childNodes[offset];
				}
			}

			// move to the right until we find an element
			while (checkElement && checkElement.nodeType === 3) {
				characters += checkElement.data.length;
				checkElement = checkElement.nextSibling;
			}

			return {
				'element': checkElement,
				'characters': characters
			};
		},

		/**
		 * Method which updates the rangeObject including all extending properties like commonAncestorContainer etc...
		 * TODO: is this method needed here? or should it contain the same code as Aloha.Selection.prototype.SelectionRange.prototype.update?
		 * @return void
		 * @hide
		 */
		update: function (event) {
			console.debug('now updating rangeObject');

			this.initializeFromUserSelection(event);
			this.updateCommonAncestorContainer();
		},

		/**
		 * Initialize the current range object from the user selection of the browser.
		 * @param event which calls the method
		 * @return void
		 * @hide
		 */
		initializeFromUserSelection: function (event) {
			var selection = rangy.getSelection(),
				browserRange;

			if (!selection) {
				return false;
			}

			// check if a ragne exists
			if (!selection.rangeCount) {
				return false;
			}

			// getBrowserRange
			browserRange = selection.getRangeAt(0);
			if (!browserRange) {
				return false;
			}

			// initially set the range to what the browser tells us
			this.startContainer = browserRange.startContainer;
			this.endContainer = browserRange.endContainer;
			this.startOffset = browserRange.startOffset;
			this.endOffset = browserRange.endOffset;

			// now try to correct the range
			this.correctRange();
			return;
		},

		/**
		 * Correct the current range. The general goal of the algorithm is to have start
		 * and end of the range in text nodes if possible and the end of the range never
		 * at the beginning of an element or text node. Details of the algorithm can be
		 * found in the code comments
		 * @method
		 */
		correctRange: function () {
			var adjacentTextNode,
			    textNode,
			    checkedElement,
			    parentNode,
			    offset;

			this.clearCaches();
			if (this.isCollapsed()) {
				// collapsed ranges are treated specially

				// first check if the range is not in a text node
				if (this.startContainer.nodeType === 1) {
					if (this.startOffset > 0 && this.startContainer.childNodes[this.startOffset - 1].nodeType === 3) {
						// when the range is between nodes (container is an element
						// node) and there is a text node to the left -> move into this text
						// node (at the end)
						this.startContainer = this.startContainer.childNodes[this.startOffset - 1];
						this.startOffset = this.startContainer.data.length;
						this.endContainer = this.startContainer;
						this.endOffset = this.startOffset;
						return;
					}

					if (this.startOffset > 0 && this.startContainer.childNodes[this.startOffset - 1].nodeType === 1) {
						// search for the next text node to the left
						adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer, this.startOffset, true);
						if (adjacentTextNode) {
							this.startContainer = this.endContainer = adjacentTextNode;
							this.startOffset = this.endOffset = adjacentTextNode.data.length;
							return;
						}
						// search for the next text node to the right
						adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer, this.startOffset, false);
						if (adjacentTextNode) {
							this.startContainer = this.endContainer = adjacentTextNode;
							this.startOffset = this.endOffset = 0;
							return;
						}
					}

					if (this.startOffset < this.startContainer.childNodes.length && this.startContainer.childNodes[this.startOffset].nodeType === 3) {
						// when the range is between nodes and there is a text node
						// to the right -> move into this text node (at the start)
						this.startContainer = this.startContainer.childNodes[this.startOffset];
						this.startOffset = 0;
						this.endContainer = this.startContainer;
						this.endOffset = 0;
						return;
					}
				}

				// when the selection is in a text node at the start, look for an adjacent text node and if one found, move into that at the end
				if (this.startContainer.nodeType === 3 && this.startOffset === 0) {
					adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer.parentNode, GENTICS.Utils.Dom.getIndexInParent(this.startContainer), true);
					//only move the selection if the adjacentTextNode is inside the current editable
					//the cursor should not be outside the editable
					if (adjacentTextNode && jQuery(adjacentTextNode).closest(Aloha.activeEditable.obj).length > 0) {
						this.startContainer = this.endContainer = adjacentTextNode;
						this.startOffset = this.endOffset = adjacentTextNode.data.length;
					}
				}
			} else {
				// expanded range found

				// correct the start, but only if between nodes
				if (this.startContainer.nodeType === 1) {
					// if there is a text node to the right, move into this
					if (this.startOffset < this.startContainer.childNodes.length && this.startContainer.childNodes[this.startOffset].nodeType === 3) {
						this.startContainer = this.startContainer.childNodes[this.startOffset];
						this.startOffset = 0;
					} else if (this.startOffset < this.startContainer.childNodes.length && this.startContainer.childNodes[this.startOffset].nodeType === 1) {
						// there is an element node to the right, so recursively check all first child nodes until we find a text node
						textNode = false;
						checkedElement = this.startContainer.childNodes[this.startOffset];
						while (textNode === false && checkedElement.childNodes && checkedElement.childNodes.length > 0) {
							// go to the first child of the checked element
							checkedElement = checkedElement.childNodes[0];
							// when this element is a text node, we are done
							if (checkedElement.nodeType === 3) {
								textNode = checkedElement;
							}
						}

						// found a text node, so move into it
						if (textNode !== false) {
							this.startContainer = textNode;
							this.startOffset = 0;
						}
					}
				}

				// check whether the start is inside a text node at the end
				if (this.startContainer.nodeType === 3 && this.startOffset === this.startContainer.data.length) {
					// check whether there is an adjacent text node to the right and if
					// yes, move into it
					adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(this.startContainer.parentNode, GENTICS.Utils.Dom.getIndexInParent(this.startContainer) + 1, false);
					if (adjacentTextNode) {
						this.startContainer = adjacentTextNode;
						this.startOffset = 0;
					}
				}

				// now correct the end
				if (this.endContainer.nodeType === 3 && this.endOffset === 0) {
					// we are in a text node at the start
					if (this.endContainer.previousSibling && this.endContainer.previousSibling.nodeType === 3) {
						// found a text node to the left -> move into it (at the end)
						this.endContainer = this.endContainer.previousSibling;
						this.endOffset = this.endContainer.data.length;
					} else if (this.endContainer.previousSibling && this.endContainer.previousSibling.nodeType === 1 && this.endContainer.parentNode) {
						// found an element node to the left -> move in between
						parentNode = this.endContainer.parentNode;
						for (offset = 0; offset < parentNode.childNodes.length; ++offset) {
							if (parentNode.childNodes[offset] == this.endContainer) {
								this.endOffset = offset;
								break;
							}
						}
						this.endContainer = parentNode;
					}
				}

				if (this.endContainer.nodeType == 1 && this.endOffset === 0) {
					// we are in an element node at the start, possibly move to the previous sibling at the end
					if (this.endContainer.previousSibling) {
						if (this.endContainer.previousSibling.nodeType === 3) {
							// previous sibling is a text node, move end into here (at the end)
							this.endContainer = this.endContainer.previousSibling;
							this.endOffset = this.endContainer.data.length;
						} else if (this.endContainer.previousSibling.nodeType === 1 && this.endContainer.previousSibling.childNodes && this.endContainer.previousSibling.childNodes.length > 0) {
							// previous sibling is another element node with children,
							// move end into here (at the end)
							this.endContainer = this.endContainer.previousSibling;
							this.endOffset = this.endContainer.childNodes.length;
						}
					}
				}

				// correct the end, but only if between nodes
				if (this.endContainer.nodeType == 1) {
					// if there is a text node to the left, move into this
					if (this.endOffset > 0 && this.endContainer.childNodes[this.endOffset - 1].nodeType === 3) {
						this.endContainer = this.endContainer.childNodes[this.endOffset - 1];
						this.endOffset = this.endContainer.data.length;
					} else if (this.endOffset > 0 && this.endContainer.childNodes[this.endOffset - 1].nodeType === 1) {
						// there is an element node to the left, so recursively check all last child nodes until we find a text node
						textNode = false;
						checkedElement = this.endContainer.childNodes[this.endOffset - 1];
						while (textNode === false && checkedElement.childNodes && checkedElement.childNodes.length > 0) {
							// go to the last child of the checked element
							checkedElement = checkedElement.childNodes[checkedElement.childNodes.length - 1];
							// when this element is a text node, we are done
							if (checkedElement.nodeType === 3) {
								textNode = checkedElement;
							}
						}

						// found a text node, so move into it
						if (textNode !== false) {
							this.endContainer = textNode;
							this.endOffset = this.endContainer.data.length;
						}
					}
				}
			}
		},

		/**
		 * Clear the caches for this range. This method must be called when the range itself (start-/endContainer or start-/endOffset) is modified.
		 * @method
		 */
		clearCaches: function () {
			this.commonAncestorContainer = undefined;
		},

		/**
		 * Get the range tree of this range.
		 * The range tree will be cached for every root object. When the range itself is modified, the cache should be cleared by calling GENTICS.Utils.RangeObject.clearCaches
		 * @param {DOMObject} root root object of the range tree, if non given, the common ancestor container of the start and end containers will be used
		 * @return {RangeTree} array of RangeTree object for the given root object
		 * @method
		 */
		getRangeTree: function (root) {
			// TODO cache rangeTrees
			if (typeof root === 'undefined') {
				root = this.getCommonAncestorContainer();
			}

			this.inselection = false;
			return this.recursiveGetRangeTree(root);
		},

		/**
		 * Recursive inner function for generating the range tree.
		 * @param currentObject current DOM object for which the range tree shall be generated
		 * @return array of Tree objects for the children of the current DOM object
		 * @hide
		 */
		recursiveGetRangeTree: function (currentObject) {
			// get all direct children of the given object
			var jQueryCurrentObject = jQuery(currentObject),
				childCount = 0,
				that = this,
				currentElements = [];

			jQueryCurrentObject.contents().each(function (index) {
				var type = 'none',
					startOffset = false,
					endOffset = false,
					collapsedFound = false,
					noneFound = false,
					partialFound = false,
					fullFound = false,
					i;

				// check for collapsed selections between nodes
				if (that.isCollapsed() && currentObject === that.startContainer && that.startOffset === index) {
					// insert an extra rangetree object for the collapsed range here
					currentElements[childCount] = new GENTICS.Utils.RangeTree();
					currentElements[childCount].type = 'collapsed';
					currentElements[childCount].domobj = undefined;
					that.inselection = false;
					collapsedFound = true;
					childCount++;
				}

				if (!that.inselection && !collapsedFound) {
					// the start of the selection was not yet found, so look for it now
					// check whether the start of the selection is found here

					// check is dependent on the node type
					switch (this.nodeType) {
					case 3:
						// text node
						if (this === that.startContainer) {
							// the selection starts here
							that.inselection = true;

							// when the startoffset is > 0, the selection type is only partial
							type = that.startOffset > 0 ? 'partial' : 'full';
							startOffset = that.startOffset;
							endOffset = this.length;
						}
						break;
					case 1:
						// element node
						if (this === that.startContainer && that.startOffset === 0) {
							// the selection starts here
							that.inselection = true;
							type = 'full';
						}
						if (currentObject === that.startContainer && that.startOffset === index) {
							// the selection starts here
							that.inselection = true;
							type = 'full';
						}
						break;
					}
				}

				if (that.inselection && !collapsedFound) {
					if (type == 'none') {
						type = 'full';
					}
					// we already found the start of the selection, so look for the end of the selection now
					// check whether the end of the selection is found here

					switch (this.nodeType) {
					case 3:
						// text node
						if (this === that.endContainer) {
							// the selection ends here
							that.inselection = false;

							// check for partial selection here
							if (that.endOffset < this.length) {
								type = 'partial';
							}
							if (startOffset === false) {
								startOffset = 0;
							}
							endOffset = that.endOffset;
						}
						break;
					case 1:
						// element node
						if (this === that.endContainer && that.endOffset === 0) {
							that.inselection = false;
						}
						break;
					}
					if (currentObject === that.endContainer && that.endOffset <= index) {
						that.inselection = false;
						type = 'none';
					}
				}

				// create the current selection tree entry
				currentElements[childCount] = new GENTICS.Utils.RangeTree();
				currentElements[childCount].domobj = this;
				currentElements[childCount].type = type;
				if (type == 'partial') {
					currentElements[childCount].startOffset = startOffset;
					currentElements[childCount].endOffset = endOffset;
				}

				// now do the recursion step into the current object
				currentElements[childCount].children = that.recursiveGetRangeTree(this);

				// check whether a selection was found within the children
				if (currentElements[childCount].children.length > 0) {
					for (i = 0; i < currentElements[childCount].children.length; ++i) {
						switch (currentElements[childCount].children[i].type) {
						case 'none':
							noneFound = true;
							break;
						case 'full':
							fullFound = true;
							break;
						case 'partial':
							partialFound = true;
							break;
						}
					}

					if (partialFound || (fullFound && noneFound)) {
						// found at least one 'partial' DOM object in the children, or both 'full' and 'none', so this element is also 'partial' contained
						currentElements[childCount].type = 'partial';
					} else if (fullFound && !partialFound && !noneFound) {
						// only found 'full' contained children, so this element is also 'full' contained
						currentElements[childCount].type = 'full';
					}
				}

				childCount++;
			});

			// extra check for collapsed selections at the end of the current element
			if (this.isCollapsed() && currentObject === this.startContainer && this.startOffset == currentObject.childNodes.length) {
				currentElements[childCount] = new GENTICS.Utils.RangeTree();
				currentElements[childCount].type = 'collapsed';
				currentElements[childCount].domobj = undefined;
			}

			return currentElements;
		},

		/**
		 * Find certain the first occurrence of some markup within the parents of either the start or the end of this range.
		 * The markup can be identified by means of a given comparator function. The function will be passed every parent (up to the eventually given limit object, which itself is not considered) to the comparator function as this.
		 * When the comparator function returns boolean true, the markup found and finally returned from this function as dom object.<br/>
		 * Example for finding an anchor tag at the start of the range up to the active editable object:<br/>
		 * <pre>
		 * range.findMarkup(
		 *   function() {
		 *     return this.nodeName.toLowerCase() == 'a';
		 *   },
		 *   jQuery(Aloha.activeEditable.obj)
		 * );
		 * </pre>
		 * @param {function} comparator comparator function to find certain markup
		 * @param {jQuery} limit limit objects for limit the parents taken into consideration
		 * @param {boolean} atEnd true for searching at the end of the range, false for the start (default: false)
		 * @return {DOMObject} the found dom object or false if nothing found.
		 * @method
		 */
		findMarkup: function (comparator, limit, atEnd) {
			var container = atEnd ? this.endContainer : this.startContainer,
				parents,
				i,
				len;
			limit = limit ? limit[0] : null;
			if (!container) {
				return;
			}
			parents = selfAndParentsUntil(container, limit);
			for (i = 0, len = parents.length; i < len; i++) {
				if (comparator.apply(parents[i])) {
					return parents[i];
				}
			}
			return false;
		},

		/**
		 * Find all markups by Tag name
		 *
		 * @param {String} tagName Tag name
		 * @param {Range Object} range Selection Range
		 * @returns {DOMObject[]} All DOM objects matching the tag Name
		 */
		findAllMarkupByTagName: function (tagName, range) {
			var rangeTree = range.getRangeTree(),
				objs = [];
			tagName = tagName.toUpperCase();

			findTagInRangeTreeRecursion(rangeTree, tagName, objs);
			return objs;
		},


		/**
		 * Get the text enclosed by this range
		 * @return {String} the text of the range
		 * @method
		 */
		getText: function () {
			if (this.isCollapsed()) {
				return '';
			}
			return this.recursiveGetText(this.getRangeTree());
		},

		recursiveGetText: function (tree) {
			if (!tree) {
				return '';
			}
			var that = this,
			    text = '';
			jQuery.each(tree, function () {
				if (this.type == 'full') {
					// fully selected element/text node
					text += jQuery(this.domobj).text();
				} else if (this.type == 'partial' && this.domobj.nodeType === 3) {
					// partially selected text node
					text += jQuery(this.domobj).text().substring(this.startOffset, this.endOffset);
				} else if (this.type == 'partial' && this.domobj.nodeType === 1 && this.children) {
					// partially selected element node
					text += that.recursiveGetText(this.children);
				}
			});
			return text;
		}
	});

	/**
	 * @namespace GENTICS.Utils
	 * @class RangeTree
	 * Class definition of a RangeTree, which gives a tree view of the DOM objects included in this range
	 * Structure:
	 * <pre>
	 * +
	 * |-domobj: <reference to the DOM Object> (NOT jQuery)
	 * |-type: defines if this node is marked by user [none|partial|full|collapsed]
	 * |-children: recursive structure like this
	 * </pre>
	 */
	GENTICS.Utils.RangeTree = Class.extend({
		/**
		 * DOMObject, if the type is one of [none|partial|full], undefined if the type is [collapsed]
		 * @property domobj
		 * @type {DOMObject}
		 */
		domobj: {},

		/**
		 * type of the participation of the dom object in the range. Is one of:
		 * <pre>
		 * - none the DOMObject is outside of the range
		 * - partial the DOMObject partially in the range
		 * - full the DOMObject is completely in the range
		 * - collapsed the current RangeTree element marks the position of a collapsed range between DOM nodes
		 * </pre>
		 * @property type
		 * @type {String}
		 */
		type: null,

		/**
		 * Array of RangeTree objects which reflect the status of the child elements of the current DOMObject
		 * @property children
		 * @type {Array}
		 */
		children: []
	});

	return GENTICS.Utils.RangeObject;
});

/* functions.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/functions',[], function () {
	

	/**
	 * The identity function returns its single argument.
	 * Useful for composition when some default behaviour is needed.
	 */
	function identity(arg) {
		return arg;
	}

	function noop() {
	}

	function returnTrue() {
		return true;
	}

	function returnFalse() {
		return false;
	}

	function complement(fn) {
		return function () {
			return !fn.apply(this, arguments);
		};
	}

	return {
		identity: identity,
		noop: noop,
		returnTrue: returnTrue,
		returnFalse: returnFalse,
		complement: complement
	};
});

/* arrays.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/arrays',['util/functions'], function (Fn) {
	

	/**
	 * Implements unique() using the browser's sort().
	 *
	 * @param a
	 *        The array to sort and strip of duplicate values.
	 *        Warning: this array will be modified in-place.
	 * @param compFn
	 *        A custom comparison function that accepts two values a and
	 *        b from the given array and returns -1, 0, 1 depending on
	 *        whether a < b, a == b, a > b respectively.
	 *
	 *        If no compFn is provided, the algorithm will use the
	 *        browsers default sort behaviour and loose comparison to
	 *        detect duplicates.
	 * @return
	 *        The given array.
	 */
	function sortUnique(a, compFn) {
		var i;
		if (compFn) {
			a.sort(compFn);
			for (i = 1; i < a.length; i++) {
				if (0 === compFn(a[i], a[i - 1])) {
					a.splice(i--, 1);
				}
			}
		} else {
			a.sort();
			for (i = 1; i < a.length; i++) {
				// Use loosely typed comparsion if no compFn is given
				// to avoid sortUnique([6, "6", 6]) => [6, "6", 6]
				if (a[i] == a[i - 1]) {
					a.splice(i--, 1);
				}
			}
		}
		return a;
	}

	/**
	 * Shallow comparison of two arrays.
	 *
	 * @param a, b
	 *        The arrays to compare.
	 * @param equalFn
	 *        A custom comparison function that accepts two values a and
	 *        b from the given arrays and returns true or false for
	 *        equal and not equal respectively.
	 *
	 *        If no equalFn is provided, the algorithm will use the strict
	 *        equals operator.
	 * @return
	 *        True if all items in a and b are equal, false if not.
	 */
	function equal(a, b, equalFn) {
		var i,
			len = a.length;
		if (len !== b.length) {
			return false;
		}
		if (equalFn) {
			for (i = 0; i < len; i++) {
				if (!equalFn(a[i], b[i])) {
					return false;
				}
			}
		} else {
			for (i = 0; i < len; i++) {
				if (a[i] !== b[i]) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Emulates ECMAScript edition 5 Arrays.map
	 * See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
	 * And http://es5.github.com/#x15.4.4.19
	 * It's not exactly according to standard, but it does what one expects.
	 */
	function map(a, fn) {
		var i, len, result = [];
		for (i = 0, len = a.length; i < len; i++) {
			result.push(fn(a[i]));
		}
		return result;
	}

	function mapNative(a, fn) {
		// Call map directly on the object instead of going through
		// Array.prototype.map. This avoids the problem that we may get
		// passed an array-like object (NodeList) which may cause an
		// error if the implementation of Array.prototype.map can only
		// deal with arrays (Array.prototype.map may be native or
		// provided by a javscript framework).
		return a.map(fn);
	}

	/**
	 * Returns a new array that contains all values in the given a for
	 * which pred returns true.
	 */
	function filter(a, pred) {
		var i,
		    len,
		    value,
		    result = [];
		for (i = 0, len = a.length; i < len; i++) {
			value = a[i];
			if (pred(value)) {
				result.push(value);
			}
		}
		return result;
	}

	/**
	 * Finds a value in the given array.
	 * Strict comparison is used to find the value.
	 * Returns the index of the first occurrence of the given value in
	 * the given a, or -1 if a contains no such value.
	 */
	function indexOf(a, value) {
		var i,
		    len;
		for (i = 0, len = a.length; i < len; i++) {
			if (value === a[i]) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Reduces an array of values to a single value.
	 *
	 * For example:
	 * Arrays.reduce([2, 3, 4], 1, function (a, b) { return a + b; });
	 * returns the result of (((1 + 2) + 3) + 4)
	 *
	 * Emulates ECMAScript edition 5 Array.reduce.
	 *
	 * @param a
	 *        An array of values.
	 * @param init
	 *        An initial value.
	 * @param fn
	 *        A function that takes two values and returns the reduction
	 *        of both.
	 */
	function reduce(a, init, fn) {
		var i,
		    len;
		for (i = 0, len = a.length; i < len; i++) {
			init = fn(init, a[i]);
		}
		return init;
	}

	/**
	 * Returns true if the given xs contains the given x.
	 */
	function contains(xs, x) {
		return -1 !== indexOf(xs, x);
	}

	/**
	 * Applies the given value to the given function unless the value is
	 * null, in which case just returns null.
	 *
	 * This is a utility function to be used with reduce().
	 */
	function applyNotNull(value, fn) {
		return value == null ? null : fn(value);
	}

	/**
	 * For each item in xs, call cb(item, index, xs).
	 *
	 * Emulates ECMAScript edition 5 Array.forEach.
	 */
	function forEach(xs, cb) {
		var i,
		    len;
		for (i = 0, len = xs.length; i < len; i++) {
			cb(xs[i], i, xs);
		}
	}

	/**
	 * Returns true if the given predicate function returns true for at
	 * least one item.
	 *
	 * Emulates ECMAScript edition 5 Array.some.
	 */
	function some(xs, pred) {
		var i,
		    len;
		for (i = 0, len = xs.length; i < len; i++) {
			if (pred(xs[i])) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns true if the given predicate function returns true for all
	 * items in xs.
	 *
	 * Emulates ECMAScript edition 5 Array.every.
	 */
	function every(xs, pred) {
		return !some(xs, Fn.complement(pred));
	}

	/**
	 * Returns all items in xs that are also contained in zs.
	 */
	function intersect(xs, zs) {
		return filter(xs, function (x) {
			return contains(zs, x);
		});
	}

	/**
	 * Returns all items in xs that are not contained in zs.
	 */
	function subtract(xs, zs) {
		return filter(xs, function (x) {
			return !contains(zs, x);
		});
	}

	/**
	 * Returns the last item in xs or null.
	 */
	function last(xs) {
		return xs.length ? xs[xs.length - 1] : null;
	}

	/**
	 * Returns the second item in xs.
	 */
	function second(xs) {
		return xs[1];
	}

	return {
		filter: filter,
		indexOf: indexOf,
		reduce: reduce,
		forEach: forEach,
		some: some,
		every: every,
		map: Array.prototype.map ? mapNative : map,
		contains: contains,
		equal: equal,
		applyNotNull: applyNotNull,
		sortUnique: sortUnique,
		intersect: intersect,
		subtract: subtract,
		second: second,
		last: last
	};
});

/* strings.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/strings',['jquery'], function ($) {
	

	var spacesRx = /\s+/;

	/**
	 * Splits a string into individual words.
	 *
	 * Words are non-empty sequences of non-space characaters.
	 */
	function words(str) {
		var list = str.split(spacesRx);
		// "  x  ".split(/\s+/) => ["", "x", ""] (Chrome)
		// "".split(/\s+/) => [""] (Chrome)
		if (list.length && list[0] === "") {
			list.shift();
		}
		if (list.length && list[list.length - 1] === "") {
			list.pop();
		}
		return list;
	}

	/**
	 * Converst a dashes form into camel cased form.
	 *
	 * For example 'data-my-attr' becomes 'dataMyAttr'.
	 *
	 * @param {string} s
	 *        Should be all lowercase and should not begin with a dash
	 */
	function dashesToCamelCase(s) {
		return s.replace(/[\-]([a-z])/gi, function (all, upper) {
			return upper.toUpperCase();
		});
	}

	/**
	 * Converts a camel cased form into dashes form.
	 *
	 * For example
	 * 'dataMyAttr' becomes 'data-my-attr',
	 * 'dataAB'     becomes 'data-a-b'.
	 *
	 * @param {string} s
	 *        Should begin with a lowercase letter and should not contain dashes.
	 */
	function camelCaseToDashes(s) {
		return s.replace(/[A-Z]/g, function (match) {
			return '-' + match.toLowerCase();
		});
	}

	/**
	 * Split str along pattern, including matches in the result.
	 *
	 * Necssary because, although "xzx".split(/(z)/) results in
	 * ["x", "z", "x"] on most modern browsers, it results in
	 * ["x", "x"] on IE.
	 *
	 * @param pattern must include the g flag, otherwise will result in
	 * an endless loop.
	 */
	function splitIncl(str, pattern) {
		var result = [];
		var lastIndex = 0;
		var match;
		while (null != (match = pattern.exec(str))) {
			if (lastIndex < match.index) {
				result.push(str.substring(lastIndex, match.index));
				lastIndex = match.index;
			}
			lastIndex += match[0].length;
			result.push(match[0]);
		}
		if (lastIndex < str.length) {
			result.push(str.substring(lastIndex, str.length));
		}
		return result;
	}

	/**
	 * Returns true for the empty string, null and undefined.
	 */
	function empty(str) {
		return "" === str || null == str;
	}

	return {
		words: words,
		dashesToCamelCase: dashesToCamelCase,
		camelCaseToDashes: camelCaseToDashes,
		splitIncl: splitIncl,
		empty: empty
	};
});

/*!
 * Aloha Editor
 * Author & Copyright (c) 2012 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed under the terms of http://www.aloha-editor.com/license.html
 *
 * @overview Provides methods to broker publish/subscribe facilities.
 */
define('PubSub', [], function () {
	

	/**
	 * A hash of channel names mapped to an array of ids of subscriptions that
	 * are listening on that channel.
	 *
	 * @type {Object<String, Array.<Number>>}
	 */
	var channels = {};

	/**
	 * A hash of subscription tuples (channel, callback), mapped against unique
	 * ids assigned to each subscription.
	 * As subscriptions are removed from this object via `unsub()' this object
	 * will become a sparse array.
	 *
	 * @type {Object<Number, Object>}
	 */
	var subscriptions = {};

	/**
	 * The last used subscription id.  This values is only used and modified in
	 * `sub().'
	 *
	 * @type {number}
	 */
	var sid = 0;

	/**
	 * Returns the channel to which a subscription matching the given sid is
	 * listening on.
	 *
	 * @param {Number} sid Id of subscription.
	 * @return {Array.<Object>} sid Id of subscription.
	 */
	function getSubscriptionChannel(sid) {
		return subscriptions[sid] && channels[subscriptions[sid].channel];
	}

	/**
	 * Publishes a message `message' on the given channel.
	 * All callbacks that have sub()scribed to listen on this channel will be
	 * invoked and receive `message' as their only argument.
	 *
	 * @private
	 * @param {String} channel Name of channel to publish the message on.
	 * @param {*} message Variable to pass to all callbacks listening on the
	 *                    given channel.
	 * @return {Number} The number of subscribed callbacks that were invoked.
	 */
	function pub(channel, message) {
		if (!channels[channel]) {
			return 0;
		}

		if (!message) {
			message = {};
		} else if (typeof message !== 'object') {
			message = {
				data: message
			};
		}

		message.channel = channel;

		// Clone a immutable snapshot of the subscription ids that we can
		// safetly iterate over.
		var sids = channels[channel].slice();

		// NB: It is necessary to read the size of the `sids' array on each
		// iteration, in case the size changes (via unsubscription) between
		// iterations.
		var i;
		for (i = 0; i < sids.length; ++i) {
			subscriptions[sids[i]].callback(message);
		}

		return i;
	}

	var PubSub = {

		/**
		 * Subscribes a callback function to a channel.  Whenever this channel
		 * publishes, this function will be invoked.  The return value is an id
		 * which identifies this subscription (a channel, and callback tuple).
		 * This id can be used to unsubscribe this subscription from the given
		 * channel.
		 *
		 * @param {String} channel Name of channel to listen on.
		 * @param {Function(Object)} callback Function to be invoked when
		 *                                    messages are published on the
		 *                                    given channel.
		 * @return {Number} Positive integer representing the sid of this
		 *                  subscription, that can be used with unsub() if
		 *                  subscription succeeds.  Otherwise the return value
		 *                  is -1;
		 */
		sub: function (channel, callback) {
			if (typeof callback !== 'function') {
				return -1;
			}

			var subscriptionIds = channels[channel];

			if (!subscriptionIds) {
				subscriptionIds = channels[channel] = [];
			}

			subscriptionIds.push(++sid);
			subscriptions[sid] = {
				channel  : channel,
				callback : callback
			};

			return sid;
		},

		/**
		 * Unsubscribes callback using an sid which was returned by sub() when
		 * the callback was subscribed.  Returns true if a subscription for
		 * this sid was found and removed, otherwise returns false.
		 *
		 * @param {Number} sid Id of subscription.
		 * @return {Boolean} True if a a subscription matching this sid was
		 *                   removed.
		 */
		unsub: function (sid) {
			if (-1 === sid || !subscriptions[sid]) {
				return false;
			}

			var subscriptionIds = getSubscriptionChannel(sid);

			// assert(typeof subscriptionIds === 'array')

			delete subscriptions[sid];
			var j = subscriptionIds.length;

			while (j) {
				if (subscriptionIds[--j] === sid) {
					subscriptionIds.splice(j, 1);
					return true;
				}
			}

			return false;
		},

		/**
		 * Publishes a message `message' on all channels that can be derived
		 * from the given channel name.
		 *
		 * @param {String} channel Name of channel to publish the message on.
		 * @param {*} message Variable to pass to all callbacks listening on
		 *                    the given channel.
		 * @return {Number} The number of subscribed callbacks that were
		 *                  invoked.
		 */
		pub: function (channel, message) {
			var segments = channel.split('.');
			var i;
			var len = segments.length;
			var channelName = '';
			var tally = 0;

			for (i = 0; i < len; ++i) {
				channelName += (0 === i ? '' : '.') + segments[i];
				tally += pub(channelName, message);
			}

			return tally;
		}

	};

	return PubSub;
});

/* maps.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/maps',[], function () {
	

	/**
	 * Checks whether the given object has no own or inherited properties.
	 *
	 * @param {!Object} obj The object to check.
	 * @return {boolean} True if the object is empty. eg: isEmpty({}) == true
	 */
	function isEmpty(obj) {
		var name;
		for (name in obj) {
			if (obj.hasOwnProperty(name)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Fill the given map with the given keys mapped to the given value.
	 *
	 * @param map
	 *        The given map will have one entry added for each given key.
	 * @param keys
	 *        An array of string keys. Javascript maps can only
	 *        contain string keys, so these must be strings or
	 *        or they will be cast to string.
	 * @param value
	 *        A single value that each given key will map to.
	 * @return
	 *        The given map.
	 */
	function fillKeys(map, keys, value) {
		var i = keys.length;
		while (i--) {
			map[keys[i]] = value;
		}
		return map;
	}

	/**
	 * Fill the given map with entries from the given tuples.
	 *
	 * @param map
	 *        The given map will have one entry added for each item in
	 *        the given array.
	 * @param tuples
	 *        An array of [key, value] tuples. Javascript maps can only
	 *        contain string keys, so the keys must be strings or
	 *        or they will be cast to string.
	 * @return
	 *        The given map.
	 */
	function fillTuples(map, tuples) {
		var i = tuples.length,
			tuple;
		while (i--) {
			tuple = tuples[i];
			map[tuple[0]] = tuple[1];
		}
		return map;
	}

	/**
	 * Returns an array of the map's keys.
	 */
	function keys(map) {
		var ks = [],
			k;
		for (k in map) {
			if (map.hasOwnProperty(k)) {
				ks.push(k);
			}
		}
		return ks;
	}

	/**
	 * For each mapping, call cb(value, key, map).
	 *
	 * Emulates ECMAScript edition 5 Array.forEach.
	 *
	 * Contrary to "for (key in map)" iterates only over the
	 * "hasOwnProperty" properties of the map, which is usually what you
	 * want.
	 */
	function forEach(map, cb) {
		var key;
		for (key in map) {
			if (map.hasOwnProperty(key)) {
				cb(map[key], key, map);
			}
		}
	}

	return {
		isEmpty: isEmpty,
		fillTuples: fillTuples,
		fillKeys: fillKeys,
		keys: keys,
		forEach: forEach
	};
});

/* html.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/html',[
	'jquery',
	'util/dom',
	'util/maps',
	'util/arrays'
], function (
	jQuery,
	Dom,
	Maps,
	Arrays
) {
	

	var inlineFormattableMap = {
		'A': true,
		'B': true,
		'EM': true,
		'FONT': true,
		'I': true,
		'S': true,
		'SPAN': true,
		'STRIKE': true,
		'STRONG': true,
		'SUB': true,
		'SUP': true,
		'U': true
	};

	// NB: "block-level" is not technically defined for elements that are new in
	// HTML5.
	var BLOCKLEVEL_ELEMENTS = [
		'address',
		'article',    // HTML5
		'aside',      // HTML5
		'audio',      // HTML5
		'blockquote',
		'canvas',     // HTML5
		'dd',
		'div',
		'dl',
		'fieldset',
		'figcaption',
		'figure',
		'footer',
		'form',
		'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
		'header',
		'hgroup',
		'hr',
		'noscript',
		'ol',
		'output',
		'p',
		'pre',
		'section',   // HTML5
		'table',
		'tfoot',
		'ul',
		'video'      // HTML5
	];

	/**
	 * Void elements are elements which are not permitted to contain content.
	 * https://developer.mozilla.org/en-US/docs/Web/HTML/Element
	 *
	 * @type {Object}
	 */
	var VOID_ELEMENTS = [
		'area',
		'base',
		'br',
		'col',
		'command',
		'embed',
		'hr',
		'img',
		'input',
		'keygen',
		'link',
		'meta',
		'param',
		'source',
		'track',
		'wbr'
	];

	/**
	 * Text-level semantic and edit elements to be remove during
	 * copying or pasting.
	 *
	 * See:
	 * http://dev.w3.org/html5/spec/text-level-semantics.html#usage-summary
	 *
	 * Configurable.
	 *
	 * @type {Array.<string>}
	 */
	var TEXT_LEVEL_SEMANTIC_ELEMENTS = [
		'a',
		'abbr',
		'b',
		'bdi',
		'bdo',
		'cite',
		'code',
		'del',
		'dfn',
		'em',
		'i',
		'ins',
		'kbd',
		'mark',
		'q',
		'rp',
		'rt',
		'ruby',
		's',
		'samp',
		'small',
		'strong',
		'sub',
		'sup',
		'time',
		'u',
		'var'
	];

	/**
	 * Unicode zero width space characters:
	 * http://www.unicode.org/Public/UNIDATA/Scripts.txt
	 *
	 * @const
	 * @type {Array.<string>}
	 */
	var ZERO_WIDTH_CHARACTERS = [
		'\\u200B', // ZWSP
		'\\u200C',
		'\\u200D',
		'\\uFEFF'  // ZERO WIDTH NO-BREAK SPACE
	];

	/**
	 * Unicode White_Space characters are those that have the Unicode property
	 * "White_Space" in the Unicode PropList.txt data file.
	 *
	 * http://www.unicode.org/Public/UNIDATA/PropList.txt
	 *
	 * @const
	 * @type {Array.<string>}
	 */
	var WHITE_SPACE_CHARACTERS_UNICODES = [
		'\\u0009',
		'\\u000A',
		'\\u000B',
		'\\u000C',
		'\\u000D',
		'\\u0020',
		'\\u0085',
		'\\u00A0', // NON BREAKING SPACE ("&nbsp;")
		'\\u1680',
		'\\u180E',
		'\\u2000',
		'\\u2001',
		'\\u2002',
		'\\u2003',
		'\\u2004',
		'\\u2005',
		'\\u2006',
		'\\u2007',
		'\\u2008',
		'\\u2009',
		'\\u200A',
		'\\u2028',
		'\\u2029',
		'\\u202F',
		'\\u205F',
		'\\u3000'
	];

	var wspChars = WHITE_SPACE_CHARACTERS_UNICODES.join('');

	/**
	 * Regular expression that checks whether a string consists only of one or
	 * more white space characters.
	 *
	 * @type {RegExp}
	 */
	var WSP_CHARACTERS = new RegExp('^[' + wspChars + ']+$');
	var WSP_CHARACTERS_LEFT = new RegExp('^[' + wspChars + ']+');
	var WSP_CHARACTERS_RIGHT = new RegExp('[' + wspChars + ']+$');

	/**
	 * Regular expression that matches one or more sequences of zero width
	 * characters.
	 *
	 * @type {RegExp}
	 */
	var ZWSP_CHARACTERS = new RegExp('[' + ZERO_WIDTH_CHARACTERS.join('') + ']+');
	var ZWSP_CHARACTERS_LEFT = new RegExp('^[' + ZERO_WIDTH_CHARACTERS.join('') + ']+');
	var ZWSP_CHARACTERS_RIGHT = new RegExp('[' + ZERO_WIDTH_CHARACTERS.join('') + ']+$');

	function isWSPorZWSPText(text) {
		return WSP_CHARACTERS.test(text) || ZWSP_CHARACTERS.test(text);
	}

	function isWSPorZWSPNode(node) {
		return 3 === node.nodeType && isWSPorZWSPText(node.data);
	}

	/**
	 * Map containing lowercase and uppercase tagnames of block element as keys
	 * mapped against true.
	 *
	 * @type {object<string, boolean>}
	 */
	var blocksTagnameMap = {};
	Maps.fillKeys(blocksTagnameMap, BLOCKLEVEL_ELEMENTS, true);
	Maps.fillKeys(
		blocksTagnameMap,
		Arrays.map(BLOCKLEVEL_ELEMENTS, function (str) {
			return str.toUpperCase();
		}),
		true
	);

	function isBlock(node) {
		return blocksTagnameMap[node.nodeName];
	}

	function isIgnorableWhitespace(node) {
		return 3 === node.nodeType && !node.length;
	}

	function isInlineFormattable(node) {
		return inlineFormattableMap[node.nodeName];
	}

	/**
	 * Checks whether the given element is a block that contains a "propping"
	 * <br> element.
	 *
	 * A propping <br> is one which is inserted into block element to ensure
	 * that the otherwise empty element will be rendered visibly.
	 *
	 * @param {HTMLElement} node
	 * @return {boolean} True if node contains a propping <br>
	 */
	function isProppedBlock(node) {
		if (!blocksTagnameMap[node.nodeName]) {
			return false;
		}
		var found = false;
		var kids = node.children;
		var len = kids.length;
		var i;
		for (i = 0; i < len; i++) {
			if (!found && 'br' === kids[i].nodeName.toLowerCase()) {
				found = true;
			} else if (!isIgnorableWhitespace(kids[i])) {
				return false;
			}
		}
		return found;
	}

	function isEditingHost(node) {
		return 1 === node.nodeType && "true" === node.contentEditable;
	}

	/**
	 * Starting from the given node, and working backwards through the siblings,
	 * find the node that satisfies the given condition.
	 *
	 * @param {HTMLElement} node The node at which to start the search.
	 * @param {function(HTMLElement):boolean} condition A predicate the receives
	 *                                        one of children of `node`.
	 *
	 * @return {HTMLElement} The first node that meets the given condition.
	 */
	function findNodeRight(node, condition) {
		while (node && !condition(node)) {
			node = node.previousSibling;
		}
		return node;
	}

	function findNodeLeft(node, condition) {
		while (node && !condition(node)) {
			node = node.nextSibling;
		}
		return node;
	}

	/**
	 * Checks if the given editable is a valid container for paragraphs.
	 *
	 * @param {Aloha.Editable} editable The editable to be checked
	 *
	 * @return {boolean} False if the editable may not contain paragraphs
	 */
	function allowNestedParagraph(editable) {
		if (editable.obj[0] && Dom.allowsNesting(editable.obj[0], jQuery("<p>")[0])) {
			return true;
		}
		return false;
	}

	/**
	 * Removes a strange characters from at the beginning and end of the string
	 * 
	 * @param {String} str A string to be trimmed
	 * 
	 * @return {String}
	 */
	function trimWhitespaceCharacters(str) {
		return str
			.replace(WSP_CHARACTERS_LEFT, '')
			.replace(WSP_CHARACTERS_RIGHT, '')
			.replace(ZWSP_CHARACTERS_LEFT, '')
			.replace(ZWSP_CHARACTERS_RIGHT, '');
	}

	function isUnrenderedNode(node) {
		if (3 === node.nodeType && 0 === node.data.length) {
			return true;
		}
		if ((node === node.parentNode.lastChild)
				&& isBlock(node.parentNode)
					&& 'BR' === node.nodeName) {
			return true;
		}
		return isWSPorZWSPNode(node);
	}

	return {
		BLOCKLEVEL_ELEMENTS: BLOCKLEVEL_ELEMENTS,
		VOID_ELEMENTS: VOID_ELEMENTS,
		TEXT_LEVEL_SEMANTIC_ELEMENTS: TEXT_LEVEL_SEMANTIC_ELEMENTS,
		isBlock: isBlock,
		isIgnorableWhitespace: isIgnorableWhitespace,
		isInlineFormattable: isInlineFormattable,
		isProppedBlock: isProppedBlock,
		isEditingHost: isEditingHost,
		findNodeLeft: findNodeLeft,
		findNodeRight: findNodeRight,
		allowNestedParagraph: allowNestedParagraph,
		trimWhitespaceCharacters: trimWhitespaceCharacters,
		isWSPorZWSPNode: isWSPorZWSPNode,
		isWSPorZWSPText: isWSPorZWSPText,
		isUnrenderedNode: isUnrenderedNode
	};
});

define('aloha/engine',[
	'aloha/core',
	'aloha/ecma5shims',
	'util/maps',
	'util/html',
	'util/dom',
	'jquery'
], function (
	Aloha,
	$_,
	Maps,
	Html,
	Dom,
	jQuery
) {
	

	/**
	 *
	 * @param obj
	 * @param attr
	 * @returns {Boolean} true
	 */
	function hasAttribute(obj, attr) {
		var native_method = obj.hasAttribute;
		if (native_method) {
			return obj.hasAttribute(attr);
		}
		return (typeof obj.attributes[attr] !== 'undefined');
	}

	/**
	 * Insert the node `node` after `preceding`.
	 *
	 * @param {Element} node
	 * @param {Element} preceding
	 * @return {Element}
	 */
	function insertAfter(node, preceding) {
		var next = preceding.nextSibling,
		    parent = preceding.parentNode;
		if (next) {
			parent.insertBefore(node, next);
		} else {
			parent.appendChild(node);
		}
		return node;
	}

	/**
	 * Splits text node `node` at the given text index.
	 *
	 * Note that we cannot use splitText() because it is bugridden in IE 9.
	 *
	 * Borrowed from rangy.
	 *
	 * @param {Element} node
	 * @param {number} index
	 * @return {Element}
	 */
	function splitText(node, index) {
		var newNode = node.cloneNode(false);
		newNode.deleteData(0, index);
		node.deleteData(index, node.length - index);
		insertAfter(newNode, node);
		return newNode;
	}

	var htmlNamespace = "http://www.w3.org/1999/xhtml";

	var cssStylingFlag = false;

	// This is bad :(
	var globalRange = null;

	// Commands are stored in a dictionary where we call their actions and such
	var commands = {};

	///////////////////////////////////////////////////////////////////////////////
	////////////////////////////// Utility functions //////////////////////////////
	///////////////////////////////////////////////////////////////////////////////
	//@{

	// Opera 11 puts HTML elements in the null namespace, it seems.
	function isHtmlNamespace(ns) {
		return ns === null || !ns || ns === htmlNamespace;
	}

	// "An HTML element is an Element whose namespace is the HTML namespace."
	//
	// I allow an extra argument to more easily check whether something is a
	// particular HTML element, like isNamedHtmlElement(node, 'OL').  It accepts arrays
	// too, like isHtmlElementInArray(node, ["OL", "UL"]) to check if it's an ol or ul.
	// TODO This function was prominent during profiling. Remove it
	//      and replace with calls to isAnyHtmlElement, isNamedHtmlElement
	//      and is isMappedHtmlElement.
	function isHtmlElement_obsolete(node, tags) {
		if (typeof tags == "string") {
			tags = [tags];
		}
		if (typeof tags == "object") {
			tags = $_(tags).map(function (tag) {
				return tag.toUpperCase();
			});
		}
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI) && (typeof tags == "undefined" || $_(tags).indexOf(node.tagName) != -1);
	}

	function isAnyHtmlElement(node) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI);
	}

	// name should be uppercase
	function isNamedHtmlElement(node, name) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI)
		// This function is passed in a mix of upper and lower case names
			&& name.toUpperCase() === node.nodeName;
	}

	// TODO remove when isHtmlElementInArray is removed
	function arrayContainsInsensitive(array, str) {
		var i, len;
		str = str.toUpperCase();
		for (i = 0, len = array.length; i < len; i++) {
			if (array[i].toUpperCase() === str) {
				return true;
			}
		}
		return false;
	}
	// TODO replace calls to this function with calls to isMappedHtmlElement()
	function isHtmlElementInArray(node, array) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI)
		// This function is passed in a mix of upper and lower case names
			&& arrayContainsInsensitive(array, node.nodeName);
	}

	// map must have all-uppercase keys
	function isMappedHtmlElement(node, map) {
		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI) && map[node.nodeName];
	}

	/**
	 * Method to count the number of styles in the given style
	 */
	function getStyleLength(node) {
		var s;
		var styleLength = 0;

		if (!node) {
			return 0;
		}

		if (!node.style) {
			return 0;
		}

		// some browsers support .length on styles
		if (typeof node.style.length !== 'undefined') {
			return node.style.length;
		}

		/*jslint forin: true*/ //not sure whether node.style.hasOwnProperty is valid
		for (s in node.style) {
			if (node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
				styleLength++;
			}
		}
		/*jslint forin: false*/

		return styleLength;
	}

	function toArray(obj) {
		if (!obj) {
			return null;
		}
		var array = [],
			i,
		    l = obj.length;
		// iterate backwards ensuring that length is an UInt32
		i = l >>> 0;
		while (i--) {
			array[i] = obj[i];
		}
		return array;
	}

	function nextNodeDescendants(node) {
		while (node && !node.nextSibling) {
			node = node.parentNode;
		}
		if (!node) {
			return null;
		}
		return node.nextSibling;
	}

	function nextNode(node) {
		if (node.hasChildNodes()) {
			return node.firstChild;
		}
		return nextNodeDescendants(node);
	}

	function previousNode(node) {
		if (node.previousSibling) {
			node = node.previousSibling;
			while (node.hasChildNodes()) {
				node = node.lastChild;
			}
			return node;
		}
		if (node.parentNode && node.parentNode.nodeType == $_.Node.ELEMENT_NODE) {
			return node.parentNode;
		}
		return null;
	}

	/**
	 * Returns true if ancestor is an ancestor of descendant, false otherwise.
	 */
	function isAncestor(ancestor, descendant) {
		return ancestor && descendant && Boolean($_.compareDocumentPosition(ancestor, descendant) & $_.Node.DOCUMENT_POSITION_CONTAINED_BY);
	}

	/**
	 * Returns true if ancestor is an ancestor of or equal to descendant, false
	 * otherwise.
	 */
	function isAncestorContainer(ancestor, descendant) {
		return (ancestor || descendant) && (ancestor == descendant || isAncestor(ancestor, descendant));
	}

	/**
	 * Returns true if descendant is a descendant of ancestor, false otherwise.
	 */
	function isDescendant(descendant, ancestor) {
		return ancestor && descendant && Boolean($_.compareDocumentPosition(ancestor, descendant) & $_.Node.DOCUMENT_POSITION_CONTAINED_BY);
	}

	/**
	 * Returns true if node1 is before node2 in tree order, false otherwise.
	 */
	function isBefore(node1, node2) {
		return Boolean($_.compareDocumentPosition(node1, node2) & $_.Node.DOCUMENT_POSITION_FOLLOWING);
	}

	/**
	 * Returns true if node1 is after node2 in tree order, false otherwise.
	 */
	function isAfter(node1, node2) {
		return Boolean($_.compareDocumentPosition(node1, node2) & $_.Node.DOCUMENT_POSITION_PRECEDING);
	}

	function getAncestors(node) {
		var ancestors = [];
		while (node.parentNode) {
			ancestors.unshift(node.parentNode);
			node = node.parentNode;
		}
		return ancestors;
	}

	function getDescendants(node) {
		var descendants = [];
		var stop = nextNodeDescendants(node);
		while (null != (node = nextNode(node)) && node != stop) {
			descendants.push(node);
		}
		return descendants;
	}

	function convertProperty(property) {
		// Special-case for now
		var map = {
			"fontFamily": "font-family",
			"fontSize": "font-size",
			"fontStyle": "font-style",
			"fontWeight": "font-weight",
			"textDecoration": "text-decoration"
		};
		if (typeof map[property] != "undefined") {
			return map[property];
		}

		return property;
	}

	// Return the <font size=X> value for the given CSS size, or undefined if there
	// is none.
	function cssSizeToLegacy(cssVal) {
		return {
			"xx-small": 1,
			"small": 2,
			"medium": 3,
			"large": 4,
			"x-large": 5,
			"xx-large": 6,
			"xxx-large": 7
		}[cssVal];
	}

	// Return the CSS size given a legacy size.
	function legacySizeToCss(legacyVal) {
		return {
			1: "xx-small",
			2: "small",
			3: "medium",
			4: "large",
			5: "x-large",
			6: "xx-large",
			7: "xxx-large"
		}[legacyVal];
	}

	// "the directionality" from HTML.  I don't bother caring about non-HTML
	// elements.
	//
	// "The directionality of an element is either 'ltr' or 'rtl', and is
	// determined as per the first appropriate set of steps from the following
	// list:"
	function getDirectionality(element) {
		// "If the element's dir attribute is in the ltr state
		//     The directionality of the element is 'ltr'."
		if (element.dir == "ltr") {
			return "ltr";
		}

		// "If the element's dir attribute is in the rtl state
		//     The directionality of the element is 'rtl'."
		if (element.dir == "rtl") {
			return "rtl";
		}

		// "If the element's dir attribute is in the auto state
		// "If the element is a bdi element and the dir attribute is not in a
		// defined state (i.e. it is not present or has an invalid value)
		//     [lots of complicated stuff]
		//
		// Skip this, since no browser implements it anyway.

		// "If the element is a root element and the dir attribute is not in a
		// defined state (i.e. it is not present or has an invalid value)
		//     The directionality of the element is 'ltr'."
		if (!isAnyHtmlElement(element.parentNode)) {
			return "ltr";
		}

		// "If the element has a parent element and the dir attribute is not in a
		// defined state (i.e. it is not present or has an invalid value)
		//     The directionality of the element is the same as the element's
		//     parent element's directionality."
		return getDirectionality(element.parentNode);
	}

	//@}

	///////////////////////////////////////////////////////////////////////////////
	///////////////////////////// DOM Range functions /////////////////////////////
	///////////////////////////////////////////////////////////////////////////////
	//@{

	// "The length of a Node node is the following, depending on node:
	//
	// ProcessingInstruction
	// DocumentType
	//   Always 0.
	// Text
	// Comment
	//   node's length.
	// Any other node
	//   node's childNodes's length."
	function getNodeLength(node) {
		switch (node.nodeType) {
		case $_.Node.PROCESSING_INSTRUCTION_NODE:
		case $_.Node.DOCUMENT_TYPE_NODE:
			return 0;

		case $_.Node.TEXT_NODE:
		case $_.Node.COMMENT_NODE:
			return node.length;

		default:
			return node.childNodes.length;
		}
	}

	/**
	 * The position of two boundary points relative to one another, as defined by
	 * DOM Range.
	 */
	function getPosition(nodeA, offsetA, nodeB, offsetB) {
		// "If node A is the same as node B, return equal if offset A equals offset
		// B, before if offset A is less than offset B, and after if offset A is
		// greater than offset B."
		if (nodeA == nodeB) {
			if (offsetA == offsetB) {
				return "equal";
			}
			if (offsetA < offsetB) {
				return "before";
			}
			if (offsetA > offsetB) {
				return "after";
			}
		}

		var documentPosition = $_.compareDocumentPosition(nodeB, nodeA);
		// "If node A is after node B in tree order, compute the position of (node
		// B, offset B) relative to (node A, offset A). If it is before, return
		// after. If it is after, return before."
		if (documentPosition & $_.Node.DOCUMENT_POSITION_FOLLOWING) {
			var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
			if (pos == "before") {
				return "after";
			}
			if (pos == "after") {
				return "before";
			}
		}

		// "If node A is an ancestor of node B:"
		if (documentPosition & $_.Node.DOCUMENT_POSITION_CONTAINS) {
			// "Let child equal node B."
			var child = nodeB;

			// "While child is not a child of node A, set child to its parent."
			while (child.parentNode != nodeA) {
				child = child.parentNode;
			}

			// "If the index of child is less than offset A, return after."
			if (Dom.getIndexInParent(child) < offsetA) {
				return "after";
			}
		}

		// "Return before."
		return "before";
	}

	/**
	 * Returns the furthest ancestor of a Node as defined by DOM Range.
	 */
	function getFurthestAncestor(node) {
		var root = node;
		while (root.parentNode != null) {
			root = root.parentNode;
		}
		return root;
	}

	/**
	 * "contained" as defined by DOM Range: "A Node node is contained in a range
	 * range if node's furthest ancestor is the same as range's root, and (node, 0)
	 * is after range's start, and (node, length of node) is before range's end."
	 */
	function isContained(node, range) {
		var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
		if (pos1 !== "after") {
			return false;
		}
		var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
		if (pos2 !== "before") {
			return false;
		}
		return getFurthestAncestor(node) == getFurthestAncestor(range.startContainer);
	}

	/**
	 * Return all nodes contained in range that the provided function returns true
	 * for, omitting any with an ancestor already being returned.
	 */
	function getContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		if (node.hasChildNodes() && range.startOffset < node.childNodes.length) {
			// A child is contained
			node = node.childNodes[range.startOffset];
		} else if (range.startOffset == getNodeLength(node)) {
			// No descendant can be contained
			node = nextNodeDescendants(node);
		} else {
			// No children; this node at least can't be contained
			node = nextNode(node);
		}

		var stop = range.endContainer;
		if (stop.hasChildNodes() && range.endOffset < stop.childNodes.length) {
			// The node after the last contained node is a child
			stop = stop.childNodes[range.endOffset];
		} else {
			// This node and/or some of its children might be contained
			stop = nextNodeDescendants(stop);
		}

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isContained(node, range) && condition(node)) {
				nodeList.push(node);
				node = nextNodeDescendants(node);
				continue;
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	/**
	 * As above, but includes nodes with an ancestor that's already been returned.
	 */
	function getAllContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		if (node.hasChildNodes() && range.startOffset < node.childNodes.length) {
			// A child is contained
			node = node.childNodes[range.startOffset];
		} else if (range.startOffset == getNodeLength(node)) {
			// No descendant can be contained
			node = nextNodeDescendants(node);
		} else {
			// No children; this node at least can't be contained
			node = nextNode(node);
		}

		var stop = range.endContainer;
		if (stop.hasChildNodes() && range.endOffset < stop.childNodes.length) {
			// The node after the last contained node is a child
			stop = stop.childNodes[range.endOffset];
		} else {
			// This node and/or some of its children might be contained
			stop = nextNodeDescendants(stop);
		}

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isContained(node, range) && condition(node)) {
				nodeList.push(node);
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	// Returns either null, or something of the form rgb(x, y, z), or something of
	// the form rgb(x, y, z, w) with w != 0.
	function normalizeColor(color) {
		if (color.toLowerCase() == "currentcolor") {
			return null;
		}

		var outerSpan = document.createElement("span");
		document.body.appendChild(outerSpan);
		outerSpan.style.color = "black";

		var innerSpan = document.createElement("span");
		outerSpan.appendChild(innerSpan);
		innerSpan.style.color = color;
		color = $_.getComputedStyle(innerSpan).color;

		if (color == "rgb(0, 0, 0)") {
			// Maybe it's really black, maybe it's invalid.
			outerSpan.color = "white";
			color = $_.getComputedStyle(innerSpan).color;
			if (color != "rgb(0, 0, 0)") {
				return null;
			}
		}

		document.body.removeChild(outerSpan);

		// I rely on the fact that browsers generally provide consistent syntax for
		// getComputedStyle(), although it's not standardized.  There are only two
		// exceptions I found:
		if (/^rgba\([0-9]+, [0-9]+, [0-9]+, 1\)$/.test(color)) {
			// IE10PP2 seems to do this sometimes.
			return color.replace("rgba", "rgb").replace(", 1)", ")");
		}
		if (color == "transparent") {
			// IE10PP2, Firefox 7.0a2, and Opera 11.50 all return "transparent" if
			// the specified value is "transparent".
			return "rgba(0, 0, 0, 0)";
		}
		return color;
	}

	// Returns either null, or something of the form #xxxxxx, or the color itself
	// if it's a valid keyword.
	function parseSimpleColor(color) {
		color = color.toLowerCase();
		if ($_(["aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green", "greenyellow", "grey", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"]).indexOf(color) != -1) {
			return color;
		}

		color = normalizeColor(color);
		var matches = /^rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)$/.exec(color);
		if (matches) {
			return "#" + parseInt(matches[1], 10).toString(16).replace(/^.$/, "0$&") + parseInt(matches[2], 10).toString(16).replace(/^.$/, "0$&") + parseInt(matches[3], 10).toString(16).replace(/^.$/, "0$&");
		} else if (/^#[abcdef0123456789]+$/i.exec(color)) {
			// return hexadecimal color values (as returned by IE 7/8)
			return color;
		}
		return null;
	}

	//@}

	//////////////////////////////////////////////////////////////////////////////
	/////////////////////////// Edit command functions ///////////////////////////
	//////////////////////////////////////////////////////////////////////////////

	/////////////////////////////////////////////////
	///// Methods of the HTMLDocument interface /////
	/////////////////////////////////////////////////
	//@{

	var getStateOverride,
	    setStateOverride,
	    resetOverrides,
	    unsetStateOverride,
	    getValueOverride,
	    setValueOverride,
	    unsetValueOverride;

	var executionStackDepth = 0;

	// Helper function for fontSize's action plus queryOutputHelper.  It's just the
	// middle of fontSize's action, ripped out into its own function.
	function normalizeFontSize(value) {
		// "Strip leading and trailing whitespace from value."
		//
		// Cheap hack, not following the actual algorithm.
		value = $_(value).trim();

		// "If value is a valid floating point number, or would be a valid
		// floating point number if a single leading "+" character were
		// stripped:"
		if (/^[\-+]?[0-9]+(\.[0-9]+)?([eE][\-+]?[0-9]+)?$/.test(value)) {
			var mode;

			// "If the first character of value is "+", delete the character
			// and let mode be "relative-plus"."
			if (value[0] == "+") {
				value = value.slice(1);
				mode = "relative-plus";
				// "Otherwise, if the first character of value is "-", delete the
				// character and let mode be "relative-minus"."
			} else if (value[0] == "-") {
				value = value.slice(1);
				mode = "relative-minus";
				// "Otherwise, let mode be "absolute"."
			} else {
				mode = "absolute";
			}

			// "Apply the rules for parsing non-negative integers to value, and
			// let number be the result."
			//
			// Another cheap hack.
			var num = parseInt(value, 10);

			// "If mode is "relative-plus", add three to number."
			if (mode == "relative-plus") {
				num += 3;
			}

			// "If mode is "relative-minus", negate number, then add three to
			// it."
			if (mode == "relative-minus") {
				num = 3 - num;
			}

			// "If number is less than one, let number equal 1."
			if (num < 1) {
				num = 1;
			}

			// "If number is greater than seven, let number equal 7."
			if (num > 7) {
				num = 7;
			}

			// "Set value to the string here corresponding to number:" [table
			// omitted]
			value = {
				1: "xx-small",
				2: "small",
				3: "medium",
				4: "large",
				5: "x-large",
				6: "xx-large",
				7: "xxx-large"
			}[num];
		}

		return value;
	}

	function getLegacyFontSize(size) {
		// For convenience in other places in my code, I handle all sizes, not just
		// pixel sizes as the spec says.  This means pixel sizes have to be passed
		// in suffixed with "px", not as plain numbers.
		size = normalizeFontSize(size);

		if (jQuery.inArray(size, ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) == -1 && !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc|px)$/.test(size)) {
			// There is no sensible legacy size for things like "2em".
			return null;
		}

		var font = document.createElement("font");
		document.body.appendChild(font);
		if (size == "xxx-large") {
			font.size = 7;
		} else {
			font.style.fontSize = size;
		}
		var pixelSize = parseInt($_.getComputedStyle(font).fontSize, 10);
		document.body.removeChild(font);

		// "Let returned size be 1."
		var returnedSize = 1;

		// "While returned size is less than 7:"
		while (returnedSize < 7) {
			// "Let lower bound be the resolved value of "font-size" in pixels
			// of a font element whose size attribute is set to returned size."
			font = document.createElement("font");
			font.size = returnedSize;
			document.body.appendChild(font);
			var lowerBound = parseInt($_.getComputedStyle(font).fontSize, 10);

			// "Let upper bound be the resolved value of "font-size" in pixels
			// of a font element whose size attribute is set to one plus
			// returned size."
			font.size = 1 + returnedSize;
			var upperBound = parseInt($_.getComputedStyle(font).fontSize, 10);
			document.body.removeChild(font);

			// "Let average be the average of upper bound and lower bound."
			var average = (upperBound + lowerBound) / 2;

			// "If pixel size is less than average, return the one-element
			// string consisting of the digit returned size."
			if (pixelSize < average) {
				return String(returnedSize);
			}

			// "Add one to returned size."
			returnedSize++;
		}

		// "Return "7"."
		return "7";
	}

	// Helper function for common behavior.
	function editCommandMethod(command, prop, range, callback) {
		var ret;

		// Set up our global range magic, but only if we're the outermost function
		if (executionStackDepth === 0) {
			globalRange = range;
		}

		executionStackDepth++;
		try {
			ret = callback();
		} catch (e) {
			executionStackDepth--;
			throw e;
		}
		executionStackDepth--;
		return ret;
	}

	function myQueryCommandEnabled(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		return editCommandMethod(command, "action", range, (function (command) {
			return function () {
				// "Among commands defined in this specification, those listed in
				// Miscellaneous commands are always enabled. The other commands defined
				// here are enabled if the active range is not null, and disabled
				// otherwise."
				return jQuery.inArray(command, ["copy", "cut", "paste", "selectall", "stylewithcss", "usecss"]) != -1 || range !== null;
			};
		}(command)));
	}

	function setActiveRange(range) {
		var rangeObject = new window.GENTICS.Utils.RangeObject();

		rangeObject.startContainer = range.startContainer;
		rangeObject.startOffset = range.startOffset;
		rangeObject.endContainer = range.endContainer;
		rangeObject.endOffset = range.endOffset;

		rangeObject.select();
	}

	function myExecCommand(commandArg, showUiArg, valueArg, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		var command = commandArg.toLowerCase();
		var showUi = showUiArg;
		var value = valueArg;

		// "If only one argument was provided, let show UI be false."
		//
		// If range was passed, I can't actually detect how many args were passed
		// . . .
		if (arguments.length == 1 || (arguments.length >= 4 && typeof showUi == "undefined")) {
			showUi = false;
		}

		// "If only one or two arguments were provided, let value be the empty
		// string."
		if (arguments.length <= 2 || (arguments.length >= 4 && typeof value == "undefined")) {
			value = "";
		}

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		//
		// "If command has no action, raise an INVALID_ACCESS_ERR exception."
		return editCommandMethod(command, "action", range, (function (command, showUi, value) {
			return function () {
				// "If command is not enabled, return false."
				if (!myQueryCommandEnabled(command)) {
					return false;
				}

				// "Take the action for command, passing value to the instructions as an
				// argument."
				commands[command].action(value, range);

				// always fix the range after the command is complete
				setActiveRange(range);

				// "Return true."
				return true;
			};
		}(command, showUi, value)));
	}

	function myQueryCommandIndeterm(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		//
		// "If command has no indeterminacy, raise an INVALID_ACCESS_ERR
		// exception."
		return editCommandMethod(command, "indeterm", range, (function (command) {
			return function () {
				// "If command is not enabled, return false."
				if (!myQueryCommandEnabled(command, range)) {
					return false;
				}

				// "Return true if command is indeterminate, otherwise false."
				return commands[command].indeterm(range);
			};
		}(command)));
	}

	function myQueryCommandState(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
		//
		// "If command has no state, raise an INVALID_ACCESS_ERR exception."
		return editCommandMethod(command, "state", range, (function (command) {
			return function () {
				// "If command is not enabled, return false."
				if (!myQueryCommandEnabled(command, range)) {
					return false;
				}

				// "If the state override for command is set, return it."
				if (typeof getStateOverride(command, range) != "undefined") {
					return getStateOverride(command, range);
				}

				// "Return true if command's state is true, otherwise false."
				return commands[command].state(range);
			};
		}(command)));
	}

	// "When the queryCommandSupported(command) method on the HTMLDocument
	// interface is invoked, the user agent must return true if command is
	// supported, and false otherwise."
	function myQueryCommandSupported(command) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		return commands.hasOwnProperty(command);
	}

	function myQueryCommandValue(command, range) {
		// "All of these methods must treat their command argument ASCII
		// case-insensitively."
		command = command.toLowerCase();

		return editCommandMethod(command, "value", range, function () {
			// "If command is not supported or has no value, return the empty string."
			if (!commands.hasOwnProperty(command) || !commands[command].hasOwnProperty("value")) {
				return "";
			}

			// "If command is "fontSize" and its value override is set, convert the
			// value override to an integer number of pixels and return the legacy
			// font size for the result."
			if (command == "fontsize" && getValueOverride("fontsize", range) !== undefined) {
				return getLegacyFontSize(getValueOverride("fontsize", range));
			}

			// "If the value override for command is set, return it."
			if (typeof getValueOverride(command, range) != "undefined") {
				return getValueOverride(command, range);
			}

			// "Return command's value."
			return commands[command].value(range);
		});
	}
	//@}

	//////////////////////////////
	///// Common definitions /////
	//////////////////////////////
	//@{

	// "A prohibited paragraph child name is "address", "article", "aside",
	// "blockquote", "caption", "center", "col", "colgroup", "dd", "details",
	// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
	// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
	// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
	// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
	// "xmp"."
	var prohibitedParagraphChildNamesMap = {
		"ADDRESS": true,
		"ARTICLE": true,
		"ASIDE": true,
		"BLOCKQUOTE": true,
		"CAPTION": true,
		"CENTER": true,
		"COL": true,
		"COLGROUP": true,
		"DD": true,
		"DETAILS": true,
		"DIR": true,
		"DIV": true,
		"DL": true,
		"DT": true,
		"FIELDSET": true,
		"FIGCAPTION": true,
		"FIGURE": true,
		"FOOTER": true,
		"FORM": true,
		"H1": true,
		"H2": true,
		"H3": true,
		"H4": true,
		"H5": true,
		"H6": true,
		"HEADER": true,
		"HGROUP": true,
		"HR": true,
		"LI": true,
		"LISTING": true,
		"MENU": true,
		"NAV": true,
		"OL": true,
		"P": true,
		"PLAINTEXT": true,
		"PRE": true,
		"SECTION": true,
		"SUMMARY": true,
		"TABLE": true,
		"TBODY": true,
		"TD": true,
		"TFOOT": true,
		"TH": true,
		"THEAD": true,
		"TR": true,
		"UL": true,
		"XMP": true
	};

	// "A prohibited paragraph child is an HTML element whose local name is a
	// prohibited paragraph child name."
	function isProhibitedParagraphChild(node) {
		return isMappedHtmlElement(node, prohibitedParagraphChildNamesMap);
	}

	var nonBlockDisplayValuesMap = {
		"inline": true,
		"inline-block": true,
		"inline-table": true,
		"none": true
	};

	// "A block node is either an Element whose "display" property does not have
	// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
	// Document, or a DocumentFragment."
	function isBlockNode(node) {
		return node && ((node.nodeType == $_.Node.ELEMENT_NODE && !nonBlockDisplayValuesMap[$_.getComputedStyle(node).display]) || node.nodeType == $_.Node.DOCUMENT_NODE || node.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE);
	}

	// "An inline node is a node that is not a block node."
	function isInlineNode(node) {
		return node && !isBlockNode(node);
	}

	// "An editing host is a node that is either an Element with a contenteditable
	// attribute set to the true state, or the Element child of a Document whose
	// designMode is enabled."
	function isEditingHost(node) {
		return node && node.nodeType == $_.Node.ELEMENT_NODE && (node.contentEditable == "true" || (node.parentNode && node.parentNode.nodeType == $_.Node.DOCUMENT_NODE && node.parentNode.designMode == "on"));
	}

	// "Something is editable if it is a node which is not an editing host, does
	// not have a contenteditable attribute set to the false state, and whose
	// parent is an editing host or editable."
	function isEditable(node) {
		// This is slightly a lie, because we're excluding non-HTML elements with
		// contentEditable attributes.
		return node && !isEditingHost(node) && (node.nodeType != $_.Node.ELEMENT_NODE || node.contentEditable != "false" || jQuery(node).hasClass('aloha-table-wrapper')) && (isEditingHost(node.parentNode) || isEditable(node.parentNode));
	}

	// Helper function, not defined in the spec
	function hasEditableDescendants(node) {
		var i;
		for (i = 0; i < node.childNodes.length; i++) {
			if (isEditable(node.childNodes[i]) || hasEditableDescendants(node.childNodes[i])) {
				return true;
			}
		}
		return false;
	}

	// "The editing host of node is null if node is neither editable nor an editing
	// host; node itself, if node is an editing host; or the nearest ancestor of
	// node that is an editing host, if node is editable."
	function getEditingHostOf(node) {
		if (isEditingHost(node)) {
			return node;
		}
		if (isEditable(node)) {
			var ancestor = node.parentNode;
			while (!isEditingHost(ancestor)) {
				ancestor = ancestor.parentNode;
			}
			return ancestor;
		}
		return null;
	}

	// "Two nodes are in the same editing host if the editing host of the first is
	// non-null and the same as the editing host of the second."
	function inSameEditingHost(node1, node2) {
		return getEditingHostOf(node1) && getEditingHostOf(node1) == getEditingHostOf(node2);
	}

	// "A collapsed line break is a br that begins a line box which has nothing
	// else in it, and therefore has zero height."
	function isCollapsedLineBreak(br) {
		if (!isNamedHtmlElement(br, 'br')) {
			return false;
		}

		// Add a zwsp after it and see if that changes the height of the nearest
		// non-inline parent.  Note: this is not actually reliable, because the
		// parent might have a fixed height or something.
		var ref = br.parentNode;
		while ($_.getComputedStyle(ref).display == "inline") {
			ref = ref.parentNode;
		}

		var origStyle = {
			height: ref.style.height,
			maxHeight: ref.style.maxHeight,
			minHeight: ref.style.minHeight
		};

		ref.style.height = 'auto';
		ref.style.maxHeight = 'none';
		if (!(jQuery.browser.msie && jQuery.browser.version < 8)) {
			ref.style.minHeight = '0';
		}
		var space = document.createTextNode('\u200b');
		var origHeight = ref.offsetHeight;
		if (origHeight == 0) {
			throw 'isCollapsedLineBreak: original height is zero, bug?';
		}
		br.parentNode.insertBefore(space, br.nextSibling);
		var finalHeight = ref.offsetHeight;
		space.parentNode.removeChild(space);

		ref.style.height = origStyle.height;
		ref.style.maxHeight = origStyle.maxHeight;
		if (!(jQuery.browser.msie && jQuery.browser.version < 8)) {
			ref.style.minHeight = origStyle.minHeight;
		}

		// Allow some leeway in case the zwsp didn't create a whole new line, but
		// only made an existing line slightly higher.  Firefox 6.0a2 shows this
		// behavior when the first line is bold.
		return origHeight < finalHeight - 5;
	}

	// "An extraneous line break is a br that has no visual effect, in that
	// removing it from the DOM would not change layout, except that a br that is
	// the sole child of an li is not extraneous."
	function isExtraneousLineBreak(br) {

		if (!isNamedHtmlElement(br, 'br')) {
			return false;
		}

		if (isNamedHtmlElement(br.parentNode, "li") && br.parentNode.childNodes.length == 1) {
			return false;
		}

		// Make the line break disappear and see if that changes the block's
		// height.  Yes, this is an absurd hack.  We have to reset height etc. on
		// the reference node because otherwise its height won't change if it's not
		// auto.
		var ref = br.parentNode;
		while ($_.getComputedStyle(ref).display == "inline") {
			ref = ref.parentNode;
		}

		var origStyle = {
			height: ref.style.height,
			maxHeight: ref.style.maxHeight,
			minHeight: ref.style.minHeight,
			contentEditable: ref.contentEditable
		};

		ref.style.height = 'auto';
		ref.style.maxHeight = 'none';
		ref.style.minHeight = '0';
		// IE7 would ignore display:none in contentEditable, so we temporarily set it to false
		if (jQuery.browser.msie && jQuery.browser.version <= 7) {
			ref.contentEditable = 'false';
		}

		var origHeight = ref.offsetHeight;
		if (origHeight == 0) {
			throw "isExtraneousLineBreak: original height is zero, bug?";
		}

		var origBrDisplay = br.style.display;
		br.style.display = 'none';
		var finalHeight = ref.offsetHeight;

		// Restore original styles to the touched elements.
		ref.style.height = origStyle.height;
		ref.style.maxHeight = origStyle.maxHeight;
		ref.style.minHeight = origStyle.minHeight;
		// reset contentEditable for IE7
		if (jQuery.browser.msie && jQuery.browser.version <= 7) {
			ref.contentEditable = origStyle.contentEditable;
		}
		br.style.display = origBrDisplay;

		// https://github.com/alohaeditor/Aloha-Editor/issues/516
		// look like it works in msie > 7
		/* if (jQuery.browser.msie && jQuery.browser.version < 8) {
		   br.removeAttribute("style");
		   ref.removeAttribute("style");
		   } */

		return origHeight == finalHeight;
	}

	// "A whitespace node is either a Text node whose data is the empty string; or
	// a Text node whose data consists only of one or more tabs (0x0009), line
	// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
	// parent is an Element whose resolved value for "white-space" is "normal" or
	// "nowrap"; or a Text node whose data consists only of one or more tabs
	// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
	// parent is an Element whose resolved value for "white-space" is "pre-line"."
	function isWhitespaceNode(node) {
		var nodeTypes = $_.Node;
		if (node && node.nodeType === nodeTypes.TEXT_NODE) {
			var parentNode = node.parentNode;

			var nodeData = node.data;
			if (jQuery.trim(nodeData).length === 0) {
				return true;
			} else if (parentNode && /^[\t\n\r ]+$/.test(nodeData)) {
				if (parentNode.nodeType === nodeTypes.ELEMENT_NODE) {
					if (jQuery.inArray($_.getComputedStyle(parentNode).whiteSpace, ["normal", "nowrap"]) !== -1) {
						return true;
					} else if ($_.getComputedStyle(parentNode).whiteSpace === "pre-line") {
						return true;
					}
				} else if (parentNode.nodeType === nodeTypes.DOCUMENT_FRAGMENT_NODE) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Collapse sequences of ignorable whitespace (tab (0x0009), line feed (0x000A), carriage return (0x000D), space (0x0020)) to only one space.
	 * Preserve the given range if necessary.
	 * @param node text node
	 * @param range range
	 */
	function collapseWhitespace(node, range) {
		// "If node is neither editable nor an editing host, abort these steps."
		if (!isEditable(node) && !isEditingHost(node)) {
			return;
		}

		// if the given node is not a text node, return
		if (!node || node.nodeType !== $_.Node.TEXT_NODE) {
			return;
		}

		// if the node is in a pre or pre-wrap node, return
		if (jQuery.inArray($_.getComputedStyle(node.parentNode).whiteSpace, ["pre", "pre-wrap"]) != -1) {
			return;
		}

		// if the given node does not contain sequences of at least two consecutive ignorable whitespace characters, return
		if (!/[\t\n\r ]{2,}/.test(node.data)) {
			return;
		}

		var newData = '';
		var correctStart = range.startContainer == node;
		var correctEnd = range.endContainer == node;
		var wsFound = false;
		var i;

		// iterate through the node data
		for (i = 0; i < node.data.length; ++i) {
			if (/[\t\n\r ]/.test(node.data.substr(i, 1))) {
				// found a whitespace
				if (!wsFound) {
					// this is the first whitespace in the current sequence
					// add a whitespace to the new data sequence
					newData += ' ';
					// remember that we found a whitespace
					wsFound = true;
				} else {
					// this is not the first whitespace in the sequence, so omit this character
					if (correctStart && newData.length < range.startOffset) {
						range.startOffset--;
					}
					if (correctEnd && newData.length < range.endOffset) {
						range.endOffset--;
					}
				}
			} else {
				newData += node.data.substr(i, 1);
				wsFound = false;
			}
		}

		// set the new data
		node.data = newData;
	}

	// "node is a collapsed whitespace node if the following algorithm returns
	// true:"
	function isCollapsedWhitespaceNode(node) {
		// "If node is not a whitespace node, return false."
		if (!isWhitespaceNode(node)) {
			return false;
		}

		// "If node's data is the empty string, return true."
		if (node.data == "") {
			return true;
		}

		// "Let ancestor be node's parent."
		var ancestor = node.parentNode;

		// "If ancestor is null, return true."
		if (!ancestor) {
			return true;
		}

		// "If the "display" property of some ancestor of node has resolved value
		// "none", return true."
		if ($_(getAncestors(node)).some(function (ancestor) { return ancestor.nodeType == $_.Node.ELEMENT_NODE && $_.getComputedStyle(ancestor).display == "none"; })) {
			return true;
		}

		// "While ancestor is not a block node and its parent is not null, set
		// ancestor to its parent."
		while (!isBlockNode(ancestor) && ancestor.parentNode) {
			ancestor = ancestor.parentNode;
		}

		// "Let reference be node."
		var reference = node;

		// "While reference is a descendant of ancestor:"
		while (reference != ancestor) {
			// "Let reference be the node before it in tree order."
			reference = previousNode(reference);

			// "If reference is a block node or a br, return true."
			if (isBlockNode(reference) || isNamedHtmlElement(reference, 'br')) {
				return true;
			}

			// "If reference is a Text node that is not a whitespace node, or is an
			// img, break from this loop."
			if ((reference.nodeType == $_.Node.TEXT_NODE && !isWhitespaceNode(reference)) || isNamedHtmlElement(reference, 'img')) {
				break;
			}
		}

		// "Let reference be node."
		reference = node;

		// "While reference is a descendant of ancestor:"
		var stop = nextNodeDescendants(ancestor);
		while (reference != stop) {
			// "Let reference be the node after it in tree order, or null if there
			// is no such node."
			reference = nextNode(reference);

			// "If reference is a block node or a br, return true."
			if (isBlockNode(reference) || isNamedHtmlElement(reference, 'br')) {
				return true;
			}

			// "If reference is a Text node that is not a whitespace node, or is an
			// img, break from this loop."
			if ((reference && reference.nodeType == $_.Node.TEXT_NODE && !isWhitespaceNode(reference)) || isNamedHtmlElement(reference, 'img')) {
				break;
			}
		}

		// "Return false."
		return false;
	}

	// "Something is visible if it is a node that either is a block node, or a Text
	// node that is not a collapsed whitespace node, or an img, or a br that is not
	// an extraneous line break, or any node with a visible descendant; excluding
	// any node with an ancestor container Element whose "display" property has
	// resolved value "none"."
	function isVisible(node) {
		var i;

		if (!node) {
			return false;
		}

		if ($_(getAncestors(node).concat(node))
			    .filter(function (node) { return node.nodeType == $_.Node.ELEMENT_NODE; }, true)
			    .some(function (node) { return $_.getComputedStyle(node).display == "none"; })) {
			return false;
		}

		if (isBlockNode(node) || (node.nodeType == $_.Node.TEXT_NODE && !isCollapsedWhitespaceNode(node)) || isNamedHtmlElement(node, 'img') || (isNamedHtmlElement(node, 'br') && !isExtraneousLineBreak(node))) {
			return true;
		}

		for (i = 0; i < node.childNodes.length; i++) {
			if (isVisible(node.childNodes[i])) {
				return true;
			}
		}

		return false;
	}

	// "Something is invisible if it is a node that is not visible."
	function isInvisible(node) {
		return node && !isVisible(node);
	}

	// "A collapsed block prop is either a collapsed line break that is not an
	// extraneous line break, or an Element that is an inline node and whose
	// children are all either invisible or collapsed block props and that has at
	// least one child that is a collapsed block prop."
	function isCollapsedBlockProp(node) {
		var i;

		if (isCollapsedLineBreak(node) && !isExtraneousLineBreak(node)) {
			return true;
		}

		if (!isInlineNode(node) || node.nodeType != $_.Node.ELEMENT_NODE) {
			return false;
		}

		var hasCollapsedBlockPropChild = false;
		for (i = 0; i < node.childNodes.length; i++) {
			if (!isInvisible(node.childNodes[i]) && !isCollapsedBlockProp(node.childNodes[i])) {
				return false;
			}
			if (isCollapsedBlockProp(node.childNodes[i])) {
				hasCollapsedBlockPropChild = true;
			}
		}

		return hasCollapsedBlockPropChild;
	}

	/**
	 * Checks whether the given node is a visible text node.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` is a visible text node.
	 */
	function isInvisibleTextNode(node) {
		if (node && node.nodeType !== $_.Node.TEXT_NODE) {
			return false;
		}
		var offset = 0;
		var data = node.data;
		var len = data.length;
		while (offset < len && data.charAt(offset) === '\u200b') {
			offset++;
		}
		return offset === len;
	}

	/**
	 * Complement of isInvisibleTextNode().
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` is anything but an invisible text node.
	 */
	function isNotInvisibleTextNode(node) {
		return !isInvisibleTextNode(node);
	}

	/**
	 * Checks whether the given node is a otherwise empty block-level element
	 * containing a propping <br> element.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` is a propped up block-level element.
	 */
	function isProppedBlock(node) {
		if (!Html.isBlock(node)) {
			return false;
		}
		var child = Html.findNodeRight(node.lastChild, isVisible);
		return (
			child
			&& 'br' === child.nodeName.toLowerCase()
			&& !Html.findNodeRight(child.previousSibling, isVisible)
		);
	}

	/**
	 * Checks whether the given node is a empty element, or an element that
	 * would otherwise be empty except for a propping <br>, or an element
	 * containing only invisible text nodes.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` can be considered empty.
	 */
	function isEmptyNode(node) {
		return (
			!node.hasChildNodes()
			|| isProppedBlock(node)
			|| !Html.findNodeRight(node.lastChild, isNotInvisibleTextNode)
		);
	}

	/**
	 * Check if the given node is a empty element which is the only
	 * immediate child of a editing host.
	 *
	 * @param {HTMLElement} node
	 * @return {Boolean} True if `node` can be regarded as empty and the
	 *                   only immediate child of its parent editing host.
	 */
	function isEmptyOnlyChildOfEditingHost(node) {
		return (
			node
				&& isEmptyNode(node)
					&& isEditingHost(node.parentNode)
						&& !node.previousSibling
							&& !node.nextSibling
		);
	}

	/**
	 * Remove the given node and return the position from where it was
	 * removed.
	 *
	 * @param {HTMLElement} node Element to remove from DOM
	 * @return {object} Object containing node and offset index.
	 */
	function removeNode(node) {
		var ancestor = node.parentNode;
		var offset = Dom.getIndexInParent(node);
		ancestor.removeChild(node);
		return {
			node: ancestor,
			offset: offset
		};
	}

	// Please note: This method is deprecated and will be removed.
	// Every command should use the value and range parameter.
	//
	// "The active range is the first range in the Selection given by calling
	// getSelection() on the context object, or null if there is no such range."
	//
	// We cheat and return globalRange if that's defined.  We also ensure that the
	// active range meets the requirements that selection boundary points are
	// supposed to meet, i.e., that the nodes are both Text or Element nodes that
	// descend from a Document.
	function getActiveRange() {
		var ret;
		if (globalRange) {
			ret = globalRange;
		} else if (Aloha.getSelection().rangeCount) {
			ret = Aloha.getSelection().getRangeAt(0);
		} else {
			return null;
		}
		if (jQuery.inArray(ret.startContainer.nodeType, [$_.Node.TEXT_NODE, $_.Node.ELEMENT_NODE]) == -1 || jQuery.inArray(ret.endContainer.nodeType, [$_.Node.TEXT_NODE, $_.Node.ELEMENT_NODE]) == -1 || !ret.startContainer.ownerDocument || !ret.endContainer.ownerDocument || !isDescendant(ret.startContainer, ret.startContainer.ownerDocument) || !isDescendant(ret.endContainer, ret.endContainer.ownerDocument)) {
			throw "Invalid active range; test bug?";
		}
		return ret;
	}

	// "For some commands, each HTMLDocument must have a boolean state override
	// and/or a string value override. These do not change the command's state or
	// value, but change the way some algorithms behave, as specified in those
	// algorithms' definitions. Initially, both must be unset for every command.
	// Whenever the number of ranges in the Selection changes to something
	// different, and whenever a boundary point of the range at a given index in
	// the Selection changes to something different, the state override and value
	// override must be unset for every command."
	//
	// We implement this crudely by using setters and getters.  To verify that the
	// selection hasn't changed, we copy the active range and just check the
	// endpoints match.  This isn't really correct, but it's good enough for us.
	// Unset state/value overrides are undefined.  We put everything in a function
	// so no one can access anything except via the provided functions, since
	// otherwise callers might mistakenly use outdated overrides (if the selection
	// has changed).
	(function () {
		var stateOverrides = {};
		var valueOverrides = {};
		var storedRange = null;

		resetOverrides = function (range) {
			if (!storedRange
				    || storedRange.startContainer != range.startContainer
				    || storedRange.endContainer != range.endContainer
				    || storedRange.startOffset != range.startOffset
				    || storedRange.endOffset != range.endOffset) {
				storedRange = {
					startContainer: range.startContainer,
					endContainer: range.endContainer,
					startOffset: range.startOffset,
					endOffset: range.endOffset
				};
				if (!Maps.isEmpty(stateOverrides) || !Maps.isEmpty(valueOverrides)) {
					stateOverrides = {};
					valueOverrides = {};
					return true;
				}
			}
			return false;
		};

		getStateOverride = function (command, range) {
			resetOverrides(range);
			return stateOverrides[command];
		};

		setStateOverride = function (command, newState, range) {
			resetOverrides(range);
			stateOverrides[command] = newState;
		};

		unsetStateOverride = function (command, range) {
			resetOverrides(range);
			delete stateOverrides[command];
		};

		getValueOverride = function (command, range) {
			resetOverrides(range);
			return valueOverrides[command];
		};

		// "The value override for the backColor command must be the same as the
		// value override for the hiliteColor command, such that setting one sets
		// the other to the same thing and unsetting one unsets the other."
		setValueOverride = function (command, newValue, range) {
			resetOverrides(range);
			valueOverrides[command] = newValue;
			if (command == "backcolor") {
				valueOverrides.hilitecolor = newValue;
			} else if (command == "hilitecolor") {
				valueOverrides.backcolor = newValue;
			}
		};

		unsetValueOverride = function (command, range) {
			resetOverrides(range);
			delete valueOverrides[command];
			if (command == "backcolor") {
				delete valueOverrides.hilitecolor;
			} else if (command == "hilitecolor") {
				delete valueOverrides.backcolor;
			}
		};
	}());

	//@}

	/////////////////////////////
	///// Common algorithms /////
	/////////////////////////////

	///// Assorted common algorithms /////
	//@{

	function movePreservingRanges(node, newParent, newIndex, range) {
		// For convenience, I allow newIndex to be -1 to mean "insert at the end".
		if (newIndex == -1) {
			newIndex = newParent.childNodes.length;
		}

		// "When the user agent is to move a Node to a new location, preserving
		// ranges, it must remove the Node from its original parent (if any), then
		// insert it in the new location. In doing so, however, it must ignore the
		// regular range mutation rules, and instead follow these rules:"

		// "Let node be the moved Node, old parent and old index be the old parent
		// (which may be null) and index, and new parent and new index be the new
		// parent and index."
		var oldParent = node.parentNode;
		var oldIndex = Dom.getIndexInParent(node);
		var i;

		// We only even attempt to preserve the global range object and the ranges
		// in the selection, not every range out there (the latter is probably
		// impossible).
		var ranges = [range];
		for (i = 0; i < Aloha.getSelection().rangeCount; i++) {
			ranges.push(Aloha.getSelection().getRangeAt(i));
		}
		var boundaryPoints = [];
		$_(ranges).forEach(function (range) {
			boundaryPoints.push([range.startContainer, range.startOffset]);
			boundaryPoints.push([range.endContainer, range.endOffset]);
		});

		$_(boundaryPoints).forEach(function (boundaryPoint) {
			// "If a boundary point's node is the same as or a descendant of node,
			// leave it unchanged, so it moves to the new location."
			//
			// No modifications necessary.

			// "If a boundary point's node is new parent and its offset is greater
			// than new index, add one to its offset."
			if (boundaryPoint[0] == newParent && boundaryPoint[1] > newIndex) {
				boundaryPoint[1]++;
			}

			// "If a boundary point's node is old parent and its offset is old index or
			// old index + 1, set its node to new parent and add new index − old index
			// to its offset."
			if (boundaryPoint[0] == oldParent && (boundaryPoint[1] == oldIndex || boundaryPoint[1] == oldIndex + 1)) {
				boundaryPoint[0] = newParent;
				boundaryPoint[1] += newIndex - oldIndex;
			}

			// "If a boundary point's node is old parent and its offset is greater than
			// old index + 1, subtract one from its offset."
			if (boundaryPoint[0] == oldParent && boundaryPoint[1] > oldIndex + 1) {
				boundaryPoint[1]--;
			}
		});

		// Now actually move it and preserve the ranges.
		if (newParent.childNodes.length == newIndex) {
			newParent.appendChild(node);
		} else {
			newParent.insertBefore(node, newParent.childNodes[newIndex]);
		}

		// if we're off actual node boundaries this implies that the move was
		// part of a deletion process (backspace). If that's the case we
		// attempt to fix this by restoring the range to the first index of
		// the node that has been moved
		var newRange = null;
		if (boundaryPoints[0][1] > boundaryPoints[0][0].childNodes.length && boundaryPoints[1][1] > boundaryPoints[1][0].childNodes.length) {
			range.setStart(node, 0);
			range.setEnd(node, 0);
		} else {
			range.setStart(boundaryPoints[0][0], boundaryPoints[0][1]);
			range.setEnd(boundaryPoints[1][0], boundaryPoints[1][1]);

			Aloha.getSelection().removeAllRanges();
			for (i = 1; i < ranges.length; i++) {
				newRange = Aloha.createRange();
				newRange.setStart(boundaryPoints[2 * i][0], boundaryPoints[2 * i][1]);
				newRange.setEnd(boundaryPoints[2 * i + 1][0], boundaryPoints[2 * i + 1][1]);
				Aloha.getSelection().addRange(newRange);
			}
			if (newRange) {
				range = newRange;
			}
		}
	}

	/**
	 * Copy all non empty attributes from an existing to a new element
	 *
	 * @param {dom} element The source DOM element
	 * @param {dom} newElement The new DOM element which will get the attributes of the source DOM element
	 * @return void
	 */
	function copyAttributes(element, newElement) {

		// This is an IE7 workaround. We identified three places that were connected
		// to the mysterious ie7 crash:
		// 1. Add attribute to dom element (Initialization of jquery-ui sortable)
		// 2. Access the jquery expando attribute. Just reading the name is
		//    sufficient to make the browser vulnerable for the crash (Press enter)
		// 3. On editable blur the Aloha.editables[0].getContents(); gets invoked.
		//    This invokation somehow crashes the ie7. We assume that the access of
		//    shared expando attribute updates internal references which are not
		//    correclty handled during clone();
		if (jQuery.browser.msie && jQuery.browser.version >= 7 && typeof element.attributes[jQuery.expando] !== 'undefined') {
			jQuery(element).removeAttr(jQuery.expando);
		}

		var attrs = element.attributes;
		var i;
		for (i = 0; i < attrs.length; i++) {
			var attr = attrs[i];
			// attr.specified is an IE specific check to exclude attributes that were never really set.
			if (typeof attr.specified === "undefined" || attr.specified) {
				if (typeof newElement.setAttributeNS === 'function') {
					newElement.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
				} else {
					// fixes https://github.com/alohaeditor/Aloha-Editor/issues/515
					newElement.setAttribute(attr.name, attr.value);
				}
			}
		}
	}

	function setTagName(element, newName, range) {
		// "If element is an HTML element with local name equal to new name, return
		// element."
		if (isNamedHtmlElement(element, newName)) {
			return element;
		}

		// "If element's parent is null, return element."
		if (!element.parentNode) {
			return element;
		}

		// "Let replacement element be the result of calling createElement(new
		// name) on the ownerDocument of element."
		var replacementElement = element.ownerDocument.createElement(newName);

		// "Insert replacement element into element's parent immediately before
		// element."
		element.parentNode.insertBefore(replacementElement, element);

		// "Copy all attributes of element to replacement element, in order."
		copyAttributes(element, replacementElement);

		// "While element has children, append the first child of element as the
		// last child of replacement element, preserving ranges."
		while (element.childNodes.length) {
			movePreservingRanges(element.firstChild, replacementElement, replacementElement.childNodes.length, range);
		}

		// "Remove element from its parent."
		element.parentNode.removeChild(element);

		// if the range still uses the old element, we modify it to the new one
		if (range.startContainer === element) {
			range.startContainer = replacementElement;
		}
		if (range.endContainer === element) {
			range.endContainer = replacementElement;
		}

		// "Return replacement element."
		return replacementElement;
	}

	function removeExtraneousLineBreaksBefore(node) {
		// "Let ref be the previousSibling of node."
		var ref = node.previousSibling;

		// "If ref is null, abort these steps."
		if (!ref) {
			return;
		}

		// "While ref has children, set ref to its lastChild."
		while (ref.hasChildNodes()) {
			ref = ref.lastChild;
		}

		// "While ref is invisible but not an extraneous line break, and ref does
		// not equal node's parent, set ref to the node before it in tree order."
		while (isInvisible(ref) && !isExtraneousLineBreak(ref) && ref != node.parentNode) {
			ref = previousNode(ref);
		}

		// "If ref is an editable extraneous line break, remove it from its
		// parent."
		if (isEditable(ref) && isExtraneousLineBreak(ref)) {
			ref.parentNode.removeChild(ref);
		}
	}

	function removeExtraneousLineBreaksAtTheEndOf(node) {
		// "Let ref be node."
		var ref = node;

		// "While ref has children, set ref to its lastChild."
		while (ref.hasChildNodes()) {
			ref = ref.lastChild;
		}

		// "While ref is invisible but not an extraneous line break, and ref does
		// not equal node, set ref to the node before it in tree order."
		while (isInvisible(ref) && !isExtraneousLineBreak(ref) && ref != node) {
			ref = previousNode(ref);
		}

		// "If ref is an editable extraneous line break, remove it from its
		// parent."
		if (isEditable(ref) && isExtraneousLineBreak(ref)) {
			ref.parentNode.removeChild(ref);
		}
	}

	// "To remove extraneous line breaks from a node, first remove extraneous line
	// breaks before it, then remove extraneous line breaks at the end of it."
	function removeExtraneousLineBreaksFrom(node) {
		removeExtraneousLineBreaksBefore(node);
		removeExtraneousLineBreaksAtTheEndOf(node);
	}

	//@}
	///// Wrapping a list of nodes /////
	//@{

	function wrap(nodeList, siblingCriteria, newParentInstructions, range) {
		var i;

		// "If not provided, sibling criteria returns false and new parent
		// instructions returns null."
		if (typeof siblingCriteria == "undefined") {
			siblingCriteria = function () {
				return false;
			};
		}
		if (typeof newParentInstructions == "undefined") {
			newParentInstructions = function () {
				return null;
			};
		}

		// "If node list is empty, or the first member of node list is not
		// editable, return null and abort these steps."
		if (!nodeList.length || !isEditable(nodeList[0])) {
			return null;
		}

		// "If node list's last member is an inline node that's not a br, and node
		// list's last member's nextSibling is a br, append that br to node list."
		if (isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "br") && isNamedHtmlElement(nodeList[nodeList.length - 1].nextSibling, "br")) {
			nodeList.push(nodeList[nodeList.length - 1].nextSibling);
		}

		// "If the previousSibling of the first member of node list is editable and
		// running sibling criteria on it returns true, let new parent be the
		// previousSibling of the first member of node list."
		var newParent;
		if (isEditable(nodeList[0].previousSibling) && siblingCriteria(nodeList[0].previousSibling)) {
			newParent = nodeList[0].previousSibling;

			// "Otherwise, if the nextSibling of the last member of node list is
			// editable and running sibling criteria on it returns true, let new parent
			// be the nextSibling of the last member of node list."
		} else if (isEditable(nodeList[nodeList.length - 1].nextSibling) && siblingCriteria(nodeList[nodeList.length - 1].nextSibling)) {
			newParent = nodeList[nodeList.length - 1].nextSibling;

			// "Otherwise, run new parent instructions, and let new parent be the
			// result."
		} else {
			newParent = newParentInstructions();
		}

		// "If new parent is null, abort these steps and return null."
		if (!newParent) {
			return null;
		}

		// "If new parent's parent is null:"
		var newParentParentNode = newParent.parentNode;
		if (!newParentParentNode) {
			// "Insert new parent into the parent of the first member of node list
			// immediately before the first member of node list."
			nodeList[0].parentNode.insertBefore(newParent, nodeList[0]);

			// "If any range has a boundary point with node equal to the parent of
			// new parent and offset equal to the index of new parent, add one to
			// that boundary point's offset."
			//
			// Try to fix range
			var startContainer = range.startContainer,
				startOffset = range.startOffset,
				endContainer = range.endContainer,
				endOffset = range.endOffset,
			    newParentIndex = Dom.getIndexInParent(newParent);

			if (startOffset >= newParentIndex && startContainer == newParentParentNode) {
				range.setStart(startContainer, startOffset + 1);
			}
			if (endOffset >= newParentIndex && endContainer == newParentParentNode) {
				range.setEnd(endContainer, endOffset + 1);
			}

			// Only try to fix the global range. TODO remove globalRange here
			if (globalRange && globalRange !== range) {
				startContainer = globalRange.startContainer;
				startOffset = globalRange.startOffset;
				endContainer = globalRange.endContainer;
				endOffset = globalRange.endOffset;
				if (startContainer == newParentParentNode && startOffset >= newParentIndex) {
					globalRange.setStart(startContainer, startOffset + 1);
				}
				if (endContainer == newParentParentNode && endOffset >= newParentIndex) {
					globalRange.setEnd(endContainer, endOffset + 1);
				}
			}
		}

		// "Let original parent be the parent of the first member of node list."
		var originalParent = nodeList[0].parentNode;

		// "If new parent is before the first member of node list in tree order:"
		if (isBefore(newParent, nodeList[0])) {
			// "If new parent is not an inline node, but the last child of new
			// parent and the first member of node list are both inline nodes, and
			// the last child of new parent is not a br, call createElement("br")
			// on the ownerDocument of new parent and append the result as the last
			// child of new parent."
			if (!isInlineNode(newParent) && isInlineNode(newParent.lastChild) && isInlineNode(nodeList[0]) && !isNamedHtmlElement(newParent.lastChild, "BR")) {
				newParent.appendChild(newParent.ownerDocument.createElement("br"));
			}

			// "For each node in node list, append node as the last child of new
			// parent, preserving ranges."
			for (i = 0; i < nodeList.length; i++) {
				movePreservingRanges(nodeList[i], newParent, -1, range);
			}

			// "Otherwise:"
		} else {
			// "If new parent is not an inline node, but the first child of new
			// parent and the last member of node list are both inline nodes, and
			// the last member of node list is not a br, call createElement("br")
			// on the ownerDocument of new parent and insert the result as the
			// first child of new parent."
			if (!isInlineNode(newParent) && isInlineNode(newParent.firstChild) && isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "BR")) {
				newParent.insertBefore(newParent.ownerDocument.createElement("br"), newParent.firstChild);
			}

			// "For each node in node list, in reverse order, insert node as the
			// first child of new parent, preserving ranges."
			for (i = nodeList.length - 1; i >= 0; i--) {
				movePreservingRanges(nodeList[i], newParent, 0, range);
			}
		}

		// "If original parent is editable and has no children, remove it from its
		// parent."
		if (isEditable(originalParent) && !originalParent.hasChildNodes()) {
			originalParent.parentNode.removeChild(originalParent);
		}

		// "If new parent's nextSibling is editable and running sibling criteria on
		// it returns true:"
		if (isEditable(newParent.nextSibling) && siblingCriteria(newParent.nextSibling)) {
			// "If new parent is not an inline node, but new parent's last child
			// and new parent's nextSibling's first child are both inline nodes,
			// and new parent's last child is not a br, call createElement("br") on
			// the ownerDocument of new parent and append the result as the last
			// child of new parent."
			if (!isInlineNode(newParent) && isInlineNode(newParent.lastChild) && isInlineNode(newParent.nextSibling.firstChild) && !isNamedHtmlElement(newParent.lastChild, "BR")) {
				newParent.appendChild(newParent.ownerDocument.createElement("br"));
			}

			// "While new parent's nextSibling has children, append its first child
			// as the last child of new parent, preserving ranges."
			while (newParent.nextSibling.hasChildNodes()) {
				movePreservingRanges(newParent.nextSibling.firstChild, newParent, -1, range);
			}

			// "Remove new parent's nextSibling from its parent."
			newParentParentNode.removeChild(newParent.nextSibling);
		}

		// "Remove extraneous line breaks from new parent."
		removeExtraneousLineBreaksFrom(newParent);

		// "Return new parent."
		return newParent;
	}


	//@}
	///// Allowed children /////
	//@{

	// "A name of an element with inline contents is "a", "abbr", "b", "bdi",
	// "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
	// "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
	// "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
	// "xmp", "big", "blink", "font", "marquee", "nobr", or "tt"."
	var namesOfElementsWithInlineContentsMap = {
		"A": true,
		"ABBR": true,
		"B": true,
		"BDI": true,
		"BDO": true,
		"CITE": true,
		"CODE": true,
		"DFN": true,
		"EM": true,
		"H1": true,
		"H2": true,
		"H3": true,
		"H4": true,
		"H5": true,
		"H6": true,
		"I": true,
		"KBD": true,
		"MARK": true,
		"P": true,
		"PRE": true,
		"Q": true,
		"RP": true,
		"RT": true,
		"RUBY": true,
		"S": true,
		"SAMP": true,
		"SMALL": true,
		"SPAN": true,
		"STRONG": true,
		"SUB": true,
		"SUP": true,
		"U": true,
		"VAR": true,
		"ACRONYM": true,
		"LISTING": true,
		"STRIKE": true,
		"XMP": true,
		"BIG": true,
		"BLINK": true,
		"FONT": true,
		"MARQUEE": true,
		"NOBR": true,
		"TT": true
	};


	var tableRelatedElements = {
		"colgroup": true,
		"table": true,
		"tbody": true,
		"tfoot": true,
		"thead": true,
		"tr": true
	};

	var scriptRelatedElements = {
		"script": true,
		"style": true,
		"plaintext": true,
		"xmp": true
	};

	var prohibitedHeadingNestingMap = jQuery.extend({
		"H1": true,
		"H2": true,
		"H3": true,
		"H4": true,
		"H5": true,
		"H6": true
	}, prohibitedParagraphChildNamesMap);
	var prohibitedTableNestingMap = {
		"CAPTION": true,
		"COL": true,
		"COLGROUP": true,
		"TBODY": true,
		"TD": true,
		"TFOOT": true,
		"TH": true,
		"THEAD": true,
		"TR": true
	};
	var prohibitedDefNestingMap = {
		"DD": true,
		"DT": true
	};
	var prohibitedNestingCombinationsMap = {
		"A": jQuery.extend({
			"A": true
		}, prohibitedParagraphChildNamesMap),
		"DD": prohibitedDefNestingMap,
		"DT": prohibitedDefNestingMap,
		"LI": {
			"LI": true
		},
		"NOBR": jQuery.extend({
			"NOBR": true
		}, prohibitedParagraphChildNamesMap),
		"H1": prohibitedHeadingNestingMap,
		"H2": prohibitedHeadingNestingMap,
		"H3": prohibitedHeadingNestingMap,
		"H4": prohibitedHeadingNestingMap,
		"H5": prohibitedHeadingNestingMap,
		"H6": prohibitedHeadingNestingMap,
		"TD": prohibitedTableNestingMap,
		"TH": prohibitedTableNestingMap,
		// this is the same as namesOfElementsWithInlineContentsMap excluding a and h1-h6 elements above
		"ABBR": prohibitedParagraphChildNamesMap,
		"B": prohibitedParagraphChildNamesMap,
		"BDI": prohibitedParagraphChildNamesMap,
		"BDO": prohibitedParagraphChildNamesMap,
		"CITE": prohibitedParagraphChildNamesMap,
		"CODE": prohibitedParagraphChildNamesMap,
		"DFN": prohibitedParagraphChildNamesMap,
		"EM": prohibitedParagraphChildNamesMap,
		"I": prohibitedParagraphChildNamesMap,
		"KBD": prohibitedParagraphChildNamesMap,
		"MARK": prohibitedParagraphChildNamesMap,
		"P": prohibitedParagraphChildNamesMap,
		"PRE": prohibitedParagraphChildNamesMap,
		"Q": prohibitedParagraphChildNamesMap,
		"RP": prohibitedParagraphChildNamesMap,
		"RT": prohibitedParagraphChildNamesMap,
		"RUBY": prohibitedParagraphChildNamesMap,
		"S": prohibitedParagraphChildNamesMap,
		"SAMP": prohibitedParagraphChildNamesMap,
		"SMALL": prohibitedParagraphChildNamesMap,
		"SPAN": prohibitedParagraphChildNamesMap,
		"STRONG": prohibitedParagraphChildNamesMap,
		"SUB": prohibitedParagraphChildNamesMap,
		"SUP": prohibitedParagraphChildNamesMap,
		"U": prohibitedParagraphChildNamesMap,
		"VAR": prohibitedParagraphChildNamesMap,
		"ACRONYM": prohibitedParagraphChildNamesMap,
		"LISTING": prohibitedParagraphChildNamesMap,
		"STRIKE": prohibitedParagraphChildNamesMap,
		"XMP": prohibitedParagraphChildNamesMap,
		"BIG": prohibitedParagraphChildNamesMap,
		"BLINK": prohibitedParagraphChildNamesMap,
		"FONT": prohibitedParagraphChildNamesMap,
		"MARQUEE": prohibitedParagraphChildNamesMap,
		"TT": prohibitedParagraphChildNamesMap
	};

	// "An element with inline contents is an HTML element whose local name is a
	// name of an element with inline contents."
	function isElementWithInlineContents(node) {
		return isMappedHtmlElement(node, namesOfElementsWithInlineContentsMap);
	}

	function isAllowedChild(child, parent_) {
		// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
		// an HTML element with local name equal to one of those, and child is a
		// Text node whose data does not consist solely of space characters, return
		// false."
		if ((tableRelatedElements[parent_] || isHtmlElementInArray(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"])) && typeof child == "object" && child.nodeType == $_.Node.TEXT_NODE && !/^[ \t\n\f\r]*$/.test(child.data)) {
			return false;
		}

		// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
		// element with local name equal to one of those, and child is not a Text
		// node, return false."
		if ((scriptRelatedElements[parent_] || isHtmlElementInArray(parent_, ["script", "style", "plaintext", "xmp"])) && (typeof child != "object" || child.nodeType != $_.Node.TEXT_NODE)) {
			return false;
		}

		// "If child is a Document, DocumentFragment, or DocumentType, return
		// false."
		if (typeof child == "object" && (child.nodeType == $_.Node.DOCUMENT_NODE || child.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE || child.nodeType == $_.Node.DOCUMENT_TYPE_NODE)) {
			return false;
		}

		// "If child is an HTML element, set child to the local name of child."
		if (isAnyHtmlElement(child)) {
			child = child.tagName.toLowerCase();
		}

		// "If child is not a string, return true."
		if (typeof child != "string") {
			return true;
		}

		// "If parent is an HTML element:"
		if (isAnyHtmlElement(parent_)) {
			// "If child is "a", and parent or some ancestor of parent is an a,
			// return false."
			//
			// "If child is a prohibited paragraph child name and parent or some
			// ancestor of parent is an element with inline contents, return
			// false."
			//
			// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
			// some ancestor of parent is an HTML element with local name "h1",
			// "h2", "h3", "h4", "h5", or "h6", return false."
			var ancestor = parent_;
			while (ancestor) {
				if (child == "a" && isNamedHtmlElement(ancestor, 'a')) {
					return false;
				}
				if (prohibitedParagraphChildNamesMap[child.toUpperCase()] && isElementWithInlineContents(ancestor)) {
					return false;
				}
				if (/^h[1-6]$/.test(child) && isAnyHtmlElement(ancestor) && /^H[1-6]$/.test(ancestor.tagName)) {
					return false;
				}
				ancestor = ancestor.parentNode;
			}

			// "Let parent be the local name of parent."
			parent_ = parent_.tagName.toLowerCase();
		}

		// "If parent is an Element or DocumentFragment, return true."
		if (typeof parent_ == "object" && (parent_.nodeType == $_.Node.ELEMENT_NODE || parent_.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE)) {
			return true;
		}

		// "If parent is not a string, return false."
		if (typeof parent_ != "string") {
			return false;
		}

		// "If parent is on the left-hand side of an entry on the following list,
		// then return true if child is listed on the right-hand side of that
		// entry, and false otherwise."
		switch (parent_) {
		case "colgroup":
			return child == "col";
		case "table":
			return jQuery.inArray(child, ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]) != -1;
		case "tbody":
		case "thead":
		case "tfoot":
			return jQuery.inArray(child, ["td", "th", "tr"]) != -1;
		case "tr":
			return jQuery.inArray(child, ["td", "th"]) != -1;
		case "dl":
			return jQuery.inArray(child, ["dt", "dd"]) != -1;
		case "dir":
		case "ol":
		case "ul":
			return jQuery.inArray(child, ["dir", "li", "ol", "ul"]) != -1;
		case "hgroup":
			return (/^h[1-6]$/).test(child);
		}

		// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
		// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
		// false."
		if (jQuery.inArray(child, ["body", "caption", "col", "colgroup", "frame", "frameset", "head", "html", "tbody", "td", "tfoot", "th", "thead", "tr"]) != -1) {
			return false;
		}

		// "If child is "dd" or "dt" and parent is not "dl", return false."
		if (jQuery.inArray(child, ["dd", "dt"]) != -1 && parent_ != "dl") {
			return false;
		}

		// "If child is "li" and parent is not "ol" or "ul", return false."
		if (child == "li" && parent_ != "ol" && parent_ != "ul") {
			return false;
		}

		// "If parent is on the left-hand side of an entry on the following list
		// and child is listed on the right-hand side of that entry, return false."
		var leftSide = prohibitedNestingCombinationsMap[parent_.toUpperCase()];
		if (leftSide) {
			var rightSide = leftSide[child.toUpperCase()];
			if (rightSide) {
				return false;
			}
		}

		// "Return true."
		return true;
	}


	//@}

	//////////////////////////////////////
	///// Inline formatting commands /////
	//////////////////////////////////////

	///// Inline formatting command definitions /////
	//@{

	// "A node node is effectively contained in a range range if range is not
	// collapsed, and at least one of the following holds:"
	function isEffectivelyContained(node, range) {
		if (range.collapsed) {
			return false;
		}

		// "node is contained in range."
		if (isContained(node, range)) {
			return true;
		}

		// "node is range's start node, it is a Text node, and its length is
		// different from range's start offset."
		if (node == range.startContainer && node.nodeType == $_.Node.TEXT_NODE && getNodeLength(node) != range.startOffset) {
			return true;
		}

		// "node is range's end node, it is a Text node, and range's end offset is
		// not 0."
		if (node == range.endContainer && node.nodeType == $_.Node.TEXT_NODE && range.endOffset != 0) {
			return true;
		}

		// "node has at least one child; and all its children are effectively
		// contained in range; and either range's start node is not a descendant of
		// node or is not a Text node or range's start offset is zero; and either
		// range's end node is not a descendant of node or is not a Text node or
		// range's end offset is its end node's length."
		if (node.hasChildNodes() && $_(node.childNodes).every(function (child) { return isEffectivelyContained(child, range); })
			    && (!isDescendant(range.startContainer, node)
					|| range.startContainer.nodeType != $_.Node.TEXT_NODE
					|| range.startOffset == 0)
			    && (!isDescendant(range.endContainer, node)
					|| range.endContainer.nodeType != $_.Node.TEXT_NODE
					|| range.endOffset == getNodeLength(range.endContainer))) {
			return true;
		}

		return false;
	}

	// Like get(All)ContainedNodes(), but for effectively contained nodes.
	function getEffectivelyContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		while (isEffectivelyContained(node.parentNode, range)) {
			node = node.parentNode;
		}

		var stop = nextNodeDescendants(range.endContainer);

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isEffectivelyContained(node, range) && condition(node)) {
				nodeList.push(node);
				node = nextNodeDescendants(node);
				continue;
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	function getAllEffectivelyContainedNodes(range, condition) {
		if (typeof condition == "undefined") {
			condition = function () {
				return true;
			};
		}
		var node = range.startContainer;
		while (isEffectivelyContained(node.parentNode, range)) {
			node = node.parentNode;
		}

		var stop = nextNodeDescendants(range.endContainer);

		var nodeList = [];
		while (isBefore(node, stop)) {
			if (isEffectivelyContained(node, range) && condition(node)) {
				nodeList.push(node);
			}
			node = nextNode(node);
		}
		return nodeList;
	}

	// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
	// with no attributes except possibly style; or a font element with no
	// attributes except possibly style, color, face, and/or size; or an a element
	// with no attributes except possibly style and/or href."
	function isModifiableElement(node) {
		if (!isAnyHtmlElement(node)) {
			return false;
		}

		if (jQuery.inArray(node.tagName, ["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"]) != -1) {
			if (node.attributes.length == 0) {
				return true;
			}

			if (node.attributes.length == 1 && hasAttribute(node, "style")) {
				return true;
			}
		}

		if (node.tagName == "FONT" || node.tagName == "A") {
			var numAttrs = node.attributes.length;

			if (hasAttribute(node, "style")) {
				numAttrs--;
			}

			if (node.tagName == "FONT") {
				if (hasAttribute(node, "color")) {
					numAttrs--;
				}

				if (hasAttribute(node, "face")) {
					numAttrs--;
				}

				if (hasAttribute(node, "size")) {
					numAttrs--;
				}
			}

			if (node.tagName == "A" && hasAttribute(node, "href")) {
				numAttrs--;
			}

			if (numAttrs == 0) {
				return true;
			}
		}

		return false;
	}

	function isSimpleModifiableElement(node) {
		// "A simple modifiable element is an HTML element for which at least one
		// of the following holds:"
		if (!isAnyHtmlElement(node)) {
			return false;
		}

		// Only these elements can possibly be a simple modifiable element.
		if (jQuery.inArray(node.tagName, ["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"]) == -1) {
			return false;
		}

		// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
		// element with no attributes."
		if (node.attributes.length == 0) {
			return true;
		}

		// If it's got more than one attribute, everything after this fails.
		if (node.attributes.length > 1) {
			return false;
		}

		// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
		// element with exactly one attribute, which is style, which sets no CSS
		// properties (including invalid or unrecognized properties)."
		//
		// Not gonna try for invalid or unrecognized.
		if (hasAttribute(node, "style") && getStyleLength(node) == 0) {
			return true;
		}

		// "It is an a element with exactly one attribute, which is href."
		if (node.tagName == "A" && hasAttribute(node, "href")) {
			return true;
		}

		// "It is a font element with exactly one attribute, which is either color,
		// face, or size."
		if (node.tagName == "FONT" && (hasAttribute(node, "color") || hasAttribute(node, "face") || hasAttribute(node, "size"))) {
			return true;
		}

		// "It is a b or strong element with exactly one attribute, which is style,
		// and the style attribute sets exactly one CSS property (including invalid
		// or unrecognized properties), which is "font-weight"."
		if ((node.tagName == "B" || node.tagName == "STRONG") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.fontWeight != "") {
			return true;
		}

		// "It is an i or em element with exactly one attribute, which is style,
		// and the style attribute sets exactly one CSS property (including invalid
		// or unrecognized properties), which is "font-style"."
		if ((node.tagName == "I" || node.tagName == "EM") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.fontStyle != "") {
			return true;
		}

		// "It is an a, font, or span element with exactly one attribute, which is
		// style, and the style attribute sets exactly one CSS property (including
		// invalid or unrecognized properties), and that property is not
		// "text-decoration"."
		if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.textDecoration == "") {
			return true;
		}

		// "It is an a, font, s, span, strike, or u element with exactly one
		// attribute, which is style, and the style attribute sets exactly one CSS
		// property (including invalid or unrecognized properties), which is
		// "text-decoration", which is set to "line-through" or "underline" or
		// "overline" or "none"."
		if (jQuery.inArray(node.tagName, ["A", "FONT", "S", "SPAN", "STRIKE", "U"]) != -1 && hasAttribute(node, "style") && getStyleLength(node) == 1 && (node.style.textDecoration == "line-through" || node.style.textDecoration == "underline" || node.style.textDecoration == "overline" || node.style.textDecoration == "none")) {
			return true;
		}

		return false;
	}

	// "Two quantities are equivalent values for a command if either both are null,
	// or both are strings and they're equal and the command does not define any
	// equivalent values, or both are strings and the command defines equivalent
	// values and they match the definition."
	function areEquivalentValues(command, val1, val2) {
		if (val1 === null && val2 === null) {
			return true;
		}

		if (typeof val1 == "string" && typeof val2 == "string" && val1 == val2 && !(commands[command].hasOwnProperty("equivalentValues"))) {
			return true;
		}

		if (typeof val1 == "string" && typeof val2 == "string" && commands[command].hasOwnProperty("equivalentValues") && commands[command].equivalentValues(val1, val2)) {
			return true;
		}

		return false;
	}

	// "Two quantities are loosely equivalent values for a command if either they
	// are equivalent values for the command, or if the command is the fontSize
	// command; one of the quantities is one of "xx-small", "small", "medium",
	// "large", "x-large", "xx-large", or "xxx-large"; and the other quantity is
	// the resolved value of "font-size" on a font element whose size attribute has
	// the corresponding value set ("1" through "7" respectively)."
	function areLooselyEquivalentValues(command, val1, val2) {
		if (areEquivalentValues(command, val1, val2)) {
			return true;
		}

		if (command != "fontsize" || typeof val1 != "string" || typeof val2 != "string") {
			return false;
		}

		// Static variables in JavaScript?
		var callee = areLooselyEquivalentValues;
		if (callee.sizeMap === undefined) {
			callee.sizeMap = {};
			var font = document.createElement("font");
			document.body.appendChild(font);
			$_(["xx-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]).forEach(function (keyword) {
				font.size = cssSizeToLegacy(keyword);
				callee.sizeMap[keyword] = $_.getComputedStyle(font).fontSize;
			});
			document.body.removeChild(font);
		}

		return val1 === callee.sizeMap[val2] || val2 === callee.sizeMap[val1];
	}

	//@}
	///// Assorted inline formatting command algorithms /////
	//@{

	function getEffectiveCommandValue(node, command) {
		// "If neither node nor its parent is an Element, return null."
		if (node.nodeType != $_.Node.ELEMENT_NODE && (!node.parentNode || node.parentNode.nodeType != $_.Node.ELEMENT_NODE)) {
			return null;
		}

		// "If node is not an Element, return the effective command value of its
		// parent for command."
		if (node.nodeType != $_.Node.ELEMENT_NODE) {
			return getEffectiveCommandValue(node.parentNode, command);
		}

		// "If command is "createLink" or "unlink":"
		if (command == "createlink" || command == "unlink") {
			// "While node is not null, and is not an a element that has an href
			// attribute, set node to its parent."
			while (node && (!isAnyHtmlElement(node) || node.tagName != "A" || !hasAttribute(node, "href"))) {
				node = node.parentNode;
			}

			// "If node is null, return null."
			if (!node) {
				return null;
			}

			// "Return the value of node's href attribute."
			return node.getAttribute("href");
		}

		// "If command is "backColor" or "hiliteColor":"
		if (command == "backcolor" || command == "hilitecolor") {
			// "While the resolved value of "background-color" on node is any
			// fully transparent value, and node's parent is an Element, set
			// node to its parent."
			//
			// Another lame hack to avoid flawed APIs.
			while (($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)" || $_.getComputedStyle(node).backgroundColor === "" || $_.getComputedStyle(node).backgroundColor == "transparent") && node.parentNode && node.parentNode.nodeType == $_.Node.ELEMENT_NODE) {
				node = node.parentNode;
			}

			// "If the resolved value of "background-color" on node is a fully
			// transparent value, return "rgb(255, 255, 255)"."
			if ($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)" || $_.getComputedStyle(node).backgroundColor === "" || $_.getComputedStyle(node).backgroundColor == "transparent") {
				return "rgb(255, 255, 255)";
			}

			// "Otherwise, return the resolved value of "background-color" for
			// node."
			return $_.getComputedStyle(node).backgroundColor;
		}

		// "If command is "subscript" or "superscript":"
		if (command == "subscript" || command == "superscript") {
			// "Let affected by subscript and affected by superscript be two
			// boolean variables, both initially false."
			var affectedBySubscript = false;
			var affectedBySuperscript = false;

			// "While node is an inline node:"
			while (isInlineNode(node)) {
				var verticalAlign = $_.getComputedStyle(node).verticalAlign;

				// "If node is a sub, set affected by subscript to true."
				if (isNamedHtmlElement(node, 'sub')) {
					affectedBySubscript = true;
					// "Otherwise, if node is a sup, set affected by superscript to
					// true."
				} else if (isNamedHtmlElement(node, 'sup')) {
					affectedBySuperscript = true;
				}

				// "Set node to its parent."
				node = node.parentNode;
			}

			// "If affected by subscript and affected by superscript are both true,
			// return the string "mixed"."
			if (affectedBySubscript && affectedBySuperscript) {
				return "mixed";
			}

			// "If affected by subscript is true, return "subscript"."
			if (affectedBySubscript) {
				return "subscript";
			}

			// "If affected by superscript is true, return "superscript"."
			if (affectedBySuperscript) {
				return "superscript";
			}

			// "Return null."
			return null;
		}

		// "If command is "strikethrough", and the "text-decoration" property of
		// node or any of its ancestors has resolved value containing
		// "line-through", return "line-through". Otherwise, return null."
		if (command == "strikethrough") {
			do {
				if ($_.getComputedStyle(node).textDecoration.indexOf("line-through") != -1) {
					return "line-through";
				}
				node = node.parentNode;
			} while (node && node.nodeType == $_.Node.ELEMENT_NODE);
			return null;
		}

		// "If command is "underline", and the "text-decoration" property of node
		// or any of its ancestors has resolved value containing "underline",
		// return "underline". Otherwise, return null."
		if (command == "underline") {
			do {
				if ($_.getComputedStyle(node).textDecoration.indexOf("underline") != -1) {
					return "underline";
				}
				node = node.parentNode;
			} while (node && node.nodeType == $_.Node.ELEMENT_NODE);
			return null;
		}

		if (!commands[command].hasOwnProperty("relevantCssProperty")) {
			throw "Bug: no relevantCssProperty for " + command + " in getEffectiveCommandValue";
		}

		// "Return the resolved value for node of the relevant CSS property for
		// command."
		return $_.getComputedStyle(node)[commands[command].relevantCssProperty].toString();
	}

	function getSpecifiedCommandValue(element, command) {
		// "If command is "backColor" or "hiliteColor" and element's display
		// property does not have resolved value "inline", return null."
		if ((command == "backcolor" || command == "hilitecolor") && $_.getComputedStyle(element).display != "inline") {
			return null;
		}

		// "If command is "createLink" or "unlink":"
		if (command == "createlink" || command == "unlink") {
			// "If element is an a element and has an href attribute, return the
			// value of that attribute."
			if (isAnyHtmlElement(element) && element.tagName == "A" && hasAttribute(element, "href")) {
				return element.getAttribute("href");
			}

			// "Return null."
			return null;
		}

		// "If command is "subscript" or "superscript":"
		if (command == "subscript" || command == "superscript") {
			// "If element is a sup, return "superscript"."
			if (isNamedHtmlElement(element, 'sup')) {
				return "superscript";
			}

			// "If element is a sub, return "subscript"."
			if (isNamedHtmlElement(element, 'sub')) {
				return "subscript";
			}

			// "Return null."
			return null;
		}

		// "If command is "strikethrough", and element has a style attribute set,
		// and that attribute sets "text-decoration":"
		if (command == "strikethrough" && element.style.textDecoration != "") {
			// "If element's style attribute sets "text-decoration" to a value
			// containing "line-through", return "line-through"."
			if (element.style.textDecoration.indexOf("line-through") != -1) {
				return "line-through";
			}

			// "Return null."
			return null;
		}

		// "If command is "strikethrough" and element is a s or strike element,
		// return "line-through"."
		if (command == "strikethrough" && isHtmlElementInArray(element, ["S", "STRIKE"])) {
			return "line-through";
		}

		// "If command is "underline", and element has a style attribute set, and
		// that attribute sets "text-decoration":"
		if (command == "underline" && element.style.textDecoration != "") {
			// "If element's style attribute sets "text-decoration" to a value
			// containing "underline", return "underline"."
			if (element.style.textDecoration.indexOf("underline") != -1) {
				return "underline";
			}

			// "Return null."
			return null;
		}

		// "If command is "underline" and element is a u element, return
		// "underline"."
		if (command == "underline" && isNamedHtmlElement(element, 'U')) {
			return "underline";
		}

		// "Let property be the relevant CSS property for command."
		var property = commands[command].relevantCssProperty;

		// "If property is null, return null."
		if (property === null) {
			return null;
		}

		// "If element has a style attribute set, and that attribute has the
		// effect of setting property, return the value that it sets property to."
		if (element.style[property] != "") {
			return element.style[property];
		}

		// "If element is a font element that has an attribute whose effect is
		// to create a presentational hint for property, return the value that the
		// hint sets property to.  (For a size of 7, this will be the non-CSS value
		// "xxx-large".)"
		if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
			if (property == "color" && hasAttribute(element, "color")) {
				return element.color;
			}
			if (property == "fontFamily" && hasAttribute(element, "face")) {
				return element.face;
			}
			if (property == "fontSize" && hasAttribute(element, "size")) {
				// This is not even close to correct in general.
				var size = parseInt(element.size, 10);
				if (size < 1) {
					size = 1;
				}
				if (size > 7) {
					size = 7;
				}
				return {
					1: "xx-small",
					2: "small",
					3: "medium",
					4: "large",
					5: "x-large",
					6: "xx-large",
					7: "xxx-large"
				}[size];
			}
		}

		// "If element is in the following list, and property is equal to the
		// CSS property name listed for it, return the string listed for it."
		//
		// A list follows, whose meaning is copied here.
		if (property == "fontWeight" && (element.tagName == "B" || element.tagName == "STRONG")) {
			return "bold";
		}
		if (property == "fontStyle" && (element.tagName == "I" || element.tagName == "EM")) {
			return "italic";
		}

		// "Return null."
		return null;
	}

	function reorderModifiableDescendants(node, command, newValue, range) {
		// "Let candidate equal node."
		var candidate = node;

		// "While candidate is a modifiable element, and candidate has exactly one
		// child, and that child is also a modifiable element, and candidate is not
		// a simple modifiable element or candidate's specified command value for
		// command is not equivalent to new value, set candidate to its child."
		while (isModifiableElement(candidate) && candidate.childNodes.length == 1 && isModifiableElement(candidate.firstChild) && (!isSimpleModifiableElement(candidate) || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue))) {
			candidate = candidate.firstChild;
		}

		// "If candidate is node, or is not a simple modifiable element, or its
		// specified command value is not equivalent to new value, or its effective
		// command value is not loosely equivalent to new value, abort these
		// steps."
		if (candidate == node || !isSimpleModifiableElement(candidate) || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue) || !areLooselyEquivalentValues(command, getEffectiveCommandValue(candidate, command), newValue)) {
			return;
		}

		// "While candidate has children, insert the first child of candidate into
		// candidate's parent immediately before candidate, preserving ranges."
		while (candidate.hasChildNodes()) {
			movePreservingRanges(candidate.firstChild, candidate.parentNode, Dom.getIndexInParent(candidate), range);
		}

		// "Insert candidate into node's parent immediately after node."
		node.parentNode.insertBefore(candidate, node.nextSibling);

		// "Append the node as the last child of candidate, preserving ranges."
		movePreservingRanges(node, candidate, -1, range);
	}

	var recordValuesCommands = ["subscript", "bold", "fontname", "fontsize", "forecolor", "hilitecolor", "italic", "strikethrough", "underline"];

	function recordValues(nodeList) {
		// "Let values be a list of (node, command, specified command value)
		// triples, initially empty."
		var values = [];

		// "For each node in node list, for each command in the list "subscript",
		// "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic",
		// "strikethrough", and "underline" in that order:"

		// Ensure we have a plain array to avoid the potential performance
		// overhead of a NodeList
		var nodes = jQuery.makeArray(nodeList);
		var i, j;
		var node;
		var command;
		var ancestor;
		var specifiedCommandValue;
		for (i = 0; i < nodes.length; i++) {
			node = nodes[i];
			for (j = 0; j < recordValuesCommands.length; j++) {
				command = recordValuesCommands[j];

				// "Let ancestor equal node."
				ancestor = node;

				// "If ancestor is not an Element, set it to its parent."
				if (ancestor.nodeType != 1) {
					ancestor = ancestor.parentNode;
				}

				// "While ancestor is an Element and its specified command value
				// for command is null, set it to its parent."
				specifiedCommandValue = null;
				while (ancestor && ancestor.nodeType == 1 && (specifiedCommandValue = getSpecifiedCommandValue(ancestor, command)) === null) {
					ancestor = ancestor.parentNode;
				}

				// "If ancestor is an Element, add (node, command, ancestor's
				// specified command value for command) to values. Otherwise add
				// (node, command, null) to values."
				values.push([node, command, specifiedCommandValue]);
			}
		}

		// "Return values."
		return values;
	}

	//@}
	///// Clearing an element's value /////
	//@{

	function clearValue(element, command, range) {
		// "If element is not editable, return the empty list."
		if (!isEditable(element)) {
			return [];
		}

		// "If element's specified command value for command is null, return the
		// empty list."
		if (getSpecifiedCommandValue(element, command) === null) {
			return [];
		}

		// "If element is a simple modifiable element:"
		if (isSimpleModifiableElement(element)) {
			// "Let children be the children of element."
			var children = Array.prototype.slice.call(toArray(element.childNodes));

			// "For each child in children, insert child into element's parent
			// immediately before element, preserving ranges."
			var i;
			for (i = 0; i < children.length; i++) {
				movePreservingRanges(children[i], element.parentNode, Dom.getIndexInParent(element), range);
			}

			// "Remove element from its parent."
			element.parentNode.removeChild(element);

			// "Return children."
			return children;
		}

		// "If command is "strikethrough", and element has a style attribute that
		// sets "text-decoration" to some value containing "line-through", delete
		// "line-through" from the value."
		if (command == "strikethrough" && element.style.textDecoration.indexOf("line-through") != -1) {
			if (element.style.textDecoration == "line-through") {
				element.style.textDecoration = "";
			} else {
				element.style.textDecoration = element.style.textDecoration.replace("line-through", "");
			}
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}
		}

		// "If command is "underline", and element has a style attribute that sets
		// "text-decoration" to some value containing "underline", delete
		// "underline" from the value."
		if (command == "underline" && element.style.textDecoration.indexOf("underline") != -1) {
			if (element.style.textDecoration == "underline") {
				element.style.textDecoration = "";
			} else {
				element.style.textDecoration = element.style.textDecoration.replace("underline", "");
			}
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}
		}

		// "If the relevant CSS property for command is not null, unset the CSS
		// property property of element."
		if (commands[command].relevantCssProperty !== null) {
			element.style[commands[command].relevantCssProperty] = '';
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}
		}

		// "If element is a font element:"
		if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
			// "If command is "foreColor", unset element's color attribute, if set."
			if (command == "forecolor") {
				element.removeAttribute("color");
			}

			// "If command is "fontName", unset element's face attribute, if set."
			if (command == "fontname") {
				element.removeAttribute("face");
			}

			// "If command is "fontSize", unset element's size attribute, if set."
			if (command == "fontsize") {
				element.removeAttribute("size");
			}
		}

		// "If element is an a element and command is "createLink" or "unlink",
		// unset the href property of element."
		if (isNamedHtmlElement(element, 'A') && (command == "createlink" || command == "unlink")) {
			element.removeAttribute("href");
		}

		// "If element's specified command value for command is null, return the
		// empty list."
		if (getSpecifiedCommandValue(element, command) === null) {
			return [];
		}

		// "Set the tag name of element to "span", and return the one-node list
		// consisting of the result."
		return [setTagName(element, "span", range)];
	}

	//@}
	///// Forcing the value of a node /////
	//@{

	function forceValue(node, command, newValue, range) {
		var children = [];
		var i;
		var specifiedValue;

		// "If node's parent is null, abort this algorithm."
		if (!node.parentNode) {
			return;
		}

		// "If new value is null, abort this algorithm."
		if (newValue === null) {
			return;
		}

		// "If node is an allowed child of "span":"
		if (isAllowedChild(node, "span")) {
			// "Reorder modifiable descendants of node's previousSibling."
			reorderModifiableDescendants(node.previousSibling, command, newValue, range);

			// "Reorder modifiable descendants of node's nextSibling."
			reorderModifiableDescendants(node.nextSibling, command, newValue, range);

			// "Wrap the one-node list consisting of node, with sibling criteria
			// returning true for a simple modifiable element whose specified
			// command value is equivalent to new value and whose effective command
			// value is loosely equivalent to new value and false otherwise, and
			// with new parent instructions returning null."
			wrap(
				[node],
				function (node) {
					return isSimpleModifiableElement(node) && areEquivalentValues(command, getSpecifiedCommandValue(node, command), newValue) && areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue);
				},
				function () {
					return null;
				},
				range
			);
		}

		// "If the effective command value of command is loosely equivalent to new
		// value on node, abort this algorithm."
		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			return;
		}

		// "If node is not an allowed child of "span":"
		if (!isAllowedChild(node, "span")) {
			// "Let children be all children of node, omitting any that are
			// Elements whose specified command value for command is neither null
			// nor equivalent to new value."
			for (i = 0; i < node.childNodes.length; i++) {
				if (node.childNodes[i].nodeType == $_.Node.ELEMENT_NODE) {
					specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);

					if (specifiedValue !== null && !areEquivalentValues(command, newValue, specifiedValue)) {
						continue;
					}
				}
				children.push(node.childNodes[i]);
			}

			// "Force the value of each Node in children, with command and new
			// value as in this invocation of the algorithm."
			for (i = 0; i < children.length; i++) {
				forceValue(children[i], command, newValue, range);
			}

			// "Abort this algorithm."
			return;
		}

		// "If the effective command value of command is loosely equivalent to new
		// value on node, abort this algorithm."
		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			return;
		}

		// "Let new parent be null."
		var newParent = null;

		// "If the CSS styling flag is false:"
		if (!cssStylingFlag) {
			// "If command is "bold" and new value is "bold", let new parent be the
			// result of calling createElement("b") on the ownerDocument of node."
			if (command == "bold" && (newValue == "bold" || newValue == "700")) {
				newParent = node.ownerDocument.createElement("b");
			}

			// "If command is "italic" and new value is "italic", let new parent be
			// the result of calling createElement("i") on the ownerDocument of
			// node."
			if (command == "italic" && newValue == "italic") {
				newParent = node.ownerDocument.createElement("i");
			}

			// "If command is "strikethrough" and new value is "line-through", let
			// new parent be the result of calling createElement("s") on the
			// ownerDocument of node."
			if (command == "strikethrough" && newValue == "line-through") {
				newParent = node.ownerDocument.createElement("s");
			}

			// "If command is "underline" and new value is "underline", let new
			// parent be the result of calling createElement("u") on the
			// ownerDocument of node."
			if (command == "underline" && newValue == "underline") {
				newParent = node.ownerDocument.createElement("u");
			}

			// "If command is "foreColor", and new value is fully opaque with red,
			// green, and blue components in the range 0 to 255:"
			if (command == "forecolor" && parseSimpleColor(newValue)) {
				// "Let new parent be the result of calling createElement("span")
				// on the ownerDocument of node."
				// NOTE: modified this process to create span elements with style attributes
				// instead of oldschool font tags with color attributes
				newParent = node.ownerDocument.createElement("span");

				// "If new value is an extended color keyword, set the color
				// attribute of new parent to new value."
				//
				// "Otherwise, set the color attribute of new parent to the result
				// of applying the rules for serializing simple color values to new
				// value (interpreted as a simple color)."
				jQuery(newParent).css('color', parseSimpleColor(newValue));
			}

			// "If command is "fontName", let new parent be the result of calling
			// createElement("font") on the ownerDocument of node, then set the
			// face attribute of new parent to new value."
			if (command == "fontname") {
				newParent = node.ownerDocument.createElement("font");
				newParent.face = newValue;
			}
		}

		// "If command is "createLink" or "unlink":"
		if (command == "createlink" || command == "unlink") {
			// "Let new parent be the result of calling createElement("a") on the
			// ownerDocument of node."
			newParent = node.ownerDocument.createElement("a");

			// "Set the href attribute of new parent to new value."
			newParent.setAttribute("href", newValue);

			// "Let ancestor be node's parent."
			var ancestor = node.parentNode;

			// "While ancestor is not null:"
			while (ancestor) {
				// "If ancestor is an a, set the tag name of ancestor to "span",
				// and let ancestor be the result."
				if (isNamedHtmlElement(ancestor, 'A')) {
					ancestor = setTagName(ancestor, "span", range);
				}

				// "Set ancestor to its parent."
				ancestor = ancestor.parentNode;
			}
		}

		// "If command is "fontSize"; and new value is one of "xx-small", "small",
		// "medium", "large", "x-large", "xx-large", or "xxx-large"; and either the
		// CSS styling flag is false, or new value is "xxx-large": let new parent
		// be the result of calling createElement("font") on the ownerDocument of
		// node, then set the size attribute of new parent to the number from the
		// following table based on new value: [table omitted]"
		if (command == "fontsize" && jQuery.inArray(newValue, ["xx-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) != -1 && (!cssStylingFlag || newValue == "xxx-large")) {
			newParent = node.ownerDocument.createElement("font");
			newParent.size = cssSizeToLegacy(newValue);
		}

		// "If command is "subscript" or "superscript" and new value is
		// "subscript", let new parent be the result of calling
		// createElement("sub") on the ownerDocument of node."
		if ((command == "subscript" || command == "superscript") && newValue == "subscript") {
			newParent = node.ownerDocument.createElement("sub");
		}

		// "If command is "subscript" or "superscript" and new value is
		// "superscript", let new parent be the result of calling
		// createElement("sup") on the ownerDocument of node."
		if ((command == "subscript" || command == "superscript") && newValue == "superscript") {
			newParent = node.ownerDocument.createElement("sup");
		}

		// "If new parent is null, let new parent be the result of calling
		// createElement("span") on the ownerDocument of node."
		if (!newParent) {
			newParent = node.ownerDocument.createElement("span");
		}

		// "Insert new parent in node's parent before node."
		node.parentNode.insertBefore(newParent, node);

		// "If the effective command value of command for new parent is not loosely
		// equivalent to new value, and the relevant CSS property for command is
		// not null, set that CSS property of new parent to new value (if the new
		// value would be valid)."
		var property = commands[command].relevantCssProperty;
		if (property !== null && !areLooselyEquivalentValues(command, getEffectiveCommandValue(newParent, command), newValue)) {
			newParent.style[property] = newValue;
		}

		// "If command is "strikethrough", and new value is "line-through", and the
		// effective command value of "strikethrough" for new parent is not
		// "line-through", set the "text-decoration" property of new parent to
		// "line-through"."
		if (command == "strikethrough" && newValue == "line-through" && getEffectiveCommandValue(newParent, "strikethrough") != "line-through") {
			newParent.style.textDecoration = "line-through";
		}

		// "If command is "underline", and new value is "underline", and the
		// effective command value of "underline" for new parent is not
		// "underline", set the "text-decoration" property of new parent to
		// "underline"."
		if (command == "underline" && newValue == "underline" && getEffectiveCommandValue(newParent, "underline") != "underline") {
			newParent.style.textDecoration = "underline";
		}

		// "Append node to new parent as its last child, preserving ranges."
		movePreservingRanges(node, newParent, newParent.childNodes.length, range);

		// "If node is an Element and the effective command value of command for
		// node is not loosely equivalent to new value:"
		if (node.nodeType == $_.Node.ELEMENT_NODE && !areEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			// "Insert node into the parent of new parent before new parent,
			// preserving ranges."
			movePreservingRanges(node, newParent.parentNode, Dom.getIndexInParent(newParent), range);

			// "Remove new parent from its parent."
			newParent.parentNode.removeChild(newParent);

			// "Let children be all children of node, omitting any that are
			// Elements whose specified command value for command is neither null
			// nor equivalent to new value."
			children = [];
			for (i = 0; i < node.childNodes.length; i++) {
				if (node.childNodes[i].nodeType == $_.Node.ELEMENT_NODE) {
					specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);

					if (specifiedValue !== null && !areEquivalentValues(command, newValue, specifiedValue)) {
						continue;
					}
				}
				children.push(node.childNodes[i]);
			}

			// "Force the value of each Node in children, with command and new
			// value as in this invocation of the algorithm."
			for (i = 0; i < children.length; i++) {
				forceValue(children[i], command, newValue, range);
			}
		}
	}

	//@}
	///// Pushing down values /////
	//@{

	function pushDownValues(node, command, newValue, range) {
		// "If node's parent is not an Element, abort this algorithm."
		if (!node.parentNode || node.parentNode.nodeType != $_.Node.ELEMENT_NODE) {
			return;
		}

		// "If the effective command value of command is loosely equivalent to new
		// value on node, abort this algorithm."
		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
			return;
		}

		// "Let current ancestor be node's parent."
		var currentAncestor = node.parentNode;

		// "Let ancestor list be a list of Nodes, initially empty."
		var ancestorList = [];

		// "While current ancestor is an editable Element and the effective command
		// value of command is not loosely equivalent to new value on it, append
		// current ancestor to ancestor list, then set current ancestor to its
		// parent."
		while (isEditable(currentAncestor) && currentAncestor.nodeType == $_.Node.ELEMENT_NODE && !areLooselyEquivalentValues(command, getEffectiveCommandValue(currentAncestor, command), newValue)) {
			ancestorList.push(currentAncestor);
			currentAncestor = currentAncestor.parentNode;
		}

		// "If ancestor list is empty, abort this algorithm."
		if (!ancestorList.length) {
			return;
		}

		// "Let propagated value be the specified command value of command on the
		// last member of ancestor list."
		var propagatedValue = getSpecifiedCommandValue(ancestorList[ancestorList.length - 1], command);

		// "If propagated value is null and is not equal to new value, abort this
		// algorithm."
		if (propagatedValue === null && propagatedValue != newValue) {
			return;
		}

		// "If the effective command value for the parent of the last member of
		// ancestor list is not loosely equivalent to new value, and new value is
		// not null, abort this algorithm."
		if (newValue !== null && !areLooselyEquivalentValues(command, getEffectiveCommandValue(ancestorList[ancestorList.length - 1].parentNode, command), newValue)) {
			return;
		}

		// "While ancestor list is not empty:"
		while (ancestorList.length) {
			// "Let current ancestor be the last member of ancestor list."
			// "Remove the last member from ancestor list."
			currentAncestor = ancestorList.pop();

			// "If the specified command value of current ancestor for command is
			// not null, set propagated value to that value."
			if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
				propagatedValue = getSpecifiedCommandValue(currentAncestor, command);
			}

			// "Let children be the children of current ancestor."
			var children = Array.prototype.slice.call(toArray(currentAncestor.childNodes));

			// "If the specified command value of current ancestor for command is
			// not null, clear the value of current ancestor."
			if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
				clearValue(currentAncestor, command, range);
			}

			// "For every child in children:"
			var i;
			for (i = 0; i < children.length; i++) {
				var child = children[i];

				// "If child is node, continue with the next child."
				if (child == node) {
					continue;
				}

				// "If child is an Element whose specified command value for
				// command is neither null nor equivalent to propagated value,
				// continue with the next child."
				if (child.nodeType == $_.Node.ELEMENT_NODE && getSpecifiedCommandValue(child, command) !== null && !areEquivalentValues(command, propagatedValue, getSpecifiedCommandValue(child, command))) {
					continue;
				}

				// "If child is the last member of ancestor list, continue with the
				// next child."
				if (child == ancestorList[ancestorList.length - 1]) {
					continue;
				}

				// "Force the value of child, with command as in this algorithm
				// and new value equal to propagated value."
				forceValue(child, command, propagatedValue, range);
			}
		}
	}

	function restoreValues(values, range) {
		// "For each (node, command, value) triple in values:"
		$_(values).forEach(function (triple) {
			var node = triple[0];
			var command = triple[1];
			var value = triple[2];

			// "Let ancestor equal node."
			var ancestor = node;

			// "If ancestor is not an Element, set it to its parent."
			if (!ancestor || ancestor.nodeType != $_.Node.ELEMENT_NODE) {
				ancestor = ancestor.parentNode;
			}

			// "While ancestor is an Element and its specified command value for
			// command is null, set it to its parent."
			while (ancestor && ancestor.nodeType == $_.Node.ELEMENT_NODE && getSpecifiedCommandValue(ancestor, command) === null) {
				ancestor = ancestor.parentNode;
			}

			// "If value is null and ancestor is an Element, push down values on
			// node for command, with new value null."
			if (value === null && ancestor && ancestor.nodeType == $_.Node.ELEMENT_NODE) {
				pushDownValues(node, command, null, range);

				// "Otherwise, if ancestor is an Element and its specified command
				// value for command is not equivalent to value, or if ancestor is not
				// an Element and value is not null, force the value of command to
				// value on node."
			} else if ((ancestor && ancestor.nodeType == $_.Node.ELEMENT_NODE && !areEquivalentValues(command, getSpecifiedCommandValue(ancestor, command), value)) || ((!ancestor || ancestor.nodeType != $_.Node.ELEMENT_NODE) && value !== null)) {
				forceValue(node, command, value, range);
			}
		});
	}

	//@}
	///// Setting the selection's value /////
	//@{

	function setSelectionValue(command, newValue, range) {

		// Use current selected range if no range passed
		range = range || getActiveRange();

		// "If there is no editable text node effectively contained in the active
		// range:"
		if (!$_(getAllEffectivelyContainedNodes(range)).filter(function (node) { return node.nodeType == $_.Node.TEXT_NODE; }, true).some(isEditable)) {
			// "If command has inline command activated values, set the state
			// override to true if new value is among them and false if it's not."
			if (commands[command].hasOwnProperty("inlineCommandActivatedValues")) {
				setStateOverride(
					command,
					$_(commands[command].inlineCommandActivatedValues).indexOf(newValue) != -1,
					range
				);
			}

			// "If command is "subscript", unset the state override for
			// "superscript"."
			if (command == "subscript") {
				unsetStateOverride("superscript", range);
			}

			// "If command is "superscript", unset the state override for
			// "subscript"."
			if (command == "superscript") {
				unsetStateOverride("subscript", range);
			}

			// "If new value is null, unset the value override (if any)."
			if (newValue === null) {
				unsetValueOverride(command, range);

				// "Otherwise, if command has a value specified, set the value override
				// to new value."
			} else if (commands[command].hasOwnProperty("value")) {
				setValueOverride(command, newValue, range);
			}

			// "Abort these steps."
			return;
		}

		// "If the active range's start node is an editable Text node, and its
		// start offset is neither zero nor its start node's length, call
		// splitText() on the active range's start node, with argument equal to the
		// active range's start offset. Then set the active range's start node to
		// the result, and its start offset to zero."
		if (isEditable(range.startContainer) && range.startContainer.nodeType == $_.Node.TEXT_NODE && range.startOffset != 0 && range.startOffset != getNodeLength(range.startContainer)) {
			// Account for browsers not following range mutation rules
			var newNode = splitText(range.startContainer, range.startOffset);
			var newActiveRange = Aloha.createRange();
			if (range.startContainer == range.endContainer) {
				var newEndOffset = range.endOffset - range.startOffset;
				newActiveRange.setEnd(newNode, newEndOffset);
				range.setEnd(newNode, newEndOffset);
			}
			newActiveRange.setStart(newNode, 0);
			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(newActiveRange);

			range.setStart(newNode, 0);
		}

		// "If the active range's end node is an editable Text node, and its end
		// offset is neither zero nor its end node's length, call splitText() on
		// the active range's end node, with argument equal to the active range's
		// end offset."
		if (isEditable(range.endContainer) && range.endContainer.nodeType == $_.Node.TEXT_NODE && range.endOffset != 0 && range.endOffset != getNodeLength(range.endContainer)) {
			// IE seems to mutate the range incorrectly here, so we need correction
			// here as well.  The active range will be temporarily in orphaned
			// nodes, so calling getActiveRange() after splitText() but before
			// fixing the range will throw an exception.
			// TODO: check if this is still neccessary
			var activeRange = range;
			var newStart = [activeRange.startContainer, activeRange.startOffset];
			var newEnd = [activeRange.endContainer, activeRange.endOffset];
			splitText(activeRange.endContainer, activeRange.endOffset);
			activeRange.setStart(newStart[0], newStart[1]);
			activeRange.setEnd(newEnd[0], newEnd[1]);

			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(activeRange);
		}

		// "Let element list be all editable Elements effectively contained in the
		// active range.
		//
		// "For each element in element list, clear the value of element."
		$_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
			return isEditable(node) && node.nodeType == $_.Node.ELEMENT_NODE;
		})).forEach(function (element) {
			clearValue(element, command, range);
		});

		// "Let node list be all editable nodes effectively contained in the active
		// range.
		//
		// "For each node in node list:"
		$_(getAllEffectivelyContainedNodes(range, isEditable)).forEach(function (node) {
			// "Push down values on node."
			pushDownValues(node, command, newValue, range);

			// "Force the value of node."
			forceValue(node, command, newValue, range);
		});
	}

	/**
	 * attempt to retrieve a block like a table or an Aloha Block
	 * which is located one step right of the current caret position.
	 * If an appropriate element is found it will be returned or
	 * false otherwise
	 *
	 * @param {element} node current node we're in
	 * @param {number} offset current offset within that node
	 *
	 * @return the dom node if found or false if no appropriate
	 * element was found
	 */
	function getBlockAtNextPosition(node, offset) {
		var i;

		// if we're inside a text node we first have to check
		// if there is nothing but tabs, newlines or the like
		// after our current cursor position
		if (node.nodeType === $_.Node.TEXT_NODE && offset < node.length) {
			for (i = offset; i < node.length; i++) {
				if ((node.data.charAt(i) !== '\t' && node.data.charAt(i) !== '\r' && node.data.charAt(i) !== '\n') || node.data.charCodeAt(i) === 160) { // &nbsp;
					// this is a character that has to be deleted first
					return false;
				}
			}
		}

		// try the most simple approach first: the next sibling
		// is a table
		if (node.nextSibling && node.nextSibling.className && node.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.nextSibling;
		}

		// since we got only ignorable whitespace here determine if
		// our nodes parents next sibling is a table
		if (node.parentNode && node.parentNode.nextSibling && node.parentNode.nextSibling.className && node.parentNode.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.parentNode.nextSibling;
		}

		// our parents nextsibling is a pure whitespace node such as
		// generated by sourcecode indentation so we'll check for
		// the next next sibling
		if (node.parentNode && node.parentNode.nextSibling && isWhitespaceNode(node.parentNode.nextSibling) && node.parentNode.nextSibling.nextSibling && node.parentNode.nextSibling.nextSibling.className && node.parentNode.nextSibling.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.parentNode.nextSibling.nextSibling;
		}

		// Note: the search above works for tables, since they cannot be
		// nested deeply in paragraphs and other formatting tags. If this code
		// is extended to work also for other blocks, the search probably needs to be adapted
	}

	/**
	 * Attempt to retrieve a block like a table or an Aloha Block
	 * which is located right before the current position.
	 * If an appropriate element is found, it will be returned or
	 * false otherwise
	 *
	 * @param {element} node current node
	 * @param {offset} offset current offset
	 *
	 * @return dom node of found or false if no appropriate
	 * element was found
	 */
	function getBlockAtPreviousPosition(node, offset) {
		var i;

		if (node.nodeType === $_.Node.TEXT_NODE && offset > 0) {
			for (i = offset - 1; i >= 0; i--) {
				if ((node.data.charAt(i) !== '\t' && node.data.charAt(i) !== '\r' && node.data.charAt(i) !== '\n') || node.data.charCodeAt(i) === 160) { // &nbsp;
					// this is a character that has to be deleted first
					return false;
				}
			}
		}

		// try the previous sibling
		if (node.previousSibling && node.previousSibling.className && node.previousSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.previousSibling;
		}

		// try the parent's previous sibling
		if (node.parentNode && node.parentNode.previousSibling && node.parentNode.previousSibling.className && node.parentNode.previousSibling.className.indexOf("aloha-table-wrapper") >= 0) {
			return node.parentNode.previousSibling;
		}

		// the parent's previous sibling might be a whitespace node
		if (node.parentNode && node.parentNode.previousSibling && isWhitespaceNode(node.parentNode.previousSibling) && node.parentNode.previousSibling.previousSibling && node.parentNode.previousSibling.previousSibling.className && node.parentNode.previousSibling.previousSibling.className.indexOf('aloha-table-wrapper') >= 0) {
			return node.parentNode.previousSibling.previousSibling;
		}

		// Note: the search above works for tables, since they cannot be
		// nested deeply in paragraphs and other formatting tags. If this code
		// is extended to work also for other blocks, the search probably needs to be adapted

		return false;
	}

	// "A boundary point (node, offset) is a block start point if either node's
	// parent is null and offset is zero; or node has a child with index offset −
	// 1, and that child is either a visible block node or a visible br."
	function isBlockStartPoint(node, offset) {
		return (!node.parentNode && offset == 0) || (0 <= offset - 1 && offset - 1 < node.childNodes.length && isVisible(node.childNodes[offset - 1]) && (isBlockNode(node.childNodes[offset - 1]) || isNamedHtmlElement(node.childNodes[offset - 1], "br")));
	}

	// "A boundary point (node, offset) is a block end point if either node's
	// parent is null and offset is node's length; or node has a child with index
	// offset, and that child is a visible block node."
	function isBlockEndPoint(node, offset) {
		return (!node.parentNode && offset == getNodeLength(node)) || (offset < node.childNodes.length && isVisible(node.childNodes[offset]) && isBlockNode(node.childNodes[offset]));
	}

	// "A boundary point is a block boundary point if it is either a block start
	// point or a block end point."
	function isBlockBoundaryPoint(node, offset) {
		return isBlockStartPoint(node, offset) || isBlockEndPoint(node, offset);
	}

	function followsLineBreak(node) {
		// "Let offset be zero."
		var offset = 0;

		// "While (node, offset) is not a block boundary point:"
		while (!isBlockBoundaryPoint(node, offset)) {
			// "If node has a visible child with index offset minus one, return
			// false."
			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isVisible(node.childNodes[offset - 1])) {
				return false;
			}

			// "If offset is zero or node has no children, set offset to node's
			// index, then set node to its parent."
			if (offset == 0 || !node.hasChildNodes()) {
				offset = Dom.getIndexInParent(node);
				node = node.parentNode;

				// "Otherwise, set node to its child with index offset minus one, then
				// set offset to node's length."
			} else {
				node = node.childNodes[offset - 1];
				offset = getNodeLength(node);
			}
		}

		// "Return true."
		return true;
	}

	function precedesLineBreak(node) {
		// "Let offset be node's length."
		var offset = getNodeLength(node);

		// "While (node, offset) is not a block boundary point:"
		while (!isBlockBoundaryPoint(node, offset)) {
			// "If node has a visible child with index offset, return false."
			if (offset < node.childNodes.length && isVisible(node.childNodes[offset])) {
				return false;
			}

			// "If offset is node's length or node has no children, set offset to
			// one plus node's index, then set node to its parent."
			if (offset == getNodeLength(node) || !node.hasChildNodes()) {
				offset = 1 + Dom.getIndexInParent(node);
				node = node.parentNode;

				// "Otherwise, set node to its child with index offset and set offset
				// to zero."
			} else {
				node = node.childNodes[offset];
				offset = 0;
			}
		}

		// "Return true."
		return true;
	}

	//@}
	///// Splitting a node list's parent /////
	//@{

	function splitParent(nodeList, range) {
		var i;

		// "Let original parent be the parent of the first member of node list."
		var originalParent = nodeList[0].parentNode;

		// "If original parent is not editable or its parent is null, do nothing
		// and abort these steps."
		if (!isEditable(originalParent) || !originalParent.parentNode) {
			return;
		}

		// "If the first child of original parent is in node list, remove
		// extraneous line breaks before original parent."
		if (jQuery.inArray(originalParent.firstChild, nodeList) != -1) {
			removeExtraneousLineBreaksBefore(originalParent);
		}

		var firstChildInNodeList = jQuery.inArray(originalParent.firstChild, nodeList) != -1;
		var lastChildInNodeList = jQuery.inArray(originalParent.lastChild, nodeList) != -1;

		// "If the first child of original parent is in node list, and original
		// parent follows a line break, set follows line break to true. Otherwise,
		// set follows line break to false."
		var followsLineBreak_ = firstChildInNodeList && followsLineBreak(originalParent);

		// "If the last child of original parent is in node list, and original
		// parent precedes a line break, set precedes line break to true.
		// Otherwise, set precedes line break to false."
		var precedesLineBreak_ = lastChildInNodeList && precedesLineBreak(originalParent);

		// "If the first child of original parent is not in node list, but its last
		// child is:"
		if (!firstChildInNodeList && lastChildInNodeList) {
			// "For each node in node list, in reverse order, insert node into the
			// parent of original parent immediately after original parent,
			// preserving ranges."
			for (i = nodeList.length - 1; i >= 0; i--) {
				movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + Dom.getIndexInParent(originalParent), range);
			}

			// "If precedes line break is true, and the last member of node list
			// does not precede a line break, call createElement("br") on the
			// context object and insert the result immediately after the last
			// member of node list."
			if (precedesLineBreak_ && !precedesLineBreak(nodeList[nodeList.length - 1])) {
				nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
			}

			// "Remove extraneous line breaks at the end of original parent."
			removeExtraneousLineBreaksAtTheEndOf(originalParent);

			// "Abort these steps."
			return;
		}

		// "If the first child of original parent is not in node list:"
		if (!firstChildInNodeList) {
			// "Let cloned parent be the result of calling cloneNode(false) on
			// original parent."
			var clonedParent = originalParent.cloneNode(false);

			// "If original parent has an id attribute, unset it."
			originalParent.removeAttribute("id");

			// "Insert cloned parent into the parent of original parent immediately
			// before original parent."
			originalParent.parentNode.insertBefore(clonedParent, originalParent);

			// "While the previousSibling of the first member of node list is not
			// null, append the first child of original parent as the last child of
			// cloned parent, preserving ranges."
			while (nodeList[0].previousSibling) {
				movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length, range);
			}
		}

		// "For each node in node list, insert node into the parent of original
		// parent immediately before original parent, preserving ranges."
		for (i = 0; i < nodeList.length; i++) {
			movePreservingRanges(nodeList[i], originalParent.parentNode, Dom.getIndexInParent(originalParent), range);
		}

		// "If follows line break is true, and the first member of node list does
		// not follow a line break, call createElement("br") on the context object
		// and insert the result immediately before the first member of node list."
		if (followsLineBreak_ && !followsLineBreak(nodeList[0])) {
			nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);
		}

		// "If the last member of node list is an inline node other than a br, and
		// the first child of original parent is a br, and original parent is not
		// an inline node, remove the first child of original parent from original
		// parent."
		if (isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "br") && isNamedHtmlElement(originalParent.firstChild, "br") && !isInlineNode(originalParent)) {
			originalParent.removeChild(originalParent.firstChild);
		}

		// "If original parent has no children:"
		if (!originalParent.hasChildNodes()) {
			// if the current range is collapsed and at the end of the originalParent.parentNode
			// the offset will not be available anymore after the next step (remove child)
			// that's why we need to fix the range to prevent a bogus offset
			if (originalParent.parentNode === range.startContainer && originalParent.parentNode === range.endContainer && range.startContainer === range.endContainer && range.startOffset === range.endOffset && originalParent.parentNode.childNodes.length === range.startOffset) {
				range.startOffset = originalParent.parentNode.childNodes.length - 1;
				range.endOffset = range.startOffset;
			}

			// "Remove original parent from its parent."
			originalParent.parentNode.removeChild(originalParent);

			// "If precedes line break is true, and the last member of node list
			// does not precede a line break, call createElement("br") on the
			// context object and insert the result immediately after the last
			// member of node list."
			if (precedesLineBreak_ && !precedesLineBreak(nodeList[nodeList.length - 1])) {
				nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
			}

			// "Otherwise, remove extraneous line breaks before original parent."
		} else {
			removeExtraneousLineBreaksBefore(originalParent);
		}

		// "If node list's last member's nextSibling is null, but its parent is not
		// null, remove extraneous line breaks at the end of node list's last
		// member's parent."
		if (!nodeList[nodeList.length - 1].nextSibling && nodeList[nodeList.length - 1].parentNode) {
			removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
		}
	}

	//@}
	///// The backColor command /////
	//@{
	commands.backcolor = {
		// Copy-pasted, same as hiliteColor
		action: function (value, range) {
			// Action is further copy-pasted, same as foreColor

			// "If value is not a valid CSS color, prepend "#" to it."
			//
			// "If value is still not a valid CSS color, or if it is currentColor,
			// abort these steps and do nothing."
			//
			// Cheap hack for testing, no attempt to be comprehensive.
			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
				value = "#" + value;
			}
			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("backcolor", value, range);
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "backgroundColor",
		equivalentValues: function (val1, val2) {
			// "Either both strings are valid CSS colors and have the same red,
			// green, blue, and alpha components, or neither string is a valid CSS
			// color."
			return normalizeColor(val1) === normalizeColor(val2);
		}
	};

	//@}
	///// The bold command /////
	//@{
	commands.bold = {
		action: function (value, range) {
			// "If queryCommandState("bold") returns true, set the selection's
			// value to "normal". Otherwise set the selection's value to "bold"."
			if (myQueryCommandState("bold", range)) {
				setSelectionValue("bold", "normal", range);
			} else {
				setSelectionValue("bold", "bold", range);
			}
		},
		inlineCommandActivatedValues: ["bold", "600", "700", "800", "900"],
		relevantCssProperty: "fontWeight",
		equivalentValues: function (val1, val2) {
			// "Either the two strings are equal, or one is "bold" and the other is
			// "700", or one is "normal" and the other is "400"."
			return val1 == val2 || (val1 == "bold" && val2 == "700") || (val1 == "700" && val2 == "bold") || (val1 == "normal" && val2 == "400") || (val1 == "400" && val2 == "normal");
		}
	};

	//@}
	///// The createLink command /////
	//@{
	commands.createlink = {
		action: function (value, range) {
			// "If value is the empty string, abort these steps and do nothing."
			if (value === "") {
				return;
			}

			// "For each editable a element that has an href attribute and is an
			// ancestor of some node effectively contained in the active range, set
			// that a element's href attribute to value."
			//
			// TODO: We don't actually do this in tree order, not that it matters
			// unless you're spying with mutation events.
			$_(getAllEffectivelyContainedNodes(getActiveRange())).forEach(function (node) {
				$_(getAncestors(node)).forEach(function (ancestor) {
					if (isEditable(ancestor) && isNamedHtmlElement(ancestor, 'a') && hasAttribute(ancestor, "href")) {
						ancestor.setAttribute("href", value);
					}
				});
			});

			// "Set the selection's value to value."
			setSelectionValue("createlink", value, range);
		},
		standardInlineValueCommand: true
	};

	//@}
	///// The fontName command /////
	//@{
	commands.fontname = {
		action: function (value, range) {
			// "Set the selection's value to value."
			setSelectionValue("fontname", value, range);
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "fontFamily"
	};

	//@}
	///// The fontSize command /////
	//@{

	commands.fontsize = {
		action: function (value, range) {
			// "If value is the empty string, abort these steps and do nothing."
			if (value === "") {
				return;
			}

			value = normalizeFontSize(value);

			// "If value is not one of the strings "xx-small", "x-small", "small",
			// "medium", "large", "x-large", "xx-large", "xxx-large", and is not a
			// valid CSS absolute length, then abort these steps and do nothing."
			//
			// More cheap hacks to skip valid CSS absolute length checks.
			if (jQuery.inArray(value, ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) == -1 && !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc)$/.test(value)) {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("fontsize", value, range);
		},
		indeterm: function () {
			// "True if among editable Text nodes that are effectively contained in
			// the active range, there are two that have distinct effective command
			// values.  Otherwise false."
			return $_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			})).map(function (node) {
				return getEffectiveCommandValue(node, "fontsize");
			}, true).filter(function (value, i, arr) {
				return $_(arr.slice(0, i)).indexOf(value) == -1;
			}).length >= 2;
		},
		value: function (range) {
			// "Let pixel size be the effective command value of the first editable
			// Text node that is effectively contained in the active range, or if
			// there is no such node, the effective command value of the active
			// range's start node, in either case interpreted as a number of
			// pixels."
			var node = getAllEffectivelyContainedNodes(range, function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			})[0];
			if (node === undefined) {
				node = range.startContainer;
			}
			var pixelSize = getEffectiveCommandValue(node, "fontsize");

			// "Return the legacy font size for pixel size."
			return getLegacyFontSize(pixelSize);
		},
		relevantCssProperty: "fontSize"
	};

	//@}
	///// The foreColor command /////
	//@{
	commands.forecolor = {
		action: function (value, range) {
			// Copy-pasted, same as backColor and hiliteColor

			// "If value is not a valid CSS color, prepend "#" to it."
			//
			// "If value is still not a valid CSS color, or if it is currentColor,
			// abort these steps and do nothing."
			//
			// Cheap hack for testing, no attempt to be comprehensive.
			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
				value = "#" + value;
			}
			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("forecolor", value, range);
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "color",
		equivalentValues: function (val1, val2) {
			// "Either both strings are valid CSS colors and have the same red,
			// green, blue, and alpha components, or neither string is a valid CSS
			// color."
			return normalizeColor(val1) === normalizeColor(val2);
		}
	};

	//@}
	///// The hiliteColor command /////
	//@{
	commands.hilitecolor = {
		// Copy-pasted, same as backColor
		action: function (value, range) {
			// Action is further copy-pasted, same as foreColor

			// "If value is not a valid CSS color, prepend "#" to it."
			//
			// "If value is still not a valid CSS color, or if it is currentColor,
			// abort these steps and do nothing."
			//
			// Cheap hack for testing, no attempt to be comprehensive.
			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
				value = "#" + value;
			}
			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
				return;
			}

			// "Set the selection's value to value."
			setSelectionValue("hilitecolor", value, range);
		},
		indeterm: function () {
			// "True if among editable Text nodes that are effectively contained in
			// the active range, there are two that have distinct effective command
			// values.  Otherwise false."
			return $_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			})).map(function (node) {
				return getEffectiveCommandValue(node, "hilitecolor");
			}, true).filter(function (value, i, arr) {
				return $_(arr.slice(0, i)).indexOf(value) == -1;
			}).length >= 2;
		},
		standardInlineValueCommand: true,
		relevantCssProperty: "backgroundColor",
		equivalentValues: function (val1, val2) {
			// "Either both strings are valid CSS colors and have the same red,
			// green, blue, and alpha components, or neither string is a valid CSS
			// color."
			return normalizeColor(val1) === normalizeColor(val2);
		}
	};

	//@}
	///// The italic command /////
	//@{
	commands.italic = {
		action: function (value, range) {
			// "If queryCommandState("italic") returns true, set the selection's
			// value to "normal". Otherwise set the selection's value to "italic"."
			if (myQueryCommandState("italic", range)) {
				setSelectionValue("italic", "normal", range);
			} else {
				setSelectionValue("italic", "italic", range);
			}
		},
		inlineCommandActivatedValues: ["italic", "oblique"],
		relevantCssProperty: "fontStyle"
	};

	//@}
	///// The removeFormat command /////
	//@{
	commands.removeformat = {
		action: function (value, range) {
			var newEnd, newStart, newNode;

			// "A removeFormat candidate is an editable HTML element with local
			// name "abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite",
			// "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q",
			// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
			// "tt", "u", or "var"."
			function isRemoveFormatCandidate(node) {
				return isEditable(node) && isHtmlElementInArray(node, ["abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite", "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q", "s", "samp", "small", "span", "strike", "strong", "sub", "sup", "tt", "u", "var"]);
			}

			// "Let elements to remove be a list of every removeFormat candidate
			// effectively contained in the active range."
			var elementsToRemove = getAllEffectivelyContainedNodes(getActiveRange(), isRemoveFormatCandidate);

			// "For each element in elements to remove:"
			$_(elementsToRemove).forEach(function (element) {
				// "While element has children, insert the first child of element
				// into the parent of element immediately before element,
				// preserving ranges."
				while (element.hasChildNodes()) {
					movePreservingRanges(element.firstChild, element.parentNode, Dom.getIndexInParent(element), getActiveRange());
				}

				// "Remove element from its parent."
				element.parentNode.removeChild(element);
			});

			// "If the active range's start node is an editable Text node, and its
			// start offset is neither zero nor its start node's length, call
			// splitText() on the active range's start node, with argument equal to
			// the active range's start offset. Then set the active range's start
			// node to the result, and its start offset to zero."
			if (isEditable(getActiveRange().startContainer) && getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().startOffset != 0 && getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {
				// Account for browsers not following range mutation rules
				if (getActiveRange().startContainer == getActiveRange().endContainer) {
					newEnd = getActiveRange().endOffset - getActiveRange().startOffset;
					newNode = splitText(getActiveRange().startContainer, getActiveRange().startOffset);
					getActiveRange().setStart(newNode, 0);
					getActiveRange().setEnd(newNode, newEnd);
				} else {
					getActiveRange().setStart(splitText(getActiveRange().startContainer, getActiveRange().startOffset), 0);
				}
			}

			// "If the active range's end node is an editable Text node, and its
			// end offset is neither zero nor its end node's length, call
			// splitText() on the active range's end node, with argument equal to
			// the active range's end offset."
			if (isEditable(getActiveRange().endContainer) && getActiveRange().endContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().endOffset != 0 && getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {
				// IE seems to mutate the range incorrectly here, so we need
				// correction here as well.  Have to be careful to set the range to
				// something not including the text node so that getActiveRange()
				// doesn't throw an exception due to a temporarily detached
				// endpoint.
				newStart = [getActiveRange().startContainer, getActiveRange().startOffset];
				newEnd = [getActiveRange().endContainer, getActiveRange().endOffset];
				getActiveRange().setEnd(document.documentElement, 0);
				splitText(newEnd[0], newEnd[1]);
				getActiveRange().setStart(newStart[0], newStart[1]);
				getActiveRange().setEnd(newEnd[0], newEnd[1]);
			}

			// "Let node list consist of all editable nodes effectively contained
			// in the active range."
			//
			// "For each node in node list, while node's parent is a removeFormat
			// candidate in the same editing host as node, split the parent of the
			// one-node list consisting of node."
			$_(getAllEffectivelyContainedNodes(getActiveRange(), isEditable)).forEach(function (node) {
				while (isRemoveFormatCandidate(node.parentNode) && inSameEditingHost(node.parentNode, node)) {
					splitParent([node], getActiveRange());
				}
			});

			// "For each of the entries in the following list, in the given order,
			// set the selection's value to null, with command as given."
			$_(["subscript", "bold", "fontname", "fontsize", "forecolor", "hilitecolor", "italic", "strikethrough", "underline"]).forEach(function (command) {
				setSelectionValue(command, null, range);
			});
		}
	};

	//@}
	///// The strikethrough command /////
	//@{
	commands.strikethrough = {
		action: function (value, range) {
			// "If queryCommandState("strikethrough") returns true, set the
			// selection's value to null. Otherwise set the selection's value to
			// "line-through"."
			if (myQueryCommandState("strikethrough", range)) {
				setSelectionValue("strikethrough", null, range);
			} else {
				setSelectionValue("strikethrough", "line-through", range);
			}
		},
		inlineCommandActivatedValues: ["line-through"]
	};

	//@}
	///// The subscript command /////
	//@{
	commands.subscript = {
		action: function (value, range) {
			// "Call queryCommandState("subscript"), and let state be the result."
			var state = myQueryCommandState("subscript", range);

			// "Set the selection's value to null."
			setSelectionValue("subscript", null, range);

			// "If state is false, set the selection's value to "subscript"."
			if (!state) {
				setSelectionValue("subscript", "subscript", range);
			}
		},
		indeterm: function () {
			// "True if either among editable Text nodes that are effectively
			// contained in the active range, there is at least one with effective
			// command value "subscript" and at least one with some other effective
			// command value; or if there is some editable Text node effectively
			// contained in the active range with effective command value "mixed".
			// Otherwise false."
			var nodes = getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
				return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
			});
			return (($_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") == "subscript"; })
					 && $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") != "subscript"; }))
					|| $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") == "mixed"; }));
		},
		inlineCommandActivatedValues: ["subscript"]
	};

	//@}
	///// The superscript command /////
	//@{
	commands.superscript = {
		action: function (value, range) {
			// "Call queryCommandState("superscript"), and let state be the
			// result."
			var state = myQueryCommandState("superscript", range);

			// "Set the selection's value to null."
			setSelectionValue("superscript", null, range);

			// "If state is false, set the selection's value to "superscript"."
			if (!state) {
				setSelectionValue("superscript", "superscript", range);
			}
		},
		indeterm: function () {
			// "True if either among editable Text nodes that are effectively
			// contained in the active range, there is at least one with effective
			// command value "superscript" and at least one with some other
			// effective command value; or if there is some editable Text node
			// effectively contained in the active range with effective command
			// value "mixed".  Otherwise false."
			var nodes = getAllEffectivelyContainedNodes(
				getActiveRange(),
				function (node) {
					return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
				}
			);
			return (($_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") == "superscript"; })
					 && $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") != "superscript"; }))
					|| $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") == "mixed"; }));
		},
		inlineCommandActivatedValues: ["superscript"]
	};

	//@}
	///// The underline command /////
	//@{
	commands.underline = {
		action: function (value, range) {
			// "If queryCommandState("underline") returns true, set the selection's
			// value to null. Otherwise set the selection's value to "underline"."
			if (myQueryCommandState("underline", range)) {
				setSelectionValue("underline", null, range);
			} else {
				setSelectionValue("underline", "underline", range);
			}
		},
		inlineCommandActivatedValues: ["underline"]
	};

	//@}
	///// The unlink command /////
	//@{
	commands.unlink = {
		action: function () {
			// "Let hyperlinks be a list of every a element that has an href
			// attribute and is contained in the active range or is an ancestor of
			// one of its boundary points."
			//
			// As usual, take care to ensure it's tree order.  The correctness of
			// the following is left as an exercise for the reader.
			var range = getActiveRange();
			var hyperlinks = [];
			var node;
			for (node = range.startContainer; node; node = node.parentNode) {
				if (isNamedHtmlElement(node, 'A') && hasAttribute(node, "href")) {
					hyperlinks.unshift(node);
				}
			}
			for (node = range.startContainer; node != nextNodeDescendants(range.endContainer); node = nextNode(node)) {
				if (isNamedHtmlElement(node, 'A') && hasAttribute(node, "href") && (isContained(node, range) || isAncestor(node, range.endContainer) || node == range.endContainer)) {
					hyperlinks.push(node);
				}
			}

			// "Clear the value of each member of hyperlinks."
			var i;
			for (i = 0; i < hyperlinks.length; i++) {
				clearValue(hyperlinks[i], "unlink", range);
			}
		},
		standardInlineValueCommand: true
	};

	//@}

	/////////////////////////////////////
	///// Block formatting commands /////
	/////////////////////////////////////

	///// Block formatting command definitions /////
	//@{

	// "An indentation element is either a blockquote, or a div that has a style
	// attribute that sets "margin" or some subproperty of it."
	function isIndentationElement(node) {
		// Handling of indentation elements while deleting is somehow broken (pressing backspace
		// in blockquotes wraps the blockquote into a div, ...)
		// therefore for now, we pretend that indentation elements do not exist at all.
		return false;
	}

	// "A simple indentation element is an indentation element that has no
	// attributes other than one or more of
	//
	//   * "a style attribute that sets no properties other than "margin", "border",
	//     "padding", or subproperties of those;
	//   * "a class attribute;
	//   * "a dir attribute."
	function isSimpleIndentationElement(node) {
		if (!isIndentationElement(node)) {
			return false;
		}

		if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
			return false;
		}

		var i;
		for (i = 0; i < node.attributes.length; i++) {
			if (!isHtmlNamespace(node.attributes[i].namespaceURI) || jQuery.inArray(node.attributes[i].name, ["style", "class", "dir"]) == -1) {
				return false;
			}
		}

		if (typeof node.style.length !== 'undefined') {
			for (i = 0; i < node.style.length; i++) {
				// This is approximate, but it works well enough for my purposes.
				if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
					return false;
				}
			}
		} else {
			var s;
			/*jslint forin: true*/ //not sure whether node.style.hasOwnProperty is valid
			for (s in node.style) {
				// This is approximate, but it works well enough for my purposes.
				if (!/^(-[a-z]+-)?(margin|border|padding)/.test(s) && node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
					return false;
				}
			}
			/*jslint forin: false*/
		}

		return true;
	}

	// "A non-list single-line container is an HTML element with local name
	// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre",
	// or "xmp"."
	function isNonListSingleLineContainer(node) {
		return isHtmlElementInArray(node, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre", "xmp"]);
	}

	// "A single-line container is either a non-list single-line container, or an
	// HTML element with local name "li", "dt", or "dd"."
	function isSingleLineContainer(node) {
		return isNonListSingleLineContainer(node) || isHtmlElementInArray(node, ["li", "dt", "dd"]);
	}

	// "The default single-line container name is "p"."
	var defaultSingleLineContainerName = "p";

	//@}
	///// Check whether the given element is an end break /////
	//@{
	function isEndBreak(element) {
		return (isNamedHtmlElement(element, 'br') && element.parentNode.lastChild === element);
	}

	//@}
	///// Create an end break /////
	//@{
	function createEndBreak() {
		return document.createElement("br");
	}

	/**
	 * Ensure the container is editable
	 * E.g. when called for an empty paragraph or header, and the browser is not IE,
	 * we need to append a br (marked with class aloha-end-br)
	 * For IE7, there is a special behaviour that will append zero-width whitespace
	 * @param {DOMNode} container
	 */
	function ensureContainerEditable(container) {
		if (!container) {
			return;
		}

		// Because it is useful to be able to completely empty the contents of
		// an editing host during editing.  So long as the container's
		// contenteditable attribute is "true" (as is the case during editing),
		// the element will be rendered visibly in all browsers.  This fact
		// allows us to not have to prop up the container with a <br> in order
		// to keep it accessible to the editor.
		if (isEditingHost(container)) {
			return;
		}

		if (isNamedHtmlElement(container.lastChild, "br")) {
			return;
		}

		if ($_(container.childNodes).some(isVisible)) {
			return;
		}

		if (!jQuery.browser.msie) {
			// for normal browsers, the end-br will do
			container.appendChild(createEndBreak());
		} else if (jQuery.browser.msie && jQuery.browser.version <= 7 && isHtmlElementInArray(container, ["p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "blockquote"])) {
			// for IE7, we need to insert a text node containing a single zero-width whitespace character
			if (!container.firstChild) {
				container.appendChild(document.createTextNode('\u200b'));
			}
		}
	}

	//@}
	///// Assorted block formatting command algorithms /////
	//@{

	function fixDisallowedAncestors(node, range) {
		var i;

		// "If node is not editable, abort these steps."
		if (!isEditable(node)) {
			return;
		}

		// "If node is not an allowed child of any of its ancestors in the same
		// editing host, and is not an HTML element with local name equal to the
		// default single-line container name:"
		if ($_(getAncestors(node)).every(function (ancestor) { return !inSameEditingHost(node, ancestor) || !isAllowedChild(node, ancestor); })
			    && !isHtmlElement_obsolete(node, defaultSingleLineContainerName)) {
			// "If node is a dd or dt, wrap the one-node list consisting of node,
			// with sibling criteria returning true for any dl with no attributes
			// and false otherwise, and new parent instructions returning the
			// result of calling createElement("dl") on the context object. Then
			// abort these steps."
			if (isHtmlElementInArray(node, ["dd", "dt"])) {
				wrap(
					[node],
					function (sibling) {
						return isNamedHtmlElement(sibling, 'dl') && !sibling.attributes.length;
					},
					function () {
						return document.createElement("dl");
					},
					range
				);
				return;
			}

			// "If node is not a prohibited paragraph child, abort these steps."
			if (!isProhibitedParagraphChild(node)) {
				return;
			}

			// "Set the tag name of node to the default single-line container name,
			// and let node be the result."
			node = setTagName(node, defaultSingleLineContainerName, range);

			ensureContainerEditable(node);

			// "Fix disallowed ancestors of node."
			fixDisallowedAncestors(node, range);

			// "Let descendants be all descendants of node."
			var descendants = getDescendants(node);

			// "Fix disallowed ancestors of each member of descendants."
			for (i = 0; i < descendants.length; i++) {
				fixDisallowedAncestors(descendants[i], range);
			}

			// "Abort these steps."
			return;
		}

		// "Record the values of the one-node list consisting of node, and let
		// values be the result."
		var values = recordValues([node]);
		var newStartOffset, newEndOffset;

		// "While node is not an allowed child of its parent, split the parent of
		// the one-node list consisting of node."
		while (!isAllowedChild(node, node.parentNode)) {
			// If the parent contains only this node and possibly empty text nodes, we rather want to unwrap the node, instead of splitting.
			// With splitting, we would get empty nodes, like:
			// split: <p><p>foo</p></p> -> <p></p><p>foo</p> (bad)
			// unwrap: <p><p>foo</p></p> -> <p>foo</p> (good)

			// First remove empty text nodes that are children of the parent and correct the range if necessary
			// we do this to have the node being the only child of its parent, so that we can replace the parent with the node
			for (i = node.parentNode.childNodes.length - 1; i >= 0; --i) {
				if (node.parentNode.childNodes[i].nodeType == 3 && node.parentNode.childNodes[i].data.length == 0) {
					// we remove the empty text node
					node.parentNode.removeChild(node.parentNode.childNodes[i]);

					// if the range points to somewhere behind the removed text node, we reduce the offset
					if (range.startContainer == node.parentNode && range.startOffset > i) {
						range.startOffset--;
					}
					if (range.endContainer == node.parentNode && range.endOffset > i) {
						range.endOffset--;
					}
				}
			}

			// now that the parent has only the node as child (because we
			// removed any existing empty text nodes), we can safely unwrap the
			// node's contents, and correct the range if necessary
			if (node.parentNode.childNodes.length == 1) {
				newStartOffset = range.startOffset;
				newEndOffset = range.endOffset;

				if (range.startContainer === node.parentNode && range.startOffset > Dom.getIndexInParent(node)) {
					// the node (1 element) will be replaced by its contents (contents().length elements)
					newStartOffset = range.startOffset + (jQuery(node).contents().length - 1);
				}
				if (range.endContainer === node.parentNode && range.endOffset > Dom.getIndexInParent(node)) {
					// the node (1 element) will be replaced by its contents (contents().length elements)
					newEndOffset = range.endOffset + (jQuery(node).contents().length - 1);
				}
				jQuery(node).contents().unwrap();
				range.startOffset = newStartOffset;
				range.endOffset = newEndOffset;
				// after unwrapping, we are done
				break;
			} else {
				// store the original parent
				var originalParent = node.parentNode;
				splitParent([node], range);
				// check whether the parent did not change, so the split did not work, e.g.
				// because we already reached the editing host itself.
				// this situation can occur, e.g. when we insert a paragraph into an contenteditable span
				// in such cases, we just unwrap the contents of the paragraph
				if (originalParent === node.parentNode) {
					// so we unwrap now
					newStartOffset = range.startOffset;
					newEndOffset = range.endOffset;

					if (range.startContainer === node.parentNode && range.startOffset > Dom.getIndexInParent(node)) {
						// the node (1 element) will be replaced by its contents (contents().length elements)
						newStartOffset = range.startOffset + (jQuery(node).contents().length - 1);
					}
					if (range.endContainer === node.parentNode && range.endOffset > Dom.getIndexInParent(node)) {
						// the node (1 element) will be replaced by its contents (contents().length elements)
						newEndOffset = range.endOffset + (jQuery(node).contents().length - 1);
					}
					jQuery(node).contents().unwrap();
					range.startOffset = newStartOffset;
					range.endOffset = newEndOffset;
					// after unwrapping, we are done
					break;
				}
			}
		}

		// "Restore the values from values."
		restoreValues(values, range);
	}

	/**
	 * This method "normalizes" sublists of the given item (which is supposed to be a LI):
	 * If sublists are found in the LI element, they are moved directly into the outer list.
	 * @param item item
	 * @param range range, which will be modified if necessary
	 */
	function normalizeSublists(item, range) {
		// "If item is not an li or it is not editable or its parent is not
		// editable, abort these steps."
		if (!isNamedHtmlElement(item, 'LI') || !isEditable(item) || !isEditable(item.parentNode)) {
			return;
		}

		// "Let new item be null."
		var newItem = null;

		function isOlUl(node) {
			return isHtmlElementInArray(node, ["OL", "UL"]);
		}

		// "While item has an ol or ul child:"
		while ($_(item.childNodes).some(isOlUl)) {
			// "Let child be the last child of item."
			var child = item.lastChild;

			// "If child is an ol or ul, or new item is null and child is a Text
			// node whose data consists of zero of more space characters:"
			if (isHtmlElementInArray(child, ["OL", "UL"]) || (!newItem && child.nodeType == $_.Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
				// "Set new item to null."
				newItem = null;

				// "Insert child into the parent of item immediately following
				// item, preserving ranges."
				movePreservingRanges(child, item.parentNode, 1 + Dom.getIndexInParent(item), range);

				// "Otherwise:"
			} else {
				// "If new item is null, let new item be the result of calling
				// createElement("li") on the ownerDocument of item, then insert
				// new item into the parent of item immediately after item."
				if (!newItem) {
					newItem = item.ownerDocument.createElement("li");
					item.parentNode.insertBefore(newItem, item.nextSibling);
				}

				// "Insert child into new item as its first child, preserving
				// ranges."
				movePreservingRanges(child, newItem, 0, range);
			}
		}
	}

	/**
	 * This method is the exact opposite of normalizeSublists.
	 * List nodes directly nested into each other are corrected to be nested in li elements (so that the resulting lists conform the html5 specification)
	 * @param item list node
	 * @param range range, which is preserved when modifying the list
	 */
	function unNormalizeSublists(item, range) {
		// "If item is not an ol or ol or it is not editable or its parent is not
		// editable, abort these steps."
		if (!isHtmlElementInArray(item, ["OL", "UL"]) || !isEditable(item)) {
			return;
		}

		var $list = jQuery(item);
		$list.children("ol,ul").each(function (index, sublist) {
			if (isNamedHtmlElement(sublist.previousSibling, "LI")) {
				// move the sublist into the LI
				movePreservingRanges(sublist, sublist.previousSibling, sublist.previousSibling.childNodes.length, range);
			}
		});
	}

	//@}
	///// Block-extending a range /////
	//@{

	function blockExtend(range) {
		// "Let start node, start offset, end node, and end offset be the start
		// and end nodes and offsets of the range."
		var startNode = range.startContainer;
		var startOffset = range.startOffset;
		var endNode = range.endContainer;
		var endOffset = range.endOffset;

		// "If some ancestor container of start node is an li, set start offset to
		// the index of the last such li in tree order, and set start node to that
		// li's parent."
		var liAncestors = $_(getAncestors(startNode).concat(startNode)).filter(function (ancestor) { return isNamedHtmlElement(ancestor, 'li'); }).slice(-1);
		if (liAncestors.length) {
			startOffset = Dom.getIndexInParent(liAncestors[0]);
			startNode = liAncestors[0].parentNode;
		}

		// "If (start node, start offset) is not a block start point, repeat the
		// following steps:"
		if (!isBlockStartPoint(startNode, startOffset)) {
			do {
				// "If start offset is zero, set it to start node's index, then set
				// start node to its parent."
				if (startOffset == 0) {
					startOffset = Dom.getIndexInParent(startNode);
					startNode = startNode.parentNode;

					// "Otherwise, subtract one from start offset."
				} else {
					startOffset--;
				}

				// "If (start node, start offset) is a block boundary point, break from
				// this loop."
			} while (!isBlockBoundaryPoint(startNode, startOffset));
		}

		// "While start offset is zero and start node's parent is not null, set
		// start offset to start node's index, then set start node to its parent."
		while (startOffset == 0 && startNode.parentNode) {
			startOffset = Dom.getIndexInParent(startNode);
			startNode = startNode.parentNode;
		}

		// "If some ancestor container of end node is an li, set end offset to one
		// plus the index of the last such li in tree order, and set end node to
		// that li's parent."
		liAncestors = $_(getAncestors(endNode).concat(endNode)).filter(function (ancestor) { return isNamedHtmlElement(ancestor, 'li'); }).slice(-1);
		if (liAncestors.length) {
			endOffset = 1 + Dom.getIndexInParent(liAncestors[0]);
			endNode = liAncestors[0].parentNode;
		}

		// "If (end node, end offset) is not a block end point, repeat the
		// following steps:"
		if (!isBlockEndPoint(endNode, endOffset)) {
			do {
				// "If end offset is end node's length, set it to one plus end node's
				// index, then set end node to its parent."
				if (endOffset == getNodeLength(endNode)) {
					endOffset = 1 + Dom.getIndexInParent(endNode);
					endNode = endNode.parentNode;

					// "Otherwise, add one to end offset.
				} else {
					endOffset++;
				}

				// "If (end node, end offset) is a block boundary point, break from
				// this loop."
			} while (!isBlockBoundaryPoint(endNode, endOffset));
		}

		// "While end offset is end node's length and end node's parent is not
		// null, set end offset to one plus end node's index, then set end node to
		// its parent."
		while (endOffset == getNodeLength(endNode) && endNode.parentNode) {
			endOffset = 1 + Dom.getIndexInParent(endNode);
			endNode = endNode.parentNode;
		}

		// "Let new range be a new range whose start and end nodes and offsets
		// are start node, start offset, end node, and end offset."
		var newRange = Aloha.createRange();
		newRange.setStart(startNode, startOffset);
		newRange.setEnd(endNode, endOffset);

		// "Return new range."
		return newRange;
	}

	function getSelectionListState() {
		// "Block-extend the active range, and let new range be the result."
		var newRange = blockExtend(getActiveRange());

		// "Let node list be a list of nodes, initially empty."
		//
		// "For each node contained in new range, append node to node list if the
		// last member of node list (if any) is not an ancestor of node; node is
		// editable; node is not an indentation element; and node is either an ol
		// or ul, or the child of an ol or ul, or an allowed child of "li"."
		var nodeList = getContainedNodes(newRange, function (node) {
			return isEditable(node) && !isIndentationElement(node) && (isHtmlElementInArray(node, ["ol", "ul"]) || isHtmlElementInArray(node.parentNode, ["ol", "ul"]) || isAllowedChild(node, "li"));
		});

		// "If node list is empty, return "none"."
		if (!nodeList.length) {
			return "none";
		}

		// "If every member of node list is either an ol or the child of an ol or
		// the child of an li child of an ol, and none is a ul or an ancestor of a
		// ul, return "ol"."
		if ($_(nodeList).every(function (node) { return (isNamedHtmlElement(node, 'ol')
														 || isNamedHtmlElement(node.parentNode, "ol")
														 || (isNamedHtmlElement(node.parentNode, "li")
															 && isNamedHtmlElement(node.parentNode.parentNode, "ol"))); })
			    && !$_(nodeList).some(function (node) { return isNamedHtmlElement(node, 'ul') || (node.querySelector && node.querySelector("ul")); })) {
			return "ol";
		}

		// "If every member of node list is either a ul or the child of a ul or the
		// child of an li child of a ul, and none is an ol or an ancestor of an ol,
		// return "ul"."
		if ($_(nodeList).every(function (node) { return (isNamedHtmlElement(node, 'ul')
														 || isNamedHtmlElement(node.parentNode, "ul")
														 || (isNamedHtmlElement(node.parentNode, "li")
															 && isNamedHtmlElement(node.parentNode.parentNode, "ul"))); })
			    && !$_(nodeList).some(function (node) { return isNamedHtmlElement(node, 'ol') || (node.querySelector && node.querySelector("ol")); })) {
			return "ul";
		}

		var hasOl = $_(nodeList).some(function (node) {
			return (isNamedHtmlElement(node, 'ol')
					|| isNamedHtmlElement(node.parentNode, "ol")
					|| (node.querySelector && node.querySelector("ol"))
					|| (isNamedHtmlElement(node.parentNode, "li")
						&& isNamedHtmlElement(node.parentNode.parentNode, "ol")));
		});
		var hasUl = $_(nodeList).some(function (node) {
			return (isNamedHtmlElement(node, 'ul')
					|| isNamedHtmlElement(node.parentNode, "ul")
					|| (node.querySelector && node.querySelector("ul"))
					|| (isNamedHtmlElement(node.parentNode, "li")
						&& isNamedHtmlElement(node.parentNode.parentNode, "ul")));
		});
		// "If some member of node list is either an ol or the child or ancestor of
		// an ol or the child of an li child of an ol, and some member of node list
		// is either a ul or the child or ancestor of a ul or the child of an li
		// child of a ul, return "mixed"."
		if (hasOl && hasUl) {
			return "mixed";
		}

		// "If some member of node list is either an ol or the child or ancestor of
		// an ol or the child of an li child of an ol, return "mixed ol"."
		if (hasOl) {
			return "mixed ol";
		}

		// "If some member of node list is either a ul or the child or ancestor of
		// a ul or the child of an li child of a ul, return "mixed ul"."
		if (hasUl) {
			return "mixed ul";
		}

		// "Return "none"."
		return "none";
	}

	function getAlignmentValue(node) {
		// "While node is neither null nor an Element, or it is an Element but its
		// "display" property has resolved value "inline" or "none", set node to
		// its parent."
		while ((node && node.nodeType != $_.Node.ELEMENT_NODE) || (node.nodeType == $_.Node.ELEMENT_NODE && jQuery.inArray($_.getComputedStyle(node).display, ["inline", "none"]) != -1)) {
			node = node.parentNode;
		}

		// "If node is not an Element, return "left"."
		if (!node || node.nodeType != $_.Node.ELEMENT_NODE) {
			return "left";
		}

		var resolvedValue = $_.getComputedStyle(node).textAlign
		// Hack around browser non-standardness
			.replace(/^-(moz|webkit)-/, "").replace(/^auto$/, "start");

		// "If node's "text-align" property has resolved value "start", return
		// "left" if the directionality of node is "ltr", "right" if it is "rtl"."
		if (resolvedValue == "start") {
			return getDirectionality(node) == "ltr" ? "left" : "right";
		}

		// "If node's "text-align" property has resolved value "end", return
		// "right" if the directionality of node is "ltr", "left" if it is "rtl"."
		if (resolvedValue == "end") {
			return getDirectionality(node) == "ltr" ? "right" : "left";
		}

		// "If node's "text-align" property has resolved value "center", "justify",
		// "left", or "right", return that value."
		if (jQuery.inArray(resolvedValue, ["center", "justify", "left", "right"]) != -1) {
			return resolvedValue;
		}

		// "Return "left"."
		return "left";
	}

	//@}
	///// Recording and restoring overrides /////
	//@{

	function recordCurrentOverrides(range) {
		// "Let overrides be a list of (string, string or boolean) ordered pairs,
		// initially empty."
		var overrides = [];

		// "If there is a value override for "createLink", add ("createLink", value
		// override for "createLink") to overrides."
		if (getValueOverride("createlink", range) !== undefined) {
			overrides.push(["createlink", getValueOverride("createlink", range)]);
		}

		// "For each command in the list "bold", "italic", "strikethrough",
		// "subscript", "superscript", "underline", in order: if there is a state
		// override for command, add (command, command's state override) to
		// overrides."
		$_(["bold", "italic", "strikethrough", "subscript", "superscript", "underline"]).forEach(function (command) {
			if (getStateOverride(command, range) !== undefined) {
				overrides.push([command, getStateOverride(command, range)]);
			}
		});

		// "For each command in the list "fontName", "fontSize", "foreColor",
		// "hiliteColor", in order: if there is a value override for command, add
		// (command, command's value override) to overrides."
		$_(["fontname", "fontsize", "forecolor", "hilitecolor"]).forEach(function (command) {
			if (getValueOverride(command, range) !== undefined) {
				overrides.push([command, getValueOverride(command, range)]);
			}
		});

		// "Return overrides."
		return overrides;
	}

	function recordCurrentStatesAndValues(range) {
		// "Let overrides be a list of (string, string or boolean) ordered pairs,
		// initially empty."
		var overrides = [];

		// "Let node be the first editable Text node effectively contained in the
		// active range, or null if there is none."
		var node = $_(getAllEffectivelyContainedNodes(range)).filter(function (node) {
			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
		})[0];

		// "If node is null, return overrides."
		if (!node) {
			return overrides;
		}

		// "Add ("createLink", value for "createLink") to overrides."
		overrides.push(["createlink", commands.createlink.value(range)]);

		// "For each command in the list "bold", "italic", "strikethrough",
		// "subscript", "superscript", "underline", in order: if node's effective
		// command value for command is one of its inline command activated values,
		// add (command, true) to overrides, and otherwise add (command, false) to
		// overrides."
		$_(["bold", "italic", "strikethrough", "subscript", "superscript", "underline"]).forEach(function (command) {
			if ($_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(node, command)) != -1) {
				overrides.push([command, true]);
			} else {
				overrides.push([command, false]);
			}
		});

		// "For each command in the list "fontName", "foreColor", "hiliteColor", in
		// order: add (command, command's value) to overrides."

		$_(["fontname", "fontsize", "forecolor", "hilitecolor"]).forEach(function (command) {
			overrides.push([command, commands[command].value(range)]);
		});

		// "Add ("fontSize", node's effective command value for "fontSize") to
		// overrides."
		overrides.push(["fontsize", getEffectiveCommandValue(node, "fontsize")]);

		// "Return overrides."
		return overrides;
	}

	function restoreStatesAndValues(overrides, range) {
		var i;
		var command;
		var override;
		// "Let node be the first editable Text node effectively contained in the
		// active range, or null if there is none."
		var node = $_(getAllEffectivelyContainedNodes(range)).filter(function (node) {
			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
		})[0];

		function isEditableTextNode(node) {
			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
		}

		// "If node is not null, then for each (command, override) pair in
		// overrides, in order:"
		if (node) {

			for (i = 0; i < overrides.length; i++) {
				command = overrides[i][0];
				override = overrides[i][1];

				// "If override is a boolean, and queryCommandState(command)
				// returns something different from override, call
				// execCommand(command)."
				if (typeof override == "boolean" && myQueryCommandState(command, range) != override) {
					myExecCommand(command, false, override, range);

					// "Otherwise, if override is a string, and command is not
					// "fontSize", and queryCommandValue(command) returns something not
					// equivalent to override, call execCommand(command, false,
					// override)."
				} else if (typeof override == "string" && command != "fontsize" && !areEquivalentValues(command, myQueryCommandValue(command, range), override)) {
					myExecCommand(command, false, override, range);

					// "Otherwise, if override is a string; and command is "fontSize";
					// and either there is a value override for "fontSize" that is not
					// equal to override, or there is no value override for "fontSize"
					// and node's effective command value for "fontSize" is not loosely
					// equivalent to override: call execCommand("fontSize", false,
					// override)."
				} else if (typeof override == "string"
						   && command == "fontsize"
						   && ((getValueOverride("fontsize", range) !== undefined
								&& getValueOverride("fontsize", range) !== override)
							   || (getValueOverride("fontsize", range) === undefined
								   && !areLooselyEquivalentValues(command, getEffectiveCommandValue(node, "fontsize"), override)))) {
					myExecCommand("fontsize", false, override, range);

					// "Otherwise, continue this loop from the beginning."
				} else {
					continue;
				}

				// "Set node to the first editable Text node effectively contained
				// in the active range, if there is one."
				node = $_(getAllEffectivelyContainedNodes(range)).filter(isEditableTextNode)[0] || node;
			}

			// "Otherwise, for each (command, override) pair in overrides, in order:"
		} else {
			for (i = 0; i < overrides.length; i++) {
				command = overrides[i][0];
				override = overrides[i][1];

				// "If override is a boolean, set the state override for command to
				// override."
				if (typeof override == "boolean") {
					setStateOverride(command, override, range);
				}

				// "If override is a string, set the value override for command to
				// override."
				if (typeof override == "string") {
					setValueOverride(command, override, range);
				}
			}
		}
	}

	//@}
	///// Canonical space sequences /////
	//@{

	function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
		// "If n is zero, return the empty string."
		if (n == 0) {
			return "";
		}

		// "If n is one and both non-breaking start and non-breaking end are false,
		// return a single space (U+0020)."
		if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {
			return " ";
		}

		// "If n is one, return a single non-breaking space (U+00A0)."
		if (n == 1) {
			return "\xa0";
		}

		// "Let buffer be the empty string."
		var buffer = "";

		// "If non-breaking start is true, let repeated pair be U+00A0 U+0020.
		// Otherwise, let it be U+0020 U+00A0."
		var repeatedPair;
		if (nonBreakingStart) {
			repeatedPair = "\xa0 ";
		} else {
			repeatedPair = " \xa0";
		}

		// "While n is greater than three, append repeated pair to buffer and
		// subtract two from n."
		while (n > 3) {
			buffer += repeatedPair;
			n -= 2;
		}

		// "If n is three, append a three-element string to buffer depending on
		// non-breaking start and non-breaking end:"
		if (n == 3) {
			buffer += !nonBreakingStart && !nonBreakingEnd ? " \xa0 " : nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 " : !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0" : nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0" : "impossible";

			// "Otherwise, append a two-element string to buffer depending on
			// non-breaking start and non-breaking end:"
		} else {
			buffer += !nonBreakingStart && !nonBreakingEnd ? "\xa0 " : nonBreakingStart && !nonBreakingEnd ? "\xa0 " : !nonBreakingStart && nonBreakingEnd ? " \xa0" : nonBreakingStart && nonBreakingEnd ? "\xa0\xa0" : "impossible";
		}

		// "Return buffer."
		return buffer;
	}

	function canonicalizeWhitespace(node, offset) {
		// "If node is neither editable nor an editing host, abort these steps."
		if (!isEditable(node) && !isEditingHost(node)) {
			return;
		}

		// "Let start node equal node and let start offset equal offset."
		var startNode = node;
		var startOffset = offset;

		// "Repeat the following steps:"
		while (true) {
			// "If start node has a child in the same editing host with index start
			// offset minus one, set start node to that child, then set start
			// offset to start node's length."
			if (0 <= startOffset - 1 && inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {
				startNode = startNode.childNodes[startOffset - 1];
				startOffset = getNodeLength(startNode);

				// "Otherwise, if start offset is zero and start node does not follow a
				// line break and start node's parent is in the same editing host, set
				// start offset to start node's index, then set start node to its
				// parent."
			} else if (startOffset == 0 && !followsLineBreak(startNode) && inSameEditingHost(startNode, startNode.parentNode)) {
				startOffset = Dom.getIndexInParent(startNode);
				startNode = startNode.parentNode;

				// "Otherwise, if start node is a Text node and its parent's resolved
				// value for "white-space" is neither "pre" nor "pre-wrap" and start
				// offset is not zero and the (start offset − 1)st element of start
				// node's data is a space (0x0020) or non-breaking space (0x00A0),
				// subtract one from start offset."
			} else if (startNode.nodeType == $_.Node.TEXT_NODE && jQuery.inArray($_.getComputedStyle(startNode.parentNode).whiteSpace, ["pre", "pre-wrap"]) == -1 && startOffset != 0 && /[ \xa0]/.test(startNode.data[startOffset - 1])) {
				startOffset--;

				// "Otherwise, break from this loop."
			} else {
				break;
			}
		}

		// "Let end node equal start node and end offset equal start offset."
		var endNode = startNode;
		var endOffset = startOffset;

		// "Let length equal zero."
		var length = 0;

		// "Let follows space be false."
		var followsSpace = false;

		// "Repeat the following steps:"
		while (true) {
			// "If end node has a child in the same editing host with index end
			// offset, set end node to that child, then set end offset to zero."
			if (endOffset < endNode.childNodes.length && inSameEditingHost(endNode, endNode.childNodes[endOffset])) {
				endNode = endNode.childNodes[endOffset];
				endOffset = 0;

				// "Otherwise, if end offset is end node's length and end node does not
				// precede a line break and end node's parent is in the same editing
				// host, set end offset to one plus end node's index, then set end node
				// to its parent."
			} else if (endOffset == getNodeLength(endNode) && !precedesLineBreak(endNode) && inSameEditingHost(endNode, endNode.parentNode)) {
				endOffset = 1 + Dom.getIndexInParent(endNode);
				endNode = endNode.parentNode;

				// "Otherwise, if end node is a Text node and its parent's resolved
				// value for "white-space" is neither "pre" nor "pre-wrap" and end
				// offset is not end node's length and the end offsetth element of
				// end node's data is a space (0x0020) or non-breaking space (0x00A0):"
			} else if (endNode.nodeType == $_.Node.TEXT_NODE && jQuery.inArray($_.getComputedStyle(endNode.parentNode).whiteSpace, ["pre", "pre-wrap"]) == -1 && endOffset != getNodeLength(endNode) && /[ \xa0]/.test(endNode.data[endOffset])) {
				// "If follows space is true and the end offsetth element of end
				// node's data is a space (0x0020), call deleteData(end offset, 1)
				// on end node, then continue this loop from the beginning."
				if (followsSpace && " " == endNode.data[endOffset]) {
					endNode.deleteData(endOffset, 1);
					continue;
				}

				// "Set follows space to true if the end offsetth element of end
				// node's data is a space (0x0020), false otherwise."
				followsSpace = " " == endNode.data[endOffset];

				// "Add one to end offset."
				endOffset++;

				// "Add one to length."
				length++;

				// "Otherwise, break from this loop."
			} else {
				break;
			}
		}

		// "Let replacement whitespace be the canonical space sequence of length
		// length. non-breaking start is true if start offset is zero and start
		// node follows a line break, and false otherwise. non-breaking end is true
		// if end offset is end node's length and end node precedes a line break,
		// and false otherwise."
		var replacementWhitespace = canonicalSpaceSequence(length, startOffset == 0 && followsLineBreak(startNode), endOffset == getNodeLength(endNode) && precedesLineBreak(endNode));

		// "While (start node, start offset) is before (end node, end offset):"
		while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
			// "If start node has a child with index start offset, set start node
			// to that child, then set start offset to zero."
			if (startOffset < startNode.childNodes.length) {
				startNode = startNode.childNodes[startOffset];
				startOffset = 0;

				// "Otherwise, if start node is not a Text node or if start offset is
				// start node's length, set start offset to one plus start node's
				// index, then set start node to its parent."
			} else if (startNode.nodeType != $_.Node.TEXT_NODE || startOffset == getNodeLength(startNode)) {
				startOffset = 1 + Dom.getIndexInParent(startNode);
				startNode = startNode.parentNode;

				// "Otherwise:"
			} else {
				// "Remove the first element from replacement whitespace, and let
				// element be that element."
				var element = replacementWhitespace[0];
				replacementWhitespace = replacementWhitespace.slice(1);

				// "If element is not the same as the start offsetth element of
				// start node's data:"
				if (element != startNode.data[startOffset]) {
					// "Call insertData(start offset, element) on start node."
					startNode.insertData(startOffset, element);

					// "Call deleteData(start offset + 1, 1) on start node."
					startNode.deleteData(startOffset + 1, 1);
				}

				// "Add one to start offset."
				startOffset++;
			}
		}
	}

	//@}
	///// Deleting the contents of a range /////
	//@{

	function deleteContents(arg1, arg2, arg3, arg4, arg5) {
		// We accept several different calling conventions:
		//
		// 1) A single argument, which is a range.
		//
		// 2) Two arguments, the first being a range and the second flags.
		//
		// 3) Four arguments, the start and end of a range.
		//
		// 4) Five arguments, the start and end of a range plus flags.
		//
		// The flags argument is a dictionary that can have up to two keys,
		// blockMerging and stripWrappers, whose corresponding values are
		// interpreted as boolean.  E.g., {stripWrappers: false}.
		var range;
		var flags = {};
		var i;

		if (arguments.length < 3) {
			range = arg1;
		} else {
			range = Aloha.createRange();
			range.setStart(arg1, arg2);
			range.setEnd(arg3, arg4);
		}
		if (arguments.length == 2) {
			flags = arg2;
		}
		if (arguments.length == 5) {
			flags = arg5;
		}

		var blockMerging = null != flags.blockMerging ? !!flags.blockMerging : true;
		var stripWrappers = null != flags.stripWrappers ? !!flags.stripWrappers : true;

		// "If range is null, abort these steps and do nothing."
		if (!range) {
			return;
		}

		// "Let start node, start offset, end node, and end offset be range's start
		// and end nodes and offsets."
		var startNode = range.startContainer;
		var startOffset = range.startOffset;
		var endNode = range.endContainer;
		var endOffset = range.endOffset;
		var referenceNode;

		// "While start node has at least one child:"
		while (startNode.hasChildNodes()) {
			// "If start offset is start node's length, and start node's parent is
			// in the same editing host, and start node is an inline node, set
			// start offset to one plus the index of start node, then set start
			// node to its parent and continue this loop from the beginning."
			if (startOffset == getNodeLength(startNode) && inSameEditingHost(startNode, startNode.parentNode) && isInlineNode(startNode)) {
				startOffset = 1 + Dom.getIndexInParent(startNode);
				startNode = startNode.parentNode;
				continue;
			}

			// "If start offset is start node's length, break from this loop."
			if (startOffset == getNodeLength(startNode)) {
				break;
			}

			// "Let reference node be the child of start node with index equal to
			// start offset."
			referenceNode = startNode.childNodes[startOffset];

			// "If reference node is a block node or an Element with no children,
			// or is neither an Element nor a Text node, break from this loop."
			if (isBlockNode(referenceNode) || (referenceNode.nodeType == $_.Node.ELEMENT_NODE && !referenceNode.hasChildNodes()) || (referenceNode.nodeType != $_.Node.ELEMENT_NODE && referenceNode.nodeType != $_.Node.TEXT_NODE)) {
				break;
			}

			// "Set start node to reference node and start offset to 0."
			startNode = referenceNode;
			startOffset = 0;
		}

		// "While end node has at least one child:"
		while (endNode.hasChildNodes()) {
			// "If end offset is 0, and end node's parent is in the same editing
			// host, and end node is an inline node, set end offset to the index of
			// end node, then set end node to its parent and continue this loop
			// from the beginning."
			if (endOffset == 0 && inSameEditingHost(endNode, endNode.parentNode) && isInlineNode(endNode)) {
				endOffset = Dom.getIndexInParent(endNode);
				endNode = endNode.parentNode;
				continue;
			}

			// "If end offset is 0, break from this loop."
			if (endOffset == 0) {
				break;
			}

			// "Let reference node be the child of end node with index equal to end
			// offset minus one."
			referenceNode = endNode.childNodes[endOffset - 1];

			// "If reference node is a block node or an Element with no children,
			// or is neither an Element nor a Text node, break from this loop."
			if (isBlockNode(referenceNode) || (referenceNode.nodeType == $_.Node.ELEMENT_NODE && !referenceNode.hasChildNodes()) || (referenceNode.nodeType != $_.Node.ELEMENT_NODE && referenceNode.nodeType != $_.Node.TEXT_NODE)) {
				break;
			}

			// "Set end node to reference node and end offset to the length of
			// reference node."
			endNode = referenceNode;
			endOffset = getNodeLength(referenceNode);
		}

		// "If (end node, end offset) is not after (start node, start offset), set
		// range's end to its start and abort these steps."
		if (getPosition(endNode, endOffset, startNode, startOffset) !== "after") {
			range.setEnd(range.startContainer, range.startOffset);
			return range;
		}

		// "If start node is a Text node and start offset is 0, set start offset to
		// the index of start node, then set start node to its parent."
		// Commented out for unknown reason
		//if (startNode.nodeType == $_.Node.TEXT_NODE && startOffset == 0 && startNode != endNode) {
		//		startOffset = Dom.getIndexInParent(startNode);
		//		startNode = startNode.parentNode;
		//}

		// "If end node is a Text node and end offset is its length, set end offset
		// to one plus the index of end node, then set end node to its parent."
		if (endNode.nodeType == $_.Node.TEXT_NODE && endOffset == getNodeLength(endNode) && startNode != endNode) {
			endOffset = 1 + Dom.getIndexInParent(endNode);
			endNode = endNode.parentNode;
		}

		// "Set range's start to (start node, start offset) and its end to (end
		// node, end offset)."
		range.setStart(startNode, startOffset);
		range.setEnd(endNode, endOffset);

		// "Let start block be the start node of range."
		var startBlock = range.startContainer;

		// "While start block's parent is in the same editing host and start block
		// is an inline node, set start block to its parent."
		while (inSameEditingHost(startBlock, startBlock.parentNode) && isInlineNode(startBlock)) {
			startBlock = startBlock.parentNode;
		}

		// "If start block is neither a block node nor an editing host, or "span"
		// is not an allowed child of start block, or start block is a td or th,
		// set start block to null."
		if ((!isBlockNode(startBlock) && !isEditingHost(startBlock)) || !isAllowedChild("span", startBlock) || isHtmlElementInArray(startBlock, ["td", "th"])) {
			startBlock = null;
		}

		// "Let end block be the end node of range."
		var endBlock = range.endContainer;

		// "While end block's parent is in the same editing host and end block is
		// an inline node, set end block to its parent."
		while (inSameEditingHost(endBlock, endBlock.parentNode) && isInlineNode(endBlock)) {
			endBlock = endBlock.parentNode;
		}

		// "If end block is neither a block node nor an editing host, or "span" is
		// not an allowed child of end block, or end block is a td or th, set end
		// block to null."
		if ((!isBlockNode(endBlock) && !isEditingHost(endBlock)) || !isAllowedChild("span", endBlock) || isHtmlElementInArray(endBlock, ["td", "th"])) {
			endBlock = null;
		}

		// "Record current states and values, and let overrides be the result."
		var overrides = recordCurrentStatesAndValues(range);
		var parent_;
		// "If start node and end node are the same, and start node is an editable
		// Text node:"
		if (startNode == endNode && isEditable(startNode) && startNode.nodeType == $_.Node.TEXT_NODE) {
			// "Let parent be the parent of node."
			parent_ = startNode.parentNode;

			// "Call deleteData(start offset, end offset − start offset) on start
			// node."
			startNode.deleteData(startOffset, endOffset - startOffset);

			// if deleting the text moved two spaces together, we replace the left one by a &nbsp;, which makes the two spaces a visible
			// two space sequence
			if (startOffset > 0 && startNode.data.substr(startOffset - 1, 1) === ' ' && startOffset < startNode.data.length && startNode.data.substr(startOffset, 1) === ' ') {
				startNode.replaceData(startOffset - 1, 1, '\xa0');
			}

			// "Canonicalize whitespace at (start node, start offset)."
			canonicalizeWhitespace(startNode, startOffset);

			// "Set range's end to its start."
			// Ok, also set the range's start to its start, because modifying the text
			// might have somehow corrupted the range
			range.setStart(range.startContainer, range.startOffset);
			range.setEnd(range.startContainer, range.startOffset);

			// "Restore states and values from overrides."
			restoreStatesAndValues(overrides, range);

			// "If parent is editable or an editing host, is not an inline node,
			// and has no children, call createElement("br") on the context object
			// and append the result as the last child of parent."
			// only do this, if the offsetHeight is 0
			if ((isEditable(parent_) || isEditingHost(parent_)) && !isInlineNode(parent_)) {
				ensureContainerEditable(parent_);
			}

			// "Abort these steps."
			return range;
		}

		// "If start node is an editable Text node, call deleteData() on it, with
		// start offset as the first argument and (length of start node − start
		// offset) as the second argument."
		if (isEditable(startNode) && startNode.nodeType == $_.Node.TEXT_NODE) {
			startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
		}

		// "Let node list be a list of nodes, initially empty."
		//
		// "For each node contained in range, append node to node list if the last
		// member of node list (if any) is not an ancestor of node; node is
		// editable; and node is not a thead, tbody, tfoot, tr, th, or td."
		var nodeList = getContainedNodes(
			range,
			function (node) {
				return isEditable(node) && !isHtmlElementInArray(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
			}
		);

		// "For each node in node list:"
		for (i = 0; i < nodeList.length; i++) {
			var node = nodeList[i];

			// "Let parent be the parent of node."
			parent_ = node.parentNode;

			// "Remove node from parent."
			parent_.removeChild(node);

			// "If strip wrappers is true or parent is not an ancestor container of
			// start node, while parent is an editable inline node with length 0,
			// let grandparent be the parent of parent, then remove parent from
			// grandparent, then set parent to grandparent."
			if (stripWrappers || (!isAncestor(parent_, startNode) && parent_ != startNode)) {
				while (isEditable(parent_) && isInlineNode(parent_) && getNodeLength(parent_) == 0) {
					var grandparent = parent_.parentNode;
					grandparent.removeChild(parent_);
					parent_ = grandparent;
				}
			}

			// "If parent is editable or an editing host, is not an inline node,
			// and has no children, call createElement("br") on the context object
			// and append the result as the last child of parent."
			// only do this, if the offsetHeight is 0
			if ((isEditable(parent_) || isEditingHost(parent_)) && !isInlineNode(parent_)) {
				ensureContainerEditable(parent_);
			}
		}

		// "If end node is an editable Text node, call deleteData(0, end offset) on
		// it."
		if (isEditable(endNode) && endNode.nodeType == $_.Node.TEXT_NODE) {
			endNode.deleteData(0, endOffset);
		}

		// "Canonicalize whitespace at range's start."
		canonicalizeWhitespace(range.startContainer, range.startOffset);

		// "Canonicalize whitespace at range's end."
		canonicalizeWhitespace(range.endContainer, range.endOffset);

		// A reference to the position where a node is removed.
		var pos;

		// "If block merging is false, or start block or end block is null, or
		// start block is not in the same editing host as end block, or start block
		// and end block are the same:"
		if (!blockMerging || !startBlock || !endBlock || !inSameEditingHost(startBlock, endBlock) || startBlock == endBlock) {
			// "Set range's end to its start."
			range.setEnd(range.startContainer, range.startOffset);

			// Calling delete on the give markup:
			// <editable><block><br>[]</block></editable>
			// should result in:
			// <editable>[]</editable>
			var block = startBlock || endBlock;
			if (isEmptyOnlyChildOfEditingHost(block)) {
				pos = removeNode(block);
				range.setStart(pos.node, pos.offset);
				range.setEnd(pos.node, pos.offset);
			}

			// "Restore states and values from overrides."
			restoreStatesAndValues(overrides, range);

			// "Abort these steps."
			return range;
		}

		// "If start block has one child, which is a collapsed block prop, remove
		// its child from it."
		if (startBlock.children.length == 1 && isCollapsedBlockProp(startBlock.firstChild)) {
			startBlock.removeChild(startBlock.firstChild);
		}

		// "If end block has one child, which is a collapsed block prop, remove its
		// child from it."
		if (endBlock.children.length == 1 && isCollapsedBlockProp(endBlock.firstChild)) {
			endBlock.removeChild(endBlock.firstChild);
		}

		var values;
		// "If start block is an ancestor of end block:"
		if (isAncestor(startBlock, endBlock)) {
			// "Let reference node be end block."
			referenceNode = endBlock;

			// "While reference node is not a child of start block, set reference
			// node to its parent."
			while (referenceNode.parentNode != startBlock) {
				referenceNode = referenceNode.parentNode;
			}

			// "Set the start and end of range to (start block, index of reference
			// node)."
			range.setStart(startBlock, Dom.getIndexInParent(referenceNode));
			range.setEnd(startBlock, Dom.getIndexInParent(referenceNode));

			// "If end block has no children:"
			if (!endBlock.hasChildNodes()) {
				// "While end block is editable and is the only child of its parent
				// and is not a child of start block, let parent equal end block,
				// then remove end block from parent, then set end block to
				// parent."
				while (isEditable(endBlock) && endBlock.parentNode.childNodes.length == 1 && endBlock.parentNode != startBlock) {
					parent_ = endBlock;
					parent_.removeChild(endBlock);
					endBlock = parent_;
				}

				// "If end block is editable and is not an inline node, and its
				// previousSibling and nextSibling are both inline nodes, call
				// createElement("br") on the context object and insert it into end
				// block's parent immediately after end block."

				if (isEditable(endBlock) && !isInlineNode(endBlock) && isInlineNode(endBlock.previousSibling) && isInlineNode(endBlock.nextSibling)) {
					endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);
				}

				// "If end block is editable, remove it from its parent."
				if (isEditable(endBlock)) {
					endBlock.parentNode.removeChild(endBlock);
				}

				// "Restore states and values from overrides."
				restoreStatesAndValues(overrides, range);

				// "Abort these steps."
				return range;
			}

			// "If end block's firstChild is not an inline node, restore states and
			// values from overrides, then abort these steps."
			if (!isInlineNode(endBlock.firstChild)) {
				restoreStatesAndValues(overrides, range);
				return range;
			}

			// "Let children be a list of nodes, initially empty."
			var children = [];

			// "Append the first child of end block to children."
			children.push(endBlock.firstChild);

			// "While children's last member is not a br, and children's last
			// member's nextSibling is an inline node, append children's last
			// member's nextSibling to children."
			while (!isNamedHtmlElement(children[children.length - 1], "br") && isInlineNode(children[children.length - 1].nextSibling)) {
				children.push(children[children.length - 1].nextSibling);
			}

			// "Record the values of children, and let values be the result."
			values = recordValues(children);

			// "While children's first member's parent is not start block, split
			// the parent of children."
			while (children[0].parentNode != startBlock) {
				splitParent(children, range);
			}

			// "If children's first member's previousSibling is an editable br,
			// remove that br from its parent."
			if (isEditable(children[0].previousSibling) && isNamedHtmlElement(children[0].previousSibling, "br")) {
				children[0].parentNode.removeChild(children[0].previousSibling);
			}

			// "Otherwise, if start block is a descendant of end block:"
		} else if (isDescendant(startBlock, endBlock)) {
			// "Set the start and end of range to (start block, length of start
			// block)."
			range.setStart(startBlock, getNodeLength(startBlock));
			range.setEnd(startBlock, getNodeLength(startBlock));

			// "Let reference node be start block."
			referenceNode = startBlock;

			// "While reference node is not a child of end block, set reference
			// node to its parent."
			while (referenceNode.parentNode != endBlock) {
				referenceNode = referenceNode.parentNode;
			}

			// "If reference node's nextSibling is an inline node and start block's
			// lastChild is a br, remove start block's lastChild from it."
			if (isInlineNode(referenceNode.nextSibling) && isNamedHtmlElement(startBlock.lastChild, "br")) {
				startBlock.removeChild(startBlock.lastChild);
			}

			// "Let nodes to move be a list of nodes, initially empty."
			var nodesToMove = [];

			// "If reference node's nextSibling is neither null nor a br nor a
			// block node, append it to nodes to move."
			if (referenceNode.nextSibling && !isNamedHtmlElement(referenceNode.nextSibling, "br") && !isBlockNode(referenceNode.nextSibling)) {
				nodesToMove.push(referenceNode.nextSibling);
			}

			// "While nodes to move is nonempty and its last member's nextSibling
			// is neither null nor a br nor a block node, append it to nodes to
			// move."
			if (nodesToMove.length && nodesToMove[nodesToMove.length - 1].nextSibling && !isNamedHtmlElement(nodesToMove[nodesToMove.length - 1].nextSibling, "br") && !isBlockNode(nodesToMove[nodesToMove.length - 1].nextSibling)) {
				nodesToMove.push(nodesToMove[nodesToMove.length - 1].nextSibling);
			}

			// "Record the values of nodes to move, and let values be the result."
			values = recordValues(nodesToMove);

			// "For each node in nodes to move, append node as the last child of
			// start block, preserving ranges."
			$_(nodesToMove).forEach(function (node) {
				movePreservingRanges(node, startBlock, -1, range);
			});

			// "If the nextSibling of reference node is a br, remove it from its
			// parent."
			if (isNamedHtmlElement(referenceNode.nextSibling, "br")) {
				referenceNode.parentNode.removeChild(referenceNode.nextSibling);
			}

			// "Otherwise:"
		} else {
			// "Set the start and end of range to (start block, length of start
			// block)."
			range.setStart(startBlock, getNodeLength(startBlock));
			range.setEnd(startBlock, getNodeLength(startBlock));

			// "If end block's firstChild is an inline node and start block's
			// lastChild is a br, remove start block's lastChild from it."
			if (isInlineNode(endBlock.firstChild) && isNamedHtmlElement(startBlock.lastChild, "br")) {
				startBlock.removeChild(startBlock.lastChild);
			}

			// "Record the values of end block's children, and let values be the
			// result."
			values = recordValues([].slice.call(toArray(endBlock.childNodes)));

			// "While end block has children, append the first child of end block
			// to start block, preserving ranges."
			while (endBlock.hasChildNodes()) {
				movePreservingRanges(endBlock.firstChild, startBlock, -1, range);
			}

			// "While end block has no children, let parent be the parent of end
			// block, then remove end block from parent, then set end block to
			// parent."
			while (!endBlock.hasChildNodes()) {
				parent_ = endBlock.parentNode;
				parent_.removeChild(endBlock);
				endBlock = parent_;
			}
		}

		// "Restore the values from values."
		restoreValues(values, range);

		// Because otherwise calling deleteContents() with the given selection:
		//
		// <editable><block>[foo</block><block>bar]</block></editable>
		//
		// would result in:
		//
		// <editable><block>[]<br /></block></editable>
		//
		// instead of:
		//
		// <editable>[]</editable>
		//
		// Therefore, the below makes it possible to completely empty contents
		// of editing hosts via operations like CTRL+A, DEL.
		//
		// If startBlock is empty, and startBlock is the immediate and only
		// child of its parent editing host, then remove startBlock and collapse
		// the selection at the beginning of the editing post.
		if (isEmptyOnlyChildOfEditingHost(startBlock)) {
			pos = removeNode(startBlock);
			range.setStart(pos.node, pos.offset);
			range.setEnd(pos.node, pos.offset);
			startBlock = pos.node;
		}

		// "If start block has no children, call createElement("br") on the context
		// object and append the result as the last child of start block."
		ensureContainerEditable(startBlock);

		// "Restore states and values from overrides."
		restoreStatesAndValues(overrides, range);

		return range;
	}

	// "To remove a node node while preserving its descendants, split the parent of
	// node's children if it has any. If it has no children, instead remove it from
	// its parent."
	function removePreservingDescendants(node, range) {
		if (node.hasChildNodes()) {
			splitParent([].slice.call(toArray(node.childNodes)), range);
		} else {
			node.parentNode.removeChild(node);
		}
	}

	//@}
	///// Indenting and outdenting /////
	//@{

	function cleanLists(node, range) {
		// remove any whitespace nodes around list nodes
		if (node) {
			jQuery(node).find('ul,ol,li').each(function () {
				jQuery(this).contents().each(function () {
					if (isWhitespaceNode(this)) {
						var index = Dom.getIndexInParent(this);

						// if the range points to somewhere behind the removed text node, we reduce the offset
						if (range.startContainer === this.parentNode && range.startOffset > index) {
							range.startOffset--;
						} else if (range.startContainer === this) {
							// the range starts in the removed text node, let it start right before
							range.startContainer = this.parentNode;
							range.startOffset = index;
						}
						// same thing for end of the range
						if (range.endContainer === this.parentNode && range.endOffset > index) {
							range.endOffset--;
						} else if (range.endContainer === this) {
							range.endContainer = this.parentNode;
							range.endOffset = index;
						}
						// finally remove the whitespace node
						jQuery(this).remove();
					}
				});
			});
		}
	}


	//@}
	///// Indenting and outdenting /////
	//@{

	function indentNodes(nodeList, range) {
		// "If node list is empty, do nothing and abort these steps."
		if (!nodeList.length) {
			return;
		}

		// "Let first node be the first member of node list."
		var firstNode = nodeList[0];

		// "If first node's parent is an ol or ul:"
		if (isHtmlElementInArray(firstNode.parentNode, ["OL", "UL"])) {
			// "Let tag be the local name of the parent of first node."
			var tag = firstNode.parentNode.tagName;

			// "Wrap node list, with sibling criteria returning true for an HTML
			// element with local name tag and false otherwise, and new parent
			// instructions returning the result of calling createElement(tag) on
			// the ownerDocument of first node."
			wrap(
				nodeList,
				function (node) {
					return isHtmlElement_obsolete(node, tag);
				},
				function () {
					return firstNode.ownerDocument.createElement(tag);
				},
				range
			);

			// "Abort these steps."
			return;
		}

		// "Wrap node list, with sibling criteria returning true for a simple
		// indentation element and false otherwise, and new parent instructions
		// returning the result of calling createElement("blockquote") on the
		// ownerDocument of first node. Let new parent be the result."
		var newParent = wrap(
			nodeList,
			function (node) {
				return isSimpleIndentationElement(node);
			},
			function () {
				return firstNode.ownerDocument.createElement("blockquote");
			},
			range
		);

		// "Fix disallowed ancestors of new parent."
		fixDisallowedAncestors(newParent, range);
	}

	function outdentNode(node, range) {
		// "If node is not editable, abort these steps."
		if (!isEditable(node)) {
			return;
		}

		// "If node is a simple indentation element, remove node, preserving its
		// descendants.  Then abort these steps."
		if (isSimpleIndentationElement(node)) {
			removePreservingDescendants(node, range);
			return;
		}

		// "If node is an indentation element:"
		if (isIndentationElement(node)) {
			// "Unset the class and dir attributes of node, if any."
			node.removeAttribute("class");
			node.removeAttribute("dir");

			// "Unset the margin, padding, and border CSS properties of node."
			node.style.margin = "";
			node.style.padding = "";
			node.style.border = "";
			if (node.getAttribute("style") == "") {
				node.removeAttribute("style");
			}

			// "Set the tag name of node to "div"."
			setTagName(node, "div", range);

			// "Abort these steps."
			return;
		}

		// "Let current ancestor be node's parent."
		var currentAncestor = node.parentNode;

		// "Let ancestor list be a list of nodes, initially empty."
		var ancestorList = [];

		// "While current ancestor is an editable Element that is neither a simple
		// indentation element nor an ol nor a ul, append current ancestor to
		// ancestor list and then set current ancestor to its parent."
		while (isEditable(currentAncestor) && currentAncestor.nodeType == $_.Node.ELEMENT_NODE && !isSimpleIndentationElement(currentAncestor) && !isHtmlElementInArray(currentAncestor, ["ol", "ul"])) {
			ancestorList.push(currentAncestor);
			currentAncestor = currentAncestor.parentNode;
		}

		// "If current ancestor is not an editable simple indentation element:"
		if (!isEditable(currentAncestor) || !isSimpleIndentationElement(currentAncestor)) {
			// "Let current ancestor be node's parent."
			currentAncestor = node.parentNode;

			// "Let ancestor list be the empty list."
			ancestorList = [];

			// "While current ancestor is an editable Element that is neither an
			// indentation element nor an ol nor a ul, append current ancestor to
			// ancestor list and then set current ancestor to its parent."
			while (isEditable(currentAncestor) && currentAncestor.nodeType == $_.Node.ELEMENT_NODE && !isIndentationElement(currentAncestor) && !isHtmlElementInArray(currentAncestor, ["ol", "ul"])) {
				ancestorList.push(currentAncestor);
				currentAncestor = currentAncestor.parentNode;
			}
		}

		// "If node is an ol or ul and current ancestor is not an editable
		// indentation element:"
		if (isHtmlElementInArray(node, ["OL", "UL"]) && (!isEditable(currentAncestor) || !isIndentationElement(currentAncestor))) {
			// "Unset the reversed, start, and type attributes of node, if any are
			// set."
			node.removeAttribute("reversed");
			node.removeAttribute("start");
			node.removeAttribute("type");

			// "Let children be the children of node."
			var children = [].slice.call(toArray(node.childNodes));

			// "If node has attributes, and its parent is not an ol or ul, set the
			// tag name of node to "div"."
			if (node.attributes.length && !isHtmlElementInArray(node.parentNode, ["OL", "UL"])) {
				setTagName(node, "div", range);

				// "Otherwise:"
			} else {
				// "Record the values of node's children, and let values be the
				// result."
				var values = recordValues([].slice.call(toArray(node.childNodes)));

				// "Remove node, preserving its descendants."
				removePreservingDescendants(node, range);

				// "Restore the values from values."
				restoreValues(values, range);
			}

			// "Fix disallowed ancestors of each member of children."
			var i;
			for (i = 0; i < children.length; i++) {
				fixDisallowedAncestors(children[i], range);
			}

			// "Abort these steps."
			return;
		}

		// "If current ancestor is not an editable indentation element, abort these
		// steps."
		if (!isEditable(currentAncestor) || !isIndentationElement(currentAncestor)) {
			return;
		}

		// "Append current ancestor to ancestor list."
		ancestorList.push(currentAncestor);

		// "Let original ancestor be current ancestor."
		var originalAncestor = currentAncestor;

		// "While ancestor list is not empty:"
		while (ancestorList.length) {
			// "Let current ancestor be the last member of ancestor list."
			//
			// "Remove the last member of ancestor list."
			currentAncestor = ancestorList.pop();

			// "Let target be the child of current ancestor that is equal to either
			// node or the last member of ancestor list."
			var target = node.parentNode == currentAncestor ? node : ancestorList[ancestorList.length - 1];

			// "If target is an inline node that is not a br, and its nextSibling
			// is a br, remove target's nextSibling from its parent."
			if (isInlineNode(target) && !isNamedHtmlElement(target, 'BR') && isNamedHtmlElement(target.nextSibling, "BR")) {
				target.parentNode.removeChild(target.nextSibling);
			}

			// "Let preceding siblings be the preceding siblings of target, and let
			// following siblings be the following siblings of target."
			var precedingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 0, Dom.getIndexInParent(target));
			var followingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 1 + Dom.getIndexInParent(target));

			// "Indent preceding siblings."
			indentNodes(precedingSiblings, range);

			// "Indent following siblings."
			indentNodes(followingSiblings, range);
		}

		// "Outdent original ancestor."
		outdentNode(originalAncestor, range);
	}


	//@}
	///// Toggling lists /////
	//@{

	function toggleLists(tagName, range) {
		// "Let mode be "disable" if the selection's list state is tag name, and
		// "enable" otherwise."
		var mode = getSelectionListState() == tagName ? "disable" : "enable";

		tagName = tagName.toUpperCase();

		// "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
		// "ol"."
		var otherTagName = tagName == "OL" ? "UL" : "OL";

		// "Let items be a list of all lis that are ancestor containers of the
		// range's start and/or end node."
		//
		// It's annoying to get this in tree order using functional stuff without
		// doing getDescendants(document), which is slow, so I do it imperatively.
		var items = [];
		(function () {
			var ancestorContainer;
			for (ancestorContainer = range.endContainer;
				     ancestorContainer != range.commonAncestorContainer;
				     ancestorContainer = ancestorContainer.parentNode) {
				if (isNamedHtmlElement(ancestorContainer, "li")) {
					items.unshift(ancestorContainer);
				}
			}
			for (ancestorContainer = range.startContainer;
				     ancestorContainer;
				     ancestorContainer = ancestorContainer.parentNode) {
				if (isNamedHtmlElement(ancestorContainer, "li")) {
					items.unshift(ancestorContainer);
				}
			}
		}());

		// "For each item in items, normalize sublists of item."
		$_(items).forEach(function (thisArg) {
			normalizeSublists(thisArg, range);
		});

		// "Block-extend the range, and let new range be the result."
		var newRange = blockExtend(range);

		// "If mode is "enable", then let lists to convert consist of every
		// editable HTML element with local name other tag name that is contained
		// in new range, and for every list in lists to convert:"
		if (mode == "enable") {
			$_(getAllContainedNodes(newRange, function (node) {
				return isEditable(node) && isHtmlElement_obsolete(node, otherTagName);
			})).forEach(function (list) {
				// "If list's previousSibling or nextSibling is an editable HTML
				// element with local name tag name:"
				if ((isEditable(list.previousSibling) && isHtmlElement_obsolete(list.previousSibling, tagName)) || (isEditable(list.nextSibling) && isHtmlElement_obsolete(list.nextSibling, tagName))) {
					// "Let children be list's children."
					var children = [].slice.call(toArray(list.childNodes));

					// "Record the values of children, and let values be the
					// result."
					var values = recordValues(children);

					// "Split the parent of children."
					splitParent(children, range);

					// "Wrap children, with sibling criteria returning true for an
					// HTML element with local name tag name and false otherwise."
					wrap(
						children,
						function (node) {
							return isHtmlElement_obsolete(node, tagName);
						},
						function () {
							return null;
						},
						range
					);

					// "Restore the values from values."
					restoreValues(values, range);

					// "Otherwise, set the tag name of list to tag name."
				} else {
					setTagName(list, tagName, range);
				}
			});
		}

		// "Let node list be a list of nodes, initially empty."
		//
		// "For each node node contained in new range, if node is editable; the
		// last member of node list (if any) is not an ancestor of node; node
		// is not an indentation element; and either node is an ol or ul, or its
		// parent is an ol or ul, or it is an allowed child of "li"; then append
		// node to node list."
		var nodeList = getContainedNodes(newRange, function (node) {
			return isEditable(node) && !isIndentationElement(node) && (isHtmlElementInArray(node, ["OL", "UL"]) || isHtmlElementInArray(node.parentNode, ["OL", "UL"]) || isAllowedChild(node, "li"));
		});

		// "If mode is "enable", remove from node list any ol or ul whose parent is
		// not also an ol or ul."
		if (mode == "enable") {
			nodeList = $_(nodeList).filter(function (node) {
				return !isHtmlElementInArray(node, ["ol", "ul"]) || isHtmlElementInArray(node.parentNode, ["ol", "ul"]);
			});
		}

		// "If mode is "disable", then while node list is not empty:"
		var sublist, values;

		function createLi() {
			return document.createElement("li");
		}

		function isOlUl(node) {
			return isHtmlElementInArray(node, ["ol", "ul"]);
		}

		function makeIsElementPred(tagName) {
			return function (node) {
				return isHtmlElement_obsolete(node, tagName);
			};
		}

		function makeCreateElement(tagName) {
			return function () {
				return document.createElement(tagName);
			};
		}

		function makeCreateElementSublist(tagName, sublist, range) {
			return function () {
				// "If sublist's first member's parent is not an editable
				// simple indentation element, or sublist's first member's
				// parent's previousSibling is not an editable HTML element
				// with local name tag name, call createElement(tag name)
				// on the context object and return the result."
				if (!isEditable(sublist[0].parentNode) || !isSimpleIndentationElement(sublist[0].parentNode) || !isEditable(sublist[0].parentNode.previousSibling) || !isHtmlElement_obsolete(sublist[0].parentNode.previousSibling, tagName)) {
					return document.createElement(tagName);
				}

				// "Let list be sublist's first member's parent's
				// previousSibling."
				var list = sublist[0].parentNode.previousSibling;

				// "Normalize sublists of list's lastChild."
				normalizeSublists(list.lastChild, range);

				// "If list's lastChild is not an editable HTML element
				// with local name tag name, call createElement(tag name)
				// on the context object, and append the result as the last
				// child of list."
				if (!isEditable(list.lastChild) || !isHtmlElement_obsolete(list.lastChild, tagName)) {
					list.appendChild(document.createElement(tagName));
				}

				// "Return the last child of list."
				return list.lastChild;
			};
		}

		if (mode == "disable") {
			while (nodeList.length) {
				// "Let sublist be an empty list of nodes."
				sublist = [];

				// "Remove the first member from node list and append it to
				// sublist."
				sublist.push(nodeList.shift());

				// "If the first member of sublist is an HTML element with local
				// name tag name, outdent it and continue this loop from the
				// beginning."
				if (isHtmlElement_obsolete(sublist[0], tagName)) {
					outdentNode(sublist[0], range);
					continue;
				}

				// "While node list is not empty, and the first member of node list
				// is the nextSibling of the last member of sublist and is not an
				// HTML element with local name tag name, remove the first member
				// from node list and append it to sublist."
				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isHtmlElement_obsolete(nodeList[0], tagName)) {
					sublist.push(nodeList.shift());
				}

				// "Record the values of sublist, and let values be the result."
				values = recordValues(sublist);

				// "Split the parent of sublist."
				splitParent(sublist, range);

				// "Fix disallowed ancestors of each member of sublist."
				var i;
				for (i = 0; i < sublist.length; i++) {
					fixDisallowedAncestors(sublist[i], range);
				}

				// "Restore the values from values."
				restoreValues(values, range);
			}

			// "Otherwise, while node list is not empty:"
		} else {
			while (nodeList.length) {
				// "Let sublist be an empty list of nodes."
				sublist = [];

				// "While either sublist is empty, or node list is not empty and
				// its first member is the nextSibling of sublist's last member:"
				while (!sublist.length || (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling)) {
					// "If node list's first member is a p or div, set the tag name
					// of node list's first member to "li", and append the result
					// to sublist. Remove the first member from node list."
					if (isHtmlElementInArray(nodeList[0], ["p", "div"])) {
						sublist.push(setTagName(nodeList[0], "li", range));
						nodeList.shift();

						// "Otherwise, if the first member of node list is an li or ol
						// or ul, remove it from node list and append it to sublist."
					} else if (isHtmlElementInArray(nodeList[0], ["li", "ol", "ul"])) {
						sublist.push(nodeList.shift());

						// "Otherwise:"
					} else {
						// "Let nodes to wrap be a list of nodes, initially empty."
						var nodesToWrap = [];

						// "While nodes to wrap is empty, or node list is not empty
						// and its first member is the nextSibling of nodes to
						// wrap's last member and the first member of node list is
						// an inline node and the last member of nodes to wrap is
						// an inline node other than a br, remove the first member
						// from node list and append it to nodes to wrap."
						while (!nodesToWrap.length || (nodeList.length && nodeList[0] == nodesToWrap[nodesToWrap.length - 1].nextSibling && isInlineNode(nodeList[0]) && isInlineNode(nodesToWrap[nodesToWrap.length - 1]) && !isNamedHtmlElement(nodesToWrap[nodesToWrap.length - 1], "br"))) {
							nodesToWrap.push(nodeList.shift());
						}

						// "Wrap nodes to wrap, with new parent instructions
						// returning the result of calling createElement("li") on
						// the context object. Append the result to sublist."
						sublist.push(wrap(
							nodesToWrap,
							undefined,
							createLi,
							range
						));
					}
				}

				// "If sublist's first member's parent is an HTML element with
				// local name tag name, or if every member of sublist is an ol or
				// ul, continue this loop from the beginning."
				if (isHtmlElement_obsolete(sublist[0].parentNode, tagName) || $_(sublist).every(isOlUl)) {
					continue;
				}

				// "If sublist's first member's parent is an HTML element with
				// local name other tag name:"
				if (isHtmlElement_obsolete(sublist[0].parentNode, otherTagName)) {
					// "Record the values of sublist, and let values be the
					// result."
					values = recordValues(sublist);

					// "Split the parent of sublist."
					splitParent(sublist, range);

					// "Wrap sublist, with sibling criteria returning true for an
					// HTML element with local name tag name and false otherwise,
					// and new parent instructions returning the result of calling
					// createElement(tag name) on the context object."
					wrap(
						sublist,
						makeIsElementPred(tagName),
						makeCreateElement(tagName),
						range
					);

					// "Restore the values from values."
					restoreValues(values, range);

					// "Continue this loop from the beginning."
					continue;
				}

				// "Wrap sublist, with sibling criteria returning true for an HTML
				// element with local name tag name and false otherwise, and new
				// parent instructions being the following:"
				// . . .
				// "Fix disallowed ancestors of the previous step's result."
				fixDisallowedAncestors(wrap(
					sublist,
					makeIsElementPred(tagName),
					makeCreateElementSublist(tagName, sublist, range),
					range
				), range);
			}
		}
	}


	//@}
	///// Justifying the selection /////
	//@{

	function justifySelection(alignment, range) {

		// "Block-extend the active range, and let new range be the result."
		var newRange = blockExtend(range);

		// "Let element list be a list of all editable Elements contained in new
		// range that either has an attribute in the HTML namespace whose local
		// name is "align", or has a style attribute that sets "text-align", or is
		// a center."
		var elementList = getAllContainedNodes(newRange, function (node) {
			return node.nodeType == $_.Node.ELEMENT_NODE && isEditable(node)
			// Ignoring namespaces here
				&& (hasAttribute(node, "align") || node.style.textAlign != "" || isNamedHtmlElement(node, 'center'));
		});

		// "For each element in element list:"
		var i;
		for (i = 0; i < elementList.length; i++) {
			var element = elementList[i];

			// "If element has an attribute in the HTML namespace whose local name
			// is "align", remove that attribute."
			element.removeAttribute("align");

			// "Unset the CSS property "text-align" on element, if it's set by a
			// style attribute."
			element.style.textAlign = "";
			if (element.getAttribute("style") == "") {
				element.removeAttribute("style");
			}

			// "If element is a div or span or center with no attributes, remove
			// it, preserving its descendants."
			if (isHtmlElementInArray(element, ["div", "span", "center"]) && !element.attributes.length) {
				removePreservingDescendants(element, range);
			}

			// "If element is a center with one or more attributes, set the tag
			// name of element to "div"."
			if (isNamedHtmlElement(element, 'center') && element.attributes.length) {
				setTagName(element, "div", range);
			}
		}

		// "Block-extend the active range, and let new range be the result."
		newRange = blockExtend(globalRange);

		// "Let node list be a list of nodes, initially empty."
		var nodeList = [];

		// "For each node node contained in new range, append node to node list if
		// the last member of node list (if any) is not an ancestor of node; node
		// is editable; node is an allowed child of "div"; and node's alignment
		// value is not alignment."
		nodeList = getContainedNodes(newRange, function (node) {
			return isEditable(node) && isAllowedChild(node, "div") && getAlignmentValue(node) != alignment;
		});

		function makeIsAlignedDiv(alignment) {
			return function (node) {
				return isNamedHtmlElement(node, 'div') && $_(node.attributes).every(function (attr) {
					return (attr.name == "align" && attr.value.toLowerCase() == alignment) || (attr.name == "style" && getStyleLength(node) == 1 && node.style.textAlign == alignment);
				});
			};
		}

		function makeCreateAlignedDiv(alignment) {
			return function () {
				var newParent = document.createElement("div");
				newParent.setAttribute("style", "text-align: " + alignment);
				return newParent;
			};
		}

		// "While node list is not empty:"
		while (nodeList.length) {
			// "Let sublist be a list of nodes, initially empty."
			var sublist = [];

			// "Remove the first member of node list and append it to sublist."
			sublist.push(nodeList.shift());

			// "While node list is not empty, and the first member of node list is
			// the nextSibling of the last member of sublist, remove the first
			// member of node list and append it to sublist."
			while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
				sublist.push(nodeList.shift());
			}

			// "Wrap sublist. Sibling criteria returns true for any div that has
			// one or both of the following two attributes and no other attributes,
			// and false otherwise:"
			//
			//   * "An align attribute whose value is an ASCII case-insensitive
			//     match for alignment.
			//   * "A style attribute which sets exactly one CSS property
			//     (including unrecognized or invalid attributes), which is
			//     "text-align", which is set to alignment.
			//
			// "New parent instructions are to call createElement("div") on the
			// context object, then set its CSS property "text-align" to alignment
			// and return the result."
			wrap(
				sublist,
				makeIsAlignedDiv(alignment),
				makeCreateAlignedDiv(alignment),
				range
			);
		}
	}

	//@}
	///// Move the given collapsed range over adjacent zero-width whitespace characters.
	///// The range is
	//@{
	/**
	 * Move the given collapsed range over adjacent zero-width whitespace characters.
	 * If the range is not collapsed or is not contained in a text node, it is not modified
	 * @param range range to modify
	 * @param forward {Boolean} true to move forward, false to move backward
	 */
	function moveOverZWSP(range, forward) {
		var offset;
		if (!range.collapsed) {
			return;
		}

		offset = range.startOffset;

		if (forward) {
			// check whether the range starts in a text node
			if (range.startContainer && range.startContainer.nodeType === $_.Node.TEXT_NODE) {
				// move forward (i.e. increase offset) as long as we stay in the text node and have zwsp characters to the right
				while (offset < range.startContainer.data.length && range.startContainer.data.charAt(offset) === '\u200b') {
					offset++;
				}
			}
		} else {
			// check whether the range starts in a text node
			if (range.startContainer && range.startContainer.nodeType === $_.Node.TEXT_NODE) {
				// move backward (i.e. decrease offset) as long as we stay in the text node and have zwsp characters to the left
				while (offset > 0 && range.startContainer.data.charAt(offset - 1) === '\u200b') {
					offset--;
				}
			}
		}

		// if the offset was changed, set it back to the collapsed range
		if (offset !== range.startOffset) {
			range.setStart(range.startContainer, offset);
			range.setEnd(range.startContainer, offset);
		}
	}

	/**
	 * implementation of the delete command
	 * will attempt to delete contents within range if non-collapsed
	 * or delete the character left of the cursor position if range
	 * is collapsed. Is used to define the behaviour of the backspace
	 * button.
	 *
	 * @param      value   is just there for compatibility with the commands api. parameter is ignored.
	 * @param      range   the range to execute the delete command for
	 * @return     void
	 */
	commands["delete"] = {
		action: function (value, range) {
			var i;

			// special behaviour for skipping zero-width whitespaces in IE7
			if (jQuery.browser.msie && jQuery.browser.version <= 7) {
				moveOverZWSP(range, false);
			}

			// "If the active range is not collapsed, delete the contents of the
			// active range and abort these steps."
			if (!range.collapsed) {
				deleteContents(range);
				return;
			}

			// "Canonicalize whitespace at (active range's start node, active
			// range's start offset)."
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;
			var isBr = false;
			var isHr = false;

			// "Repeat the following steps:"
			while (true) {
				// we need to reset isBr and isHr on every interation of the loop
				if (offset > 0) {
					isBr = isNamedHtmlElement(node.childNodes[offset - 1], "br") || false;
					isHr = isNamedHtmlElement(node.childNodes[offset - 1], "hr") || false;
				}
				// "If offset is zero and node's previousSibling is an editable
				// invisible node, remove node's previousSibling from its parent."
				if (offset == 0 && isEditable(node.previousSibling) && isInvisible(node.previousSibling)) {
					node.parentNode.removeChild(node.previousSibling);
					continue;
				}
				// "Otherwise, if node has a child with index offset − 1 and that
				// child is an editable invisible node, remove that child from
				// node, then subtract one from offset."
				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isEditable(node.childNodes[offset - 1]) && (isInvisible(node.childNodes[offset - 1]) || isBr || isHr)) {
					node.removeChild(node.childNodes[offset - 1]);
					offset--;
					if (isBr || isHr) {
						range.setStart(node, offset);
						range.setEnd(node, offset);
						return;
					}
					continue;

				}
				// "Otherwise, if offset is zero and node is an inline node, or if
				// node is an invisible node, set offset to the index of node, then
				// set node to its parent."
				if ((offset == 0 && isInlineNode(node)) || isInvisible(node)) {
					offset = Dom.getIndexInParent(node);
					node = node.parentNode;
					continue;
				}
				// "Otherwise, if node has a child with index offset − 1 and that
				// child is an editable a, remove that child from node, preserving
				// its descendants. Then abort these steps."
				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isEditable(node.childNodes[offset - 1]) && isNamedHtmlElement(node.childNodes[offset - 1], "a")) {
					removePreservingDescendants(node.childNodes[offset - 1], range);
					return;

				}
				// "Otherwise, if node has a child with index offset − 1 and that
				// child is not a block node or a br or an img, set node to that
				// child, then set offset to the length of node."
				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && !isBlockNode(node.childNodes[offset - 1]) && !isHtmlElementInArray(node.childNodes[offset - 1], ["br", "img"])) {
					node = node.childNodes[offset - 1];
					offset = getNodeLength(node);
					continue;
				}
				// "Otherwise, break from this loop."
				// brk is a quick and dirty jslint workaround since I don't want to rewrite this loop
				var brk = true;
				if (brk) {
					break;
				}
			}

			// if the previous node is an aloha-table we want to delete it
			var delBlock = getBlockAtPreviousPosition(node, offset);
			if (delBlock) {
				delBlock.parentNode.removeChild(delBlock);
				return;
			}

			// "If node is a Text node and offset is not zero, call collapse(node,
			// offset) on the Selection. Then delete the contents of the range with
			// start (node, offset − 1) and end (node, offset) and abort these
			// steps."
			if (node.nodeType == $_.Node.TEXT_NODE && offset != 0) {
				range.setStart(node, offset - 1);
				range.setEnd(node, offset - 1);
				deleteContents(node, offset - 1, node, offset);
				return;
			}

			// @iebug
			// when inserting a special char via the plugin
			// there where problems deleting them again with backspace after insertation
			// see https://github.com/alohaeditor/Aloha-Editor/issues/517
			if (node.nodeType == $_.Node.TEXT_NODE && offset == 0 && jQuery.browser.msie) {
				offset = 1;
				range.setStart(node, offset);
				range.setEnd(node, offset);
				range.startOffset = 0;
				deleteContents(range);
				return;
			}

			// "If node is an inline node, abort these steps."
			if (isInlineNode(node)) {
				return;
			}

			// "If node has a child with index offset − 1 and that child is a br or
			// hr or img, call collapse(node, offset) on the Selection. Then delete
			// the contents of the range with start (node, offset − 1) and end
			// (node, offset) and abort these steps."
			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isHtmlElementInArray(node.childNodes[offset - 1], ["br", "hr", "img"])) {
				range.setStart(node, offset);
				range.setEnd(node, offset);
				deleteContents(range);
				return;
			}

			// "If node is an li or dt or dd and is the first child of its parent,
			// and offset is zero:"
			if (isHtmlElementInArray(node, ["li", "dt", "dd"]) && node == node.parentNode.firstChild && offset == 0) {
				// "Let items be a list of all lis that are ancestors of node."
				//
				// Remember, must be in tree order.
				var items = [];
				var ancestor;
				for (ancestor = node.parentNode; ancestor; ancestor = ancestor.parentNode) {
					if (isNamedHtmlElement(ancestor, 'li')) {
						items.unshift(ancestor);
					}
				}

				// "Normalize sublists of each item in items."
				for (i = 0; i < items.length; i++) {
					normalizeSublists(items[i], range);
				}

				// "Record the values of the one-node list consisting of node, and
				// let values be the result."
				var values = recordValues([node]);

				// "Split the parent of the one-node list consisting of node."
				splitParent([node], range);

				// "Restore the values from values."
				restoreValues(values, range);

				// "If node is a dd or dt, and it is not an allowed child of any of
				// its ancestors in the same editing host, set the tag name of node
				// to the default single-line container name and let node be the
				// result."
				if (isHtmlElementInArray(node, ["dd", "dt"]) && $_(getAncestors(node)).every(function (ancestor) { return !inSameEditingHost(node, ancestor) || !isAllowedChild(node, ancestor); })) {
					node = setTagName(node, defaultSingleLineContainerName, range);
				}

				// "Fix disallowed ancestors of node."
				fixDisallowedAncestors(node, range);

				// fix the lists to be html5 conformant
				for (i = 0; i < items.length; i++) {
					unNormalizeSublists(items[i].parentNode, range);
				}

				// "Abort these steps."
				return;
			}

			// "Let start node equal node and let start offset equal offset."
			var startNode = node;
			var startOffset = offset;

			// "Repeat the following steps:"
			while (true) {
				// "If start offset is zero, set start offset to the index of start
				// node and then set start node to its parent."
				if (startOffset == 0) {
					startOffset = Dom.getIndexInParent(startNode);
					startNode = startNode.parentNode;

					// "Otherwise, if start node has an editable invisible child with
					// index start offset minus one, remove it from start node and
					// subtract one from start offset."
				} else if (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length && isEditable(startNode.childNodes[startOffset - 1]) && isInvisible(startNode.childNodes[startOffset - 1])) {
					startNode.removeChild(startNode.childNodes[startOffset - 1]);
					startOffset--;

					// "Otherwise, break from this loop."
				} else {
					break;
				}
			}

			// "If offset is zero, and node has an editable ancestor container in
			// the same editing host that's an indentation element:"
			if (offset == 0 && $_(getAncestors(node).concat(node)).filter(function (ancestor) { return isEditable(ancestor) && inSameEditingHost(ancestor, node) && isIndentationElement(ancestor); }).length) {
				// "Block-extend the range whose start and end are both (node, 0),
				// and let new range be the result."
				var newRange = Aloha.createRange();
				newRange.setStart(node, 0);
				newRange.setEnd(node, 0);
				newRange = blockExtend(newRange);

				// "Let node list be a list of nodes, initially empty."
				//
				// "For each node current node contained in new range, append
				// current node to node list if the last member of node list (if
				// any) is not an ancestor of current node, and current node is
				// editable but has no editable descendants."
				var nodeList = getContainedNodes(newRange, function (currentNode) {
					return isEditable(currentNode) && !hasEditableDescendants(currentNode);
				});

				// "Outdent each node in node list."
				for (i = 0; i < nodeList.length; i++) {
					outdentNode(nodeList[i], range);
				}

				// "Abort these steps."
				return;
			}

			// "If the child of start node with index start offset is a table,
			// abort these steps."
			if (isNamedHtmlElement(startNode.childNodes[startOffset], "table")) {
				return;
			}

			// "If start node has a child with index start offset − 1, and that
			// child is a table:"
			if (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length && isNamedHtmlElement(startNode.childNodes[startOffset - 1], "table")) {
				// "Call collapse(start node, start offset − 1) on the context
				// object's Selection."
				range.setStart(startNode, startOffset - 1);

				// "Call extend(start node, start offset) on the context object's
				// Selection."
				range.setEnd(startNode, startOffset);

				// "Abort these steps."
				return;
			}

			// "If offset is zero; and either the child of start node with index
			// start offset minus one is an hr, or the child is a br whose
			// previousSibling is either a br or not an inline node:"
			if (offset == 0
				    && (isNamedHtmlElement(startNode.childNodes[startOffset - 1], "hr")
						|| (isNamedHtmlElement(startNode.childNodes[startOffset - 1], "br")
							&& (isNamedHtmlElement(startNode.childNodes[startOffset - 1].previousSibling, "br")
								|| !isInlineNode(startNode.childNodes[startOffset - 1].previousSibling))))) {
				// "Call collapse(node, offset) on the Selection."
				range.setStart(node, offset);
				range.setEnd(node, offset);

				// "Delete the contents of the range with start (start node, start
				// offset − 1) and end (start node, start offset)."
				deleteContents(startNode, startOffset - 1, startNode, startOffset);

				// "Abort these steps."
				return;
			}

			// "If the child of start node with index start offset is an li or dt
			// or dd, and that child's firstChild is an inline node, and start
			// offset is not zero:"
			if (isHtmlElementInArray(startNode.childNodes[startOffset], ["li", "dt", "dd"]) && isInlineNode(startNode.childNodes[startOffset].firstChild) && startOffset != 0) {
				// "Let previous item be the child of start node with index start
				// offset minus one."
				var previousItem = startNode.childNodes[startOffset - 1];

				// "If previous item's lastChild is an inline node other than a br,
				// call createElement("br") on the context object and append the
				// result as the last child of previous item."
				if (isInlineNode(previousItem.lastChild) && !isNamedHtmlElement(previousItem.lastChild, "br")) {
					previousItem.appendChild(document.createElement("br"));
				}

				// "If previous item's lastChild is an inline node, call
				// createElement("br") on the context object and append the result
				// as the last child of previous item."
				if (isInlineNode(previousItem.lastChild)) {
					previousItem.appendChild(document.createElement("br"));
				}
			}

			// "If the child of start node with index start offset is an li or dt
			// or dd, and its previousSibling is also an li or dt or dd, set start
			// node to its child with index start offset − 1, then set start offset
			// to start node's length, then set node to start node's nextSibling,
			// then set offset to 0."
			if (isHtmlElementInArray(startNode.childNodes[startOffset], ["li", "dt", "dd"]) && isHtmlElementInArray(startNode.childNodes[startOffset - 1], ["li", "dt", "dd"])) {
				startNode = startNode.childNodes[startOffset - 1];
				startOffset = getNodeLength(startNode);
				node = startNode.nextSibling;
				offset = 0;

				// "Otherwise, while start node has a child with index start offset
				// minus one:"
			} else {
				while (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length) {
					// "If start node's child with index start offset minus one is
					// editable and invisible, remove it from start node, then
					// subtract one from start offset."
					if (isEditable(startNode.childNodes[startOffset - 1]) && isInvisible(startNode.childNodes[startOffset - 1])) {
						startNode.removeChild(startNode.childNodes[startOffset - 1]);
						startOffset--;

						// "Otherwise, set start node to its child with index start
						// offset minus one, then set start offset to the length of
						// start node."
					} else {
						startNode = startNode.childNodes[startOffset - 1];
						startOffset = getNodeLength(startNode);
					}
				}
			}

			// "Delete the contents of the range with start (start node, start
			// offset) and end (node, offset)."
			var delRange = Aloha.createRange();
			delRange.setStart(startNode, startOffset);
			delRange.setEnd(node, offset);
			deleteContents(delRange);

			if (!isAncestorContainer(document.body, range.startContainer)) {
				if (delRange.startContainer.hasChildNodes()
						|| delRange.startContainer.nodeType == $_.Node.TEXT_NODE
							|| isEditingHost(delRange.startContainer)) {
					range.setStart(delRange.startContainer, delRange.startOffset);
					range.setEnd(delRange.startContainer, delRange.startOffset);
				} else {
					range.setStart(delRange.startContainer.parentNode, Dom.getIndexInParent(delRange.startContainer));
					range.setEnd(delRange.startContainer.parentNode, Dom.getIndexInParent(delRange.startContainer));
				}
			}
		}
	};

	//@}
	///// The formatBlock command /////
	//@{
	// "A formattable block name is "address", "dd", "div", "dt", "h1", "h2", "h3",
	// "h4", "h5", "h6", "p", or "pre"."
	var formattableBlockNames = ["address", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"];

	commands.formatblock = {
		action: function (value) {
			var i;

			// "If value begins with a "<" character and ends with a ">" character,
			// remove the first and last characters from it."
			if (/^<.*>$/.test(value)) {
				value = value.slice(1, -1);
			}

			// "Let value be converted to ASCII lowercase."
			value = value.toLowerCase();

			// "If value is not a formattable block name, abort these steps and do
			// nothing."
			if ($_(formattableBlockNames).indexOf(value) == -1) {
				return;
			}

			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be an empty list of nodes."
			//
			// "For each node node contained in new range, append node to node list
			// if it is editable, the last member of original node list (if any) is
			// not an ancestor of node, node is either a non-list single-line
			// container or an allowed child of "p" or a dd or dt, and node is not
			// the ancestor of a prohibited paragraph child."
			var nodeList = getContainedNodes(newRange, function (node) {
				return isEditable(node) && (isNonListSingleLineContainer(node) || isAllowedChild(node, "p") || isHtmlElementInArray(node, ["dd", "dt"])) && !$_(getDescendants(node)).some(isProhibitedParagraphChild);
			});

			// "Record the values of node list, and let values be the result."
			var values = recordValues(nodeList);

			function makeIsEditableElementInSameEditingHostDoesNotContainProhibitedParagraphChildren(node) {
				return function (ancestor) {
					return (isEditable(ancestor)
							&& inSameEditingHost(ancestor, node)
							&& isHtmlElement_obsolete(ancestor, formattableBlockNames)
							&& !$_(getDescendants(ancestor)).some(isProhibitedParagraphChild));
				};
			}

			function makeIsElementWithoutAttributes(value) {
				return function (node) {
					return isHtmlElement_obsolete(node, value) && !node.attributes.length;
				};
			}

			function returnFalse() {
				return false;
			}

			function makeCreateElement(value) {
				return function () {
					return document.createElement(value);
				};
			}

			// "For each node in node list, while node is the descendant of an
			// editable HTML element in the same editing host, whose local name is
			// a formattable block name, and which is not the ancestor of a
			// prohibited paragraph child, split the parent of the one-node list
			// consisting of node."
			for (i = 0; i < nodeList.length; i++) {
				var node = nodeList[i];
				while ($_(getAncestors(node)).some(makeIsEditableElementInSameEditingHostDoesNotContainProhibitedParagraphChildren(node))) {
					splitParent([node], newRange);
				}
			}

			// "Restore the values from values."
			restoreValues(values, newRange);

			// "While node list is not empty:"
			while (nodeList.length) {
				var sublist;

				// "If the first member of node list is a single-line
				// container:"
				if (isSingleLineContainer(nodeList[0])) {
					// "Let sublist be the children of the first member of node
					// list."
					sublist = [].slice.call(toArray(nodeList[0].childNodes));

					// "Record the values of sublist, and let values be the
					// result."
					values = recordValues(sublist);

					// "Remove the first member of node list from its parent,
					// preserving its descendants."
					removePreservingDescendants(nodeList[0], newRange);

					// "Restore the values from values."
					restoreValues(values, newRange);

					// "Remove the first member from node list."
					nodeList.shift();

					// "Otherwise:"
				} else {
					// "Let sublist be an empty list of nodes."
					sublist = [];

					// "Remove the first member of node list and append it to
					// sublist."
					sublist.push(nodeList.shift());

					// "While node list is not empty, and the first member of
					// node list is the nextSibling of the last member of
					// sublist, and the first member of node list is not a
					// single-line container, and the last member of sublist is
					// not a br, remove the first member of node list and
					// append it to sublist."
					while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isSingleLineContainer(nodeList[0]) && !isNamedHtmlElement(sublist[sublist.length - 1], "BR")) {
						sublist.push(nodeList.shift());
					}
				}

				// "Wrap sublist. If value is "div" or "p", sibling criteria
				// returns false; otherwise it returns true for an HTML element
				// with local name value and no attributes, and false otherwise.
				// New parent instructions return the result of running
				// createElement(value) on the context object. Then fix disallowed
				// ancestors of the result."
				fixDisallowedAncestors(wrap(
					sublist,
					jQuery.inArray(value, ["div", "p"]) == -1 ? makeIsElementWithoutAttributes(value) : returnFalse,
					makeCreateElement(value),
					newRange
				), newRange);
			}
		},
		indeterm: function () {
			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be all visible editable nodes that are contained in
			// new range and have no children."
			var nodeList = getAllContainedNodes(newRange, function (node) {
				return isVisible(node) && isEditable(node) && !node.hasChildNodes();
			});

			// "If node list is empty, return false."
			if (!nodeList.length) {
				return false;
			}

			// "Let type be null."
			var type = null;

			// "For each node in node list:"
			var i;
			for (i = 0; i < nodeList.length; i++) {
				var node = nodeList[i];

				// "While node's parent is editable and in the same editing host as
				// node, and node is not an HTML element whose local name is a
				// formattable block name, set node to its parent."
				while (isEditable(node.parentNode) && inSameEditingHost(node, node.parentNode) && !isHtmlElement_obsolete(node, formattableBlockNames)) {
					node = node.parentNode;
				}

				// "Let current type be the empty string."
				var currentType = "";

				// "If node is an editable HTML element whose local name is a
				// formattable block name, and node is not the ancestor of a
				// prohibited paragraph child, set current type to node's local
				// name."
				if (isEditable(node) && isHtmlElement_obsolete(node, formattableBlockNames) && !$_(getDescendants(node)).some(isProhibitedParagraphChild)) {
					currentType = node.tagName;
				}

				// "If type is null, set type to current type."
				if (type === null) {
					type = currentType;

					// "Otherwise, if type does not equal current type, return true."
				} else if (type != currentType) {
					return true;
				}
			}

			// "Return false."
			return false;
		},
		value: function () {
			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node be the first visible editable node that is contained in
			// new range and has no children. If there is no such node, return the
			// empty string."
			var nodes = getAllContainedNodes(newRange, function (node) {
				return isVisible(node) && isEditable(node) && !node.hasChildNodes();
			});
			if (!nodes.length) {
				return "";
			}
			var node = nodes[0];

			// "While node's parent is editable and in the same editing host as
			// node, and node is not an HTML element whose local name is a
			// formattable block name, set node to its parent."
			while (isEditable(node.parentNode) && inSameEditingHost(node, node.parentNode) && !isHtmlElement_obsolete(node, formattableBlockNames)) {
				node = node.parentNode;
			}

			// "If node is an editable HTML element whose local name is a
			// formattable block name, and node is not the ancestor of a prohibited
			// paragraph child, return node's local name, converted to ASCII
			// lowercase."
			if (isEditable(node) && isHtmlElement_obsolete(node, formattableBlockNames) && !$_(getDescendants(node)).some(isProhibitedParagraphChild)) {
				return node.tagName.toLowerCase();
			}

			// "Return the empty string."
			return "";
		}
	};

	//@}
	///// The forwardDelete command /////
	//@{
	commands.forwarddelete = {
		action: function (value, range) {
			// special behaviour for skipping zero-width whitespaces in IE7
			if (jQuery.browser.msie && jQuery.browser.version <= 7) {
				moveOverZWSP(range, true);
			}

			// "If the active range is not collapsed, delete the contents of the
			// active range and abort these steps."
			if (!range.collapsed) {
				deleteContents(range);
				return;
			}

			// "Canonicalize whitespace at (active range's start node, active
			// range's start offset)."
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;
			var isBr = false;
			var isHr = false;

			// "Repeat the following steps:"
			while (true) {
				// check whether the next element is a br or hr
				// Commented out for unknown reason.
				//if (offset < node.childNodes.length) {
				//				isBr = isHtmlElement_obsolete(node.childNodes[offset], "br") || false;
				//				isHr = isHtmlElement_obsolete(node.childNodes[offset], "hr") || false;
				//}

				// "If offset is the length of node and node's nextSibling is an
				// editable invisible node, remove node's nextSibling from its
				// parent."
				if (offset == getNodeLength(node) && isEditable(node.nextSibling) && isInvisible(node.nextSibling)) {
					node.parentNode.removeChild(node.nextSibling);

					// "Otherwise, if node has a child with index offset and that child
					// is an editable invisible node, remove that child from node."
				} else if (offset < node.childNodes.length && isEditable(node.childNodes[offset]) && (isInvisible(node.childNodes[offset]) || isBr || isHr)) {
					node.removeChild(node.childNodes[offset]);
					if (isBr || isHr) {
						ensureContainerEditable(node);
						range.setStart(node, offset);
						range.setEnd(node, offset);
						return;
					}

					// "Otherwise, if node has a child with index offset and that child
					// is a collapsed block prop, add one to offset."
				} else if (offset < node.childNodes.length && isCollapsedBlockProp(node.childNodes[offset])) {
					offset++;

					// "Otherwise, if offset is the length of node and node is an
					// inline node, or if node is invisible, set offset to one plus the
					// index of node, then set node to its parent."
				} else if ((offset == getNodeLength(node) && isInlineNode(node)) || isInvisible(node)) {
					offset = 1 + Dom.getIndexInParent(node);
					node = node.parentNode;

					// "Otherwise, if node has a child with index offset and that child
					// is not a block node or a br or an img, set node to that child,
					// then set offset to zero."
				} else if (offset < node.childNodes.length && !isBlockNode(node.childNodes[offset]) && !isHtmlElementInArray(node.childNodes[offset], ["br", "img"])) {
					node = node.childNodes[offset];
					offset = 0;

					// "Otherwise, break from this loop."
				} else {
					break;
				}
			}

			// collapse whitespace in the node, if it is a text node
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			// if the next node is an aloha-table we want to delete it
			var delBlock = getBlockAtNextPosition(node, offset);
			if (delBlock) {
				delBlock.parentNode.removeChild(delBlock);
				return;
			}

			var endOffset;
			// "If node is a Text node and offset is not node's length:"
			if (node.nodeType == $_.Node.TEXT_NODE && offset != getNodeLength(node)) {
				// "Call collapse(node, offset) on the Selection."
				range.setStart(node, offset);
				range.setEnd(node, offset);

				// "Let end offset be offset plus one."
				endOffset = offset + 1;

				// "While end offset is not node's length and the end offsetth
				// element of node's data has general category M when interpreted
				// as a Unicode code point, add one to end offset."
				//
				// TODO: Not even going to try handling anything beyond the most
				// basic combining marks, since I couldn't find a good list.  I
				// special-case a few Hebrew diacritics too to test basic coverage
				// of non-Latin stuff.
				while (endOffset != node.length && /^[\u0300-\u036f\u0591-\u05bd\u05c1\u05c2]$/.test(node.data[endOffset])) {
					endOffset++;
				}

				// "Delete the contents of the range with start (node, offset) and
				// end (node, end offset)."
				deleteContents(node, offset, node, endOffset);

				// "Abort these steps."
				return;
			}

			// "If node is an inline node, abort these steps."
			if (isInlineNode(node)) {
				return;
			}

			// "If node has a child with index offset and that child is a br or hr
			// or img, call collapse(node, offset) on the Selection. Then delete
			// the contents of the range with start (node, offset) and end (node,
			// offset + 1) and abort these steps."
			if (offset < node.childNodes.length && isHtmlElementInArray(node.childNodes[offset], ["br", "hr", "img"])) {
				range.setStart(node, offset);
				range.setEnd(node, offset);
				deleteContents(node, offset, node, offset + 1);
				return;
			}

			// "Let end node equal node and let end offset equal offset."
			var endNode = node;
			endOffset = offset;

			// "Repeat the following steps:"
			while (true) {
				// "If end offset is the length of end node, set end offset to one
				// plus the index of end node and then set end node to its parent."
				if (endOffset == getNodeLength(endNode)) {
					endOffset = 1 + Dom.getIndexInParent(endNode);
					endNode = endNode.parentNode;

					// "Otherwise, if end node has a an editable invisible child with
					// index end offset, remove it from end node."
				} else if (endOffset < endNode.childNodes.length && isEditable(endNode.childNodes[endOffset]) && isInvisible(endNode.childNodes[endOffset])) {
					endNode.removeChild(endNode.childNodes[endOffset]);

					// "Otherwise, break from this loop."
				} else {
					break;
				}
			}

			// "If the child of end node with index end offset minus one is a
			// table, abort these steps."
			if (isNamedHtmlElement(endNode.childNodes[endOffset - 1], "table")) {
				return;
			}

			// "If the child of end node with index end offset is a table:"
			if (isNamedHtmlElement(endNode.childNodes[endOffset], "table")) {
				// "Call collapse(end node, end offset) on the context object's
				// Selection."
				range.setStart(endNode, endOffset);

				// "Call extend(end node, end offset + 1) on the context object's
				// Selection."
				range.setEnd(endNode, endOffset + 1);

				// "Abort these steps."
				return;
			}

			// "If offset is the length of node, and the child of end node with
			// index end offset is an hr or br:"
			if (offset == getNodeLength(node) && isHtmlElementInArray(endNode.childNodes[endOffset], ["br", "hr"])) {
				// "Call collapse(node, offset) on the Selection."
				range.setStart(node, offset);
				range.setEnd(node, offset);

				// "Delete the contents of the range with end (end node, end
				// offset) and end (end node, end offset + 1)."
				deleteContents(endNode, endOffset, endNode, endOffset + 1);

				// "Abort these steps."
				return;
			}

			// "While end node has a child with index end offset:"
			while (endOffset < endNode.childNodes.length) {
				// "If end node's child with index end offset is editable and
				// invisible, remove it from end node."
				if (isEditable(endNode.childNodes[endOffset]) && isInvisible(endNode.childNodes[endOffset])) {
					endNode.removeChild(endNode.childNodes[endOffset]);

					// "Otherwise, set end node to its child with index end offset and
					// set end offset to zero."
				} else {
					endNode = endNode.childNodes[endOffset];
					endOffset = 0;
				}
			}

			// "Delete the contents of the range with start (node, offset) and end
			// (end node, end offset)."
			var newRange = deleteContents(node, offset, endNode, endOffset);
			range.setStart(newRange.startContainer, newRange.startOffset);
			range.setEnd(newRange.endContainer, newRange.endOffset);
		}
	};

	//@}
	///// The indent command /////
	//@{
	commands.indent = {
		action: function () {
			// "Let items be a list of all lis that are ancestor containers of the
			// active range's start and/or end node."
			//
			// Has to be in tree order, remember!
			var items = [];
			var node;
			for (node = getActiveRange().endContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
				if (isNamedHtmlElement(node, "LI")) {
					items.unshift(node);
				}
			}
			for (node = getActiveRange().startContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
				if (isNamedHtmlElement(node, "LI")) {
					items.unshift(node);
				}
			}
			for (node = getActiveRange().commonAncestorContainer; node; node = node.parentNode) {
				if (isNamedHtmlElement(node, "LI")) {
					items.unshift(node);
				}
			}

			// "For each item in items, normalize sublists of item."
			var i;
			for (i = 0; i < items.length; i++) {
				normalizeSublists(items[i], getActiveRange());
			}

			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be a list of nodes, initially empty."
			var nodeList = [];

			// "For each node node contained in new range, if node is editable and
			// is an allowed child of "div" or "ol" and if the last member of node
			// list (if any) is not an ancestor of node, append node to node list."
			nodeList = getContainedNodes(newRange, function (node) {
				return isEditable(node) && (isAllowedChild(node, "div") || isAllowedChild(node, "ol"));
			});

			// "If the first member of node list is an li whose parent is an ol or
			// ul, and its previousSibling is an li as well, normalize sublists of
			// its previousSibling."
			if (nodeList.length && isNamedHtmlElement(nodeList[0], "LI") && isHtmlElementInArray(nodeList[0].parentNode, ["OL", "UL"]) && isNamedHtmlElement(nodeList[0].previousSibling, "LI")) {
				normalizeSublists(nodeList[0].previousSibling, newRange);
			}

			// "While node list is not empty:"
			while (nodeList.length) {
				// "Let sublist be a list of nodes, initially empty."
				var sublist = [];

				// "Remove the first member of node list and append it to sublist."
				sublist.push(nodeList.shift());

				// "While the first member of node list is the nextSibling of the
				// last member of sublist, remove the first member of node list and
				// append it to sublist."
				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
					sublist.push(nodeList.shift());
				}

				// "Indent sublist."
				indentNodes(sublist, newRange);
			}
		}
	};

	//@}
	///// The insertHorizontalRule command /////
	//@{
	commands.inserthorizontalrule = {
		action: function (value, range) {

			// "While range's start offset is 0 and its start node's parent is not
			// null, set range's start to (parent of start node, index of start
			// node)."
			while (range.startOffset == 0 && range.startContainer.parentNode) {
				range.setStart(range.startContainer.parentNode, Dom.getIndexInParent(range.startContainer));
			}

			// "While range's end offset is the length of its end node, and its end
			// node's parent is not null, set range's end to (parent of end node, 1
			// + index of start node)."
			while (range.endOffset == getNodeLength(range.endContainer) && range.endContainer.parentNode) {
				range.setEnd(range.endContainer.parentNode, 1 + Dom.getIndexInParent(range.endContainer));
			}

			// "Delete the contents of range, with block merging false."
			deleteContents(range, {
				blockMerging: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(getActiveRange().startContainer) && !isEditingHost(getActiveRange().startContainer)) {
				return;
			}

			// "If the active range's start node is a Text node and its start
			// offset is zero, set the active range's start and end to (parent of
			// start node, index of start node)."
			if (getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().startOffset == 0) {
				getActiveRange().setStart(getActiveRange().startContainer.parentNode, Dom.getIndexInParent(getActiveRange().startContainer));
				getActiveRange().collapse(true);
			}

			// "If the active range's start node is a Text node and its start
			// offset is the length of its start node, set the active range's start
			// and end to (parent of start node, 1 + index of start node)."
			if (getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE && getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {
				getActiveRange().setStart(getActiveRange().startContainer.parentNode, 1 + Dom.getIndexInParent(getActiveRange().startContainer));
				getActiveRange().collapse(true);
			}

			// "Let hr be the result of calling createElement("hr") on the
			// context object."
			var hr = document.createElement("hr");

			// "Run insertNode(hr) on the range."
			range.insertNode(hr);

			// "Fix disallowed ancestors of hr."
			fixDisallowedAncestors(hr, range);

			// "Run collapse() on the Selection, with first argument equal to the
			// parent of hr and the second argument equal to one plus the index of
			// hr."
			//
			// Not everyone actually supports collapse(), so we do it manually
			// instead.  Also, we need to modify the actual range we're given as
			// well, for the sake of autoimplementation.html's range-filling-in.
			range.setStart(hr.parentNode, 1 + Dom.getIndexInParent(hr));
			range.setEnd(hr.parentNode, 1 + Dom.getIndexInParent(hr));
			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(range);
		}
	};

	//@}
	///// The insertHTML command /////
	//@{
	commands.inserthtml = {
		action: function (value, range) {


			// "Delete the contents of the active range."
			deleteContents(range);

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			// "Let frag be the result of calling createContextualFragment(value)
			// on the active range."
			var frag = range.createContextualFragment(value);

			// "Let last child be the lastChild of frag."
			var lastChild = frag.lastChild;

			// "If last child is null, abort these steps."
			if (!lastChild) {
				return;
			}

			// "Let descendants be all descendants of frag."
			var descendants = getDescendants(frag);

			// "If the active range's start node is a block node:"
			if (isBlockNode(range.startContainer)) {
				// "Let collapsed block props be all editable collapsed block prop
				// children of the active range's start node that have index
				// greater than or equal to the active range's start offset."
				//
				// "For each node in collapsed block props, remove node from its
				// parent."
				$_(range.startContainer.childNodes).filter(function (node, range) {
					return isEditable(node) && isCollapsedBlockProp(node) && Dom.getIndexInParent(node) >= range.startOffset;
				}, true).forEach(function (node) {
					node.parentNode.removeChild(node);
				});
			}

			// "Call insertNode(frag) on the active range."
			range.insertNode(frag);

			// "If the active range's start node is a block node with no visible
			// children, call createElement("br") on the context object and append
			// the result as the last child of the active range's start node."
			if (isBlockNode(range.startContainer)) {
				ensureContainerEditable(range.startContainer);
			}

			// "Call collapse() on the context object's Selection, with last
			// child's parent as the first argument and one plus its index as the
			// second."
			range.setStart(lastChild.parentNode, 1 + Dom.getIndexInParent(lastChild));
			range.setEnd(lastChild.parentNode, 1 + Dom.getIndexInParent(lastChild));

			// "Fix disallowed ancestors of each member of descendants."
			var i;
			for (i = 0; i < descendants.length; i++) {
				fixDisallowedAncestors(descendants[i], range);
			}

			setActiveRange(range);
		}
	};

	//@}
	///// The insertImage command /////
	//@{
	commands.insertimage = {
		action: function (value) {
			// "If value is the empty string, abort these steps and do nothing."
			if (value === "") {
				return;
			}

			// "Let range be the active range."
			var range = getActiveRange();

			// "Delete the contents of range, with strip wrappers false."
			deleteContents(range, {
				stripWrappers: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(getActiveRange().startContainer) && !isEditingHost(getActiveRange().startContainer)) {
				return;
			}

			// "If range's start node is a block node whose sole child is a br, and
			// its start offset is 0, remove its start node's child from it."
			if (isBlockNode(range.startContainer) && range.startContainer.childNodes.length == 1 && isNamedHtmlElement(range.startContainer.firstChild, "br") && range.startOffset == 0) {
				range.startContainer.removeChild(range.startContainer.firstChild);
			}

			// "Let img be the result of calling createElement("img") on the
			// context object."
			var img = document.createElement("img");

			// "Run setAttribute("src", value) on img."
			img.setAttribute("src", value);

			// "Run insertNode(img) on the range."
			range.insertNode(img);

			// "Run collapse() on the Selection, with first argument equal to the
			// parent of img and the second argument equal to one plus the index of
			// img."
			//
			// Not everyone actually supports collapse(), so we do it manually
			// instead.  Also, we need to modify the actual range we're given as
			// well, for the sake of autoimplementation.html's range-filling-in.
			range.setStart(img.parentNode, 1 + Dom.getIndexInParent(img));
			range.setEnd(img.parentNode, 1 + Dom.getIndexInParent(img));
			Aloha.getSelection().removeAllRanges();
			Aloha.getSelection().addRange(range);

			// IE adds width and height attributes for some reason, so remove those
			// to actually do what the spec says.
			img.removeAttribute("width");
			img.removeAttribute("height");
		}
	};

	//@}
	///// The insertLineBreak command /////
	//@{
	commands.insertlinebreak = {
		action: function (value, range) {
			// "Delete the contents of the active range, with strip wrappers false."
			deleteContents(range, {
				stripWrappers: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			// "If the active range's start node is an Element, and "br" is not an
			// allowed child of it, abort these steps."
			if (range.startContainer.nodeType == $_.Node.ELEMENT_NODE && !isAllowedChild("br", range.startContainer)) {
				return;
			}

			// "If the active range's start node is not an Element, and "br" is not
			// an allowed child of the active range's start node's parent, abort
			// these steps."
			if (range.startContainer.nodeType != $_.Node.ELEMENT_NODE && !isAllowedChild("br", range.startContainer.parentNode)) {
				return;
			}

			// "If the active range's start node is a Text node and its start
			// offset is zero, call collapse() on the context object's Selection,
			// with first argument equal to the active range's start node's parent
			// and second argument equal to the active range's start node's index."
			var newNode, newOffset;
			if (range.startContainer.nodeType == $_.Node.TEXT_NODE && range.startOffset == 0) {
				newNode = range.startContainer.parentNode;
				newOffset = Dom.getIndexInParent(range.startContainer);
				Aloha.getSelection().collapse(newNode, newOffset);
				range.setStart(newNode, newOffset);
				range.setEnd(newNode, newOffset);
			}

			// "If the active range's start node is a Text node and its start
			// offset is the length of its start node, call collapse() on the
			// context object's Selection, with first argument equal to the active
			// range's start node's parent and second argument equal to one plus
			// the active range's start node's index."
			if (range.startContainer.nodeType == $_.Node.TEXT_NODE && range.startOffset == getNodeLength(range.startContainer)) {
				newNode = range.startContainer.parentNode;
				newOffset = 1 + Dom.getIndexInParent(range.startContainer);
				Aloha.getSelection().collapse(newNode, newOffset);
				range.setStart(newNode, newOffset);
				range.setEnd(newNode, newOffset);
			}

			// "Let br be the result of calling createElement("br") on the context
			// object."
			var br = document.createElement("br");

			// "Call insertNode(br) on the active range."
			range.insertNode(br);

			// "Call collapse() on the context object's Selection, with br's parent
			// as the first argument and one plus br's index as the second
			// argument."
			Aloha.getSelection().collapse(br.parentNode, 1 + Dom.getIndexInParent(br));
			range.setStart(br.parentNode, 1 + Dom.getIndexInParent(br));
			range.setEnd(br.parentNode, 1 + Dom.getIndexInParent(br));

			// "If br is a collapsed line break, call createElement("br") on the
			// context object and let extra br be the result, then call
			// insertNode(extra br) on the active range."
			if (isCollapsedLineBreak(br)) {
				// TODO
				range.insertNode(createEndBreak());

				// Compensate for nonstandard implementations of insertNode
				Aloha.getSelection().collapse(br.parentNode, 1 + Dom.getIndexInParent(br));
				range.setStart(br.parentNode, 1 + Dom.getIndexInParent(br));
				range.setEnd(br.parentNode, 1 + Dom.getIndexInParent(br));
			}

			// IE7 is adding this styles: height: auto; min-height: 0px; max-height: none;
			// with that there is the ugly "IE-editable-outline"
			if (jQuery.browser.msie && jQuery.browser.version < 8) {
				br.parentNode.removeAttribute("style");
			}
		}
	};

	//@}
	///// The insertOrderedList command /////
	//@{
	commands.insertorderedlist = {
		// "Toggle lists with tag name "ol"."
		action: function (value, range) {
			toggleLists("ol", range);
		},
		// "True if the selection's list state is "mixed" or "mixed ol", false
		// otherwise."
		indeterm: function () {
			return (/^mixed( ol)?$/).test(getSelectionListState());
		},
		// "True if the selection's list state is "ol", false otherwise."
		state: function () {
			return getSelectionListState() == "ol";
		}
	};

	var listRelatedElements = {
		"LI": true,
		"DT": true,
		"DD": true
	};

	//@}
	///// The insertParagraph command /////
	//@{
	commands.insertparagraph = {
		action: function (value, range) {
			var i;

			// "Delete the contents of the active range."
			deleteContents(range);

			// clean lists in the editing host, this will remove any whitespace nodes around lists
			// because the following algorithm is not prepared to deal with them
			cleanLists(getEditingHostOf(range.startContainer), range);

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;

			// "If node is a Text node, and offset is neither 0 nor the length of
			// node, call splitText(offset) on node."
			if (node.nodeType == $_.Node.TEXT_NODE && offset != 0 && offset != getNodeLength(node)) {
				splitText(node, offset);
			}

			// "If node is a Text node and offset is its length, set offset to one
			// plus the index of node, then set node to its parent."
			if (node.nodeType == $_.Node.TEXT_NODE && offset == getNodeLength(node)) {
				offset = 1 + Dom.getIndexInParent(node);
				node = node.parentNode;
			}

			// "If node is a Text or Comment node, set offset to the index of node,
			// then set node to its parent."
			if (node.nodeType == $_.Node.TEXT_NODE || node.nodeType == $_.Node.COMMENT_NODE) {
				offset = Dom.getIndexInParent(node);
				node = node.parentNode;
			}

			// "Call collapse(node, offset) on the context object's Selection."
			Aloha.getSelection().collapse(node, offset);
			range.setStart(node, offset);
			range.setEnd(node, offset);

			// "Let container equal node."
			var container = node;

			// "While container is not a single-line container, and container's
			// parent is editable and in the same editing host as node, set
			// container to its parent."
			while (!isSingleLineContainer(container) && isEditable(container.parentNode) && inSameEditingHost(node, container.parentNode)) {
				container = container.parentNode;
			}

			// "If container is not editable or not in the same editing host as
			// node or is not a single-line container:"
			if (!isEditable(container) || !inSameEditingHost(container, node) || !isSingleLineContainer(container)) {
				// "Let tag be the default single-line container name."
				var tag = defaultSingleLineContainerName;

				// "Block-extend the active range, and let new range be the
				// result."
				var newRange = blockExtend(range);

				// "Let node list be a list of nodes, initially empty."
				//
				// "Append to node list the first node in tree order that is
				// contained in new range and is an allowed child of "p", if any."
				var nodeList = getContainedNodes(newRange, function (node) {
					return isAllowedChild(node, "p");
				}).slice(0, 1);

				// "If node list is empty:"
				if (!nodeList.length) {
					// "If tag is not an allowed child of the active range's start
					// node, abort these steps."
					if (!isAllowedChild(tag, range.startContainer)) {
						return;
					}

					// "Set container to the result of calling createElement(tag)
					// on the context object."
					container = document.createElement(tag);

					// "Call insertNode(container) on the active range."
					range.insertNode(container);

					// "Call createElement("br") on the context object, and append
					// the result as the last child of container."
					// TODO not always
					container.appendChild(createEndBreak());

					// "Call collapse(container, 0) on the context object's
					// Selection."
					// TODO: remove selection from command
					Aloha.getSelection().collapse(container, 0);
					range.setStart(container, 0);
					range.setEnd(container, 0);

					// "Abort these steps."
					return;
				}

				// "While the nextSibling of the last member of node list is not
				// null and is an allowed child of "p", append it to node list."
				while (nodeList[nodeList.length - 1].nextSibling && isAllowedChild(nodeList[nodeList.length - 1].nextSibling, "p")) {
					nodeList.push(nodeList[nodeList.length - 1].nextSibling);
				}

				// "Wrap node list, with sibling criteria returning false and new
				// parent instructions returning the result of calling
				// createElement(tag) on the context object. Set container to the
				// result."
				container = wrap(
					nodeList,
					function () {
						return false;
					},
					function () {
						return document.createElement(tag);
					},
					range
				);
			}

			// If no container has been set yet, it is not possible to insert a paragraph at this position;
			// the following steps are skipped in order to prevent critical errors from occurring;
			if (!container) {
				return;
			}

			// "If container's local name is "address", "listing", or "pre":"
			var oldHeight, newHeight;
			if (container.tagName == "ADDRESS" || container.tagName == "LISTING" || container.tagName == "PRE") {
				// "Let br be the result of calling createElement("br") on the
				// context object."
				var br = document.createElement("br");

				// remember the old height
				oldHeight = container.offsetHeight;

				// "Call insertNode(br) on the active range."
				range.insertNode(br);

				// determine the new height
				newHeight = container.offsetHeight;

				// "Call collapse(node, offset + 1) on the context object's
				// Selection."
				Aloha.getSelection().collapse(node, offset + 1);
				range.setStart(node, offset + 1);
				range.setEnd(node, offset + 1);

				// "If br is the last descendant of container, let br be the result
				// of calling createElement("br") on the context object, then call
				// insertNode(br) on the active range." (Fix: only do this, if the container height did not change by inserting a single <br/>)
				//
				// Work around browser bugs: some browsers select the
				// newly-inserted node, not per spec.
				if (oldHeight == newHeight && !isDescendant(nextNode(br), container)) {
					// TODO check
					range.insertNode(createEndBreak());
					Aloha.getSelection().collapse(node, offset + 1);
					range.setEnd(node, offset + 1);
				}

				// "Abort these steps."
				return;
			}

			// "If container's local name is "li", "dt", or "dd"; and either it has
			// no children or it has a single child and that child is a br:"
			if (listRelatedElements[container.tagName] && (!container.hasChildNodes() || (container.childNodes.length == 1 && isNamedHtmlElement(container.firstChild, "br")))) {
				// "Split the parent of the one-node list consisting of container."
				splitParent([container], range);

				// "If container has no children, call createElement("br") on the
				// context object and append the result as the last child of
				// container."
				// only do this, if inserting the br does NOT modify the offset height of the container
				//			if (!container.hasChildNodes()) {
				//				var oldHeight = container.offsetHeight, endBr = createEndBreak();
				//				container.appendChild(endBr);
				//				if (container.offsetHeight !== oldHeight) {
				//					container.removeChild(endBr);
				//				}
				//			}

				// "If container is a dd or dt, and it is not an allowed child of
				// any of its ancestors in the same editing host, set the tag name
				// of container to the default single-line container name and let
				// container be the result."
				if (isHtmlElementInArray(container, ["dd", "dt"]) && $_(getAncestors(container)).every(function (ancestor) { return !inSameEditingHost(container, ancestor) || !isAllowedChild(container, ancestor); })) {
					container = setTagName(container, defaultSingleLineContainerName, range);
				}

				// "Fix disallowed ancestors of container."
				fixDisallowedAncestors(container, range);

				// fix invalid nested lists
				if (isNamedHtmlElement(container, 'li') && isNamedHtmlElement(container.nextSibling, "li") && isHtmlElementInArray(container.nextSibling.firstChild, ["ol", "ul"])) {
					// we found a li containing only a br followed by a li containing a list as first element: merge the two li's
					var listParent = container.nextSibling,
						length = container.nextSibling.childNodes.length;
					for (i = 0; i < length; i++) {
						// we always move the first child into the container
						container.appendChild(listParent.childNodes[0]);
					}
					listParent.parentNode.removeChild(listParent);
				}

				// "Abort these steps."
				return;
			}

			// special behaviour when pressing enter in the last empty paragraph, that is nested in a blockquote
			if (isNamedHtmlElement(container, "p")
					&& isNamedHtmlElement(container.parentNode, "blockquote")
						&& !container.nextSibling
							&& (!container.hasChildNodes()
									|| (container.childNodes.length === 1
											&& isNamedHtmlElement(container.firstChild, "br")))) {
				jQuery(container.parentNode).after(container);
				return;
			}

			// "Let new line range be a new range whose start is the same as
			// the active range's, and whose end is (container, length of
			// container)."
			var newLineRange = Aloha.createRange();
			newLineRange.setStart(range.startContainer, range.startOffset);
			newLineRange.setEnd(container, getNodeLength(container));

			// "While new line range's start offset is zero and its start node is
			// not container, set its start to (parent of start node, index of
			// start node)."
			while (newLineRange.startOffset == 0 && newLineRange.startContainer != container) {
				newLineRange.setStart(newLineRange.startContainer.parentNode, Dom.getIndexInParent(newLineRange.startContainer));
			}

			// "While new line range's start offset is the length of its start node
			// and its start node is not container, set its start to (parent of
			// start node, 1 + index of start node)."
			while (newLineRange.startOffset == getNodeLength(newLineRange.startContainer) && newLineRange.startContainer != container) {
				newLineRange.setStart(newLineRange.startContainer.parentNode, 1 + Dom.getIndexInParent(newLineRange.startContainer));
			}

			// "Let end of line be true if new line range contains either nothing
			// or a single br, and false otherwise."
			var containedInNewLineRange = getContainedNodes(newLineRange);
			var endOfLine = !containedInNewLineRange.length || (containedInNewLineRange.length == 1 && isNamedHtmlElement(containedInNewLineRange[0], "br"));

			// "If the local name of container is "h1", "h2", "h3", "h4", "h5", or
			// "h6", and end of line is true, let new container name be the default
			// single-line container name."
			var newContainerName;
			if (/^H[1-6]$/.test(container.tagName) && endOfLine) {
				newContainerName = defaultSingleLineContainerName;

				// "Otherwise, if the local name of container is "dt" and end of line
				// is true, let new container name be "dd"."
			} else if (container.tagName == "DT" && endOfLine) {
				newContainerName = "dd";

				// "Otherwise, if the local name of container is "dd" and end of line
				// is true, let new container name be "dt"."
			} else if (container.tagName == "DD" && endOfLine) {
				newContainerName = "dt";

				// "Otherwise, let new container name be the local name of container."
			} else {
				newContainerName = container.tagName.toLowerCase();
			}

			// "Let new container be the result of calling createElement(new
			// container name) on the context object."
			var newContainer = document.createElement(newContainerName);

			// "Copy all non empty attributes of the container to new container."
			copyAttributes(container, newContainer);

			// "If new container has an id attribute, unset it."
			newContainer.removeAttribute("id");

			// "Insert new container into the parent of container immediately after
			// container."
			container.parentNode.insertBefore(newContainer, container.nextSibling);

			// "Let contained nodes be all nodes contained in new line range."
			var containedNodes = getAllContainedNodes(newLineRange);

			// "Let frag be the result of calling extractContents() on new line
			// range."
			var frag = newLineRange.extractContents();

			// "Unset the id attribute (if any) of each Element descendant of frag
			// that is not in contained nodes."
			var descendants = getDescendants(frag);
			for (i = 0; i < descendants.length; i++) {
				if (descendants[i].nodeType == $_.Node.ELEMENT_NODE && $_(containedNodes).indexOf(descendants[i]) == -1) {
					descendants[i].removeAttribute("id");
				}
			}

			var fragChildren = [],
				fragChild = frag.firstChild;
			if (fragChild) {
				do {
					if (!isWhitespaceNode(fragChild)) {
						fragChildren.push(fragChild);
					}
				} while (null != (fragChild = fragChild.nextSibling));
			}

			// if newContainer is a li and frag contains only a list, we add a br in the li (but only if the height would not change)
			if (isNamedHtmlElement(newContainer, 'li') && fragChildren.length && isHtmlElementInArray(fragChildren[0], ["ul", "ol"])) {
				oldHeight = newContainer.offsetHeight;
				var endBr = createEndBreak();
				newContainer.appendChild(endBr);
				newHeight = newContainer.offsetHeight;
				if (oldHeight !== newHeight) {
					newContainer.removeChild(endBr);
				}
			}

			// "Call appendChild(frag) on new container."
			newContainer.appendChild(frag);

			// "If container has no visible children, call createElement("br") on
			// the context object, and append the result as the last child of
			// container."
			ensureContainerEditable(container);

			// "If new container has no visible children, call createElement("br")
			// on the context object, and append the result as the last child of
			// new container."
			ensureContainerEditable(newContainer);

			// "Call collapse(new container, 0) on the context object's Selection."
			Aloha.getSelection().collapse(newContainer, 0);
			range.setStart(newContainer, 0);
			range.setEnd(newContainer, 0);
		}
	};

	//@}
	///// The insertText command /////
	//@{
	commands.inserttext = {
		action: function (value, range) {
			var i;

			// "Delete the contents of the active range, with strip wrappers
			// false."
			deleteContents(range, {
				stripWrappers: false
			});

			// "If the active range's start node is neither editable nor an editing
			// host, abort these steps."
			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
				return;
			}

			// "If value's length is greater than one:"
			if (value.length > 1) {
				// "For each element el in value, take the action for the
				// insertText command, with value equal to el."
				for (i = 0; i < value.length; i++) {
					commands.inserttext.action(value[i], range);
				}

				// "Abort these steps."
				return;
			}

			// "If value is the empty string, abort these steps."
			if (value == "") {
				return;
			}

			// "If value is a newline (U+00A0), take the action for the
			// insertParagraph command and abort these steps."
			if (value == "\n") {
				commands.insertparagraph.action('', range);
				return;
			}

			// "Let node and offset be the active range's start node and offset."
			var node = range.startContainer;
			var offset = range.startOffset;

			// "If node has a child whose index is offset − 1, and that child is a
			// Text node, set node to that child, then set offset to node's
			// length."
			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && node.childNodes[offset - 1].nodeType == $_.Node.TEXT_NODE) {
				node = node.childNodes[offset - 1];
				offset = getNodeLength(node);
			}

			// "If node has a child whose index is offset, and that child is a Text
			// node, set node to that child, then set offset to zero."
			if (0 <= offset && offset < node.childNodes.length && node.childNodes[offset].nodeType == $_.Node.TEXT_NODE) {
				node = node.childNodes[offset];
				offset = 0;
			}

			// "If value is a space (U+0020), and either node is an Element whose
			// resolved value for "white-space" is neither "pre" nor "pre-wrap" or
			// node is not an Element but its parent is an Element whose resolved
			// value for "white-space" is neither "pre" nor "pre-wrap", set value
			// to a non-breaking space (U+00A0)."
			var refElement = node.nodeType == $_.Node.ELEMENT_NODE ? node : node.parentNode;
			if (value == " " && refElement.nodeType == $_.Node.ELEMENT_NODE && jQuery.inArray($_.getComputedStyle(refElement).whiteSpace, ["pre", "pre-wrap"]) == -1) {
				value = "\xa0";
			}

			// "Record current overrides, and let overrides be the result."
			var overrides = recordCurrentOverrides(range);

			// "If node is a Text node:"
			if (node.nodeType == $_.Node.TEXT_NODE) {
				// "Call insertData(offset, value) on node."
				node.insertData(offset, value);

				// "Call collapse(node, offset) on the context object's Selection."
				Aloha.getSelection().collapse(node, offset);
				range.setStart(node, offset);

				// "Call extend(node, offset + 1) on the context object's
				// Selection."
				Aloha.getSelection().extend(node, offset + 1);
				range.setEnd(node, offset + 1);

				// "Otherwise:"
			} else {
				// "If node has only one child, which is a collapsed line break,
				// remove its child from it."
				//
				// FIXME: IE incorrectly returns false here instead of true
				// sometimes?
				if (node.childNodes.length == 1 && isCollapsedLineBreak(node.firstChild)) {
					node.removeChild(node.firstChild);
				}

				// "Let text be the result of calling createTextNode(value) on the
				// context object."
				var text = document.createTextNode(value);

				// "Call insertNode(text) on the active range."
				range.insertNode(text);

				// "Call collapse(text, 0) on the context object's Selection."
				Aloha.getSelection().collapse(text, 0);
				range.setStart(text, 0);

				// "Call extend(text, 1) on the context object's Selection."
				Aloha.getSelection().extend(text, 1);
				range.setEnd(text, 1);
			}

			// "Restore states and values from overrides."
			restoreStatesAndValues(overrides, range);

			// "Canonicalize whitespace at the active range's start."
			canonicalizeWhitespace(range.startContainer, range.startOffset);

			// "Canonicalize whitespace at the active range's end."
			canonicalizeWhitespace(range.endContainer, range.endOffset);

			// "Call collapseToEnd() on the context object's Selection."
			Aloha.getSelection().collapseToEnd();
			range.collapse(false);
		}
	};

	//@}
	///// The insertUnorderedList command /////
	//@{
	commands.insertunorderedlist = {
		// "Toggle lists with tag name "ul"."
		action: function (value, range) {
			toggleLists("ul", range);
		},
		// "True if the selection's list state is "mixed" or "mixed ul", false
		// otherwise."
		indeterm: function () {
			return (/^mixed( ul)?$/).test(getSelectionListState());
		},
		// "True if the selection's list state is "ul", false otherwise."
		state: function () {
			return getSelectionListState() == "ul";
		}
	};

	//@}
	///// The justifyCenter command /////
	//@{
	commands.justifycenter = {
		// "Justify the selection with alignment "center"."
		action: function (value, range) {
			justifySelection("center", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "center" and at least one
			// does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "center"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "center"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "center".
			// Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "center";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The justifyFull command /////
	//@{
	commands.justifyfull = {
		// "Justify the selection with alignment "justify"."
		action: function (value, range) {
			justifySelection("justify", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "justify" and at least
			// one does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "justify"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "justify"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "justify".
			// Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "justify";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The justifyLeft command /////
	//@{
	commands.justifyleft = {
		// "Justify the selection with alignment "left"."
		action: function (value, range) {
			justifySelection("left", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "left" and at least one
			// does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "left"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "left"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "left".  Otherwise
			// return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "left";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The justifyRight command /////
	//@{
	commands.justifyright = {
		// "Justify the selection with alignment "right"."
		action: function (value, range) {
			justifySelection("right", range);
		},
		indeterm: function () {
			// "Block-extend the active range. Return true if among visible
			// editable nodes that are contained in the result and have no
			// children, at least one has alignment value "right" and at least one
			// does not. Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "right"; })
				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "right"; });
		},
		state: function () {
			// "Block-extend the active range. Return true if there is at least one
			// visible editable node that is contained in the result and has no
			// children, and all such nodes have alignment value "right".
			// Otherwise return false."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			return nodes.length && $_(nodes).every(function (node) {
				return getAlignmentValue(node) == "right";
			});
		},
		value: function () {
			// "Block-extend the active range, and return the alignment value of
			// the first visible editable node that is contained in the result and
			// has no children. If there is no such node, return "left"."
			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
			});
			if (nodes.length) {
				return getAlignmentValue(nodes[0]);
			}
			return "left";
		}
	};

	//@}
	///// The outdent command /////
	//@{
	commands.outdent = {
		action: function () {
			// "Let items be a list of all lis that are ancestor containers of the
			// range's start and/or end node."
			//
			// It's annoying to get this in tree order using functional stuff
			// without doing getDescendants(document), which is slow, so I do it
			// imperatively.
			var items = [];
			(function () {
				var ancestorContainer;
				for (ancestorContainer = getActiveRange().endContainer;
					     ancestorContainer != getActiveRange().commonAncestorContainer;
					     ancestorContainer = ancestorContainer.parentNode) {
					if (isNamedHtmlElement(ancestorContainer, "li")) {
						items.unshift(ancestorContainer);
					}
				}
				for (ancestorContainer = getActiveRange().startContainer;
					     ancestorContainer;
					     ancestorContainer = ancestorContainer.parentNode) {
					if (isNamedHtmlElement(ancestorContainer, "li")) {
						items.unshift(ancestorContainer);
					}
				}
			}());

			// "For each item in items, normalize sublists of item."
			$_(items).forEach(function (thisArg) {
				normalizeSublists(thisArg, getActiveRange());
			});

			// "Block-extend the active range, and let new range be the result."
			var newRange = blockExtend(getActiveRange());

			// "Let node list be a list of nodes, initially empty."
			//
			// "For each node node contained in new range, append node to node list
			// if the last member of node list (if any) is not an ancestor of node;
			// node is editable; and either node has no editable descendants, or is
			// an ol or ul, or is an li whose parent is an ol or ul."
			var nodeList = getContainedNodes(newRange, function (node) {
				return isEditable(node) && (!$_(getDescendants(node)).some(isEditable) || isHtmlElementInArray(node, ["ol", "ul"]) || (isNamedHtmlElement(node, 'li') && isHtmlElementInArray(node.parentNode, ["ol", "ul"])));
			});

			// "While node list is not empty:"
			while (nodeList.length) {
				// "While the first member of node list is an ol or ul or is not
				// the child of an ol or ul, outdent it and remove it from node
				// list."
				while (nodeList.length && (isHtmlElementInArray(nodeList[0], ["OL", "UL"]) || !isHtmlElementInArray(nodeList[0].parentNode, ["OL", "UL"]))) {
					outdentNode(nodeList.shift(), newRange);
				}

				// "If node list is empty, break from these substeps."
				if (!nodeList.length) {
					break;
				}

				// "Let sublist be a list of nodes, initially empty."
				var sublist = [];

				// "Remove the first member of node list and append it to sublist."
				sublist.push(nodeList.shift());

				// "While the first member of node list is the nextSibling of the
				// last member of sublist, and the first member of node list is not
				// an ol or ul, remove the first member of node list and append it
				// to sublist."
				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isHtmlElementInArray(nodeList[0], ["OL", "UL"])) {
					sublist.push(nodeList.shift());
				}

				// "Record the values of sublist, and let values be the result."
				var values = recordValues(sublist);

				// "Split the parent of sublist, with new parent null."
				splitParent(sublist, newRange);

				// "Fix disallowed ancestors of each member of sublist."
				$_(sublist).forEach(fixDisallowedAncestors);

				// "Restore the values from values."
				restoreValues(values, newRange);
			}
		}
	};

	//@}

	//////////////////////////////////
	///// Miscellaneous commands /////
	//////////////////////////////////

	///// The selectAll command /////
	//@{
	commands.selectall = {
		// Note, this ignores the whole globalRange/getActiveRange() thing and
		// works with actual selections.  Not suitable for autoimplementation.html.
		action: function () {
			// "Let target be the body element of the context object."
			var target = document.body;

			// "If target is null, let target be the context object's
			// documentElement."
			if (!target) {
				target = document.documentElement;
			}

			// "If target is null, call getSelection() on the context object, and
			// call removeAllRanges() on the result."
			if (!target) {
				Aloha.getSelection().removeAllRanges();

				// "Otherwise, call getSelection() on the context object, and call
				// selectAllChildren(target) on the result."
			} else {
				Aloha.getSelection().selectAllChildren(target);
			}
		}
	};

	//@}
	///// The styleWithCSS command /////
	//@{
	commands.stylewithcss = {
		action: function (value) {
			// "If value is an ASCII case-insensitive match for the string
			// "false", set the CSS styling flag to false. Otherwise, set the
			// CSS styling flag to true."
			cssStylingFlag = String(value).toLowerCase() != "false";
		},
		state: function () {
			return cssStylingFlag;
		}
	};

	//@}
	///// The useCSS command /////
	//@{
	commands.usecss = {
		action: function (value) {
			// "If value is an ASCII case-insensitive match for the string "false",
			// set the CSS styling flag to true. Otherwise, set the CSS styling
			// flag to false."
			cssStylingFlag = String(value).toLowerCase() == "false";
		}
	};
	//@}

	// Some final setup
	//@{
	(function () {
		// Opera 11.50 doesn't implement Object.keys, so I have to make an explicit
		// temporary, which means I need an extra closure to not leak the temporaries
		// into the global namespace.  >:(
		var commandNames = [];
		var command;
		for (command in commands) {
			if (commands.hasOwnProperty(command)) {
				commandNames.push(command);
			}
		}
		$_(commandNames).forEach(function (command) {
			// "If a command does not have a relevant CSS property specified, it
			// defaults to null."
			if (null == commands[command].relevantCssProperty) {
				commands[command].relevantCssProperty = null;
			}

			// "If a command has inline command activated values defined but
			// nothing else defines when it is indeterminate, it is indeterminate
			// if among editable Text nodes effectively contained in the active
			// range, there is at least one whose effective command value is one of
			// the given values and at least one whose effective command value is
			// not one of the given values."
			if (null != commands[command].inlineCommandActivatedValues && null == commands[command].indeterm) {
				commands[command].indeterm = function (range) {
					var values = $_(getAllEffectivelyContainedNodes(range, function (node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE; }))
						.map(function (node) { return getEffectiveCommandValue(node, command); });

					var matchingValues = $_(values).filter(function (value) {
						return $_(commands[command].inlineCommandActivatedValues).indexOf(value) != -1;
					});

					return matchingValues.length >= 1 && values.length - matchingValues.length >= 1;
				};
			}

			// "If a command has inline command activated values defined, its state
			// is true if either no editable Text node is effectively contained in
			// the active range, and the active range's start node's effective
			// command value is one of the given values; or if there is at least
			// one editable Text node effectively contained in the active range,
			// and all of them have an effective command value equal to one of the
			// given values."
			if (null != commands[command].inlineCommandActivatedValues) {
				commands[command].state = function (range) {
					var nodes = getAllEffectivelyContainedNodes(range, function (node) {
						return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
					});

					if (nodes.length == 0) {
						return $_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(range.startContainer, command)) != -1;
					}
					return $_(nodes).every(function (node) {
						return $_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(node, command)) != -1;
					});
				};
			}

			// "If a command is a standard inline value command, it is
			// indeterminate if among editable Text nodes that are effectively
			// contained in the active range, there are two that have distinct
			// effective command values. Its value is the effective command value
			// of the first editable Text node that is effectively contained in the
			// active range, or if there is no such node, the effective command
			// value of the active range's start node."
			if (null != commands[command].standardInlineValueCommand) {
				commands[command].indeterm = function () {
					var values = $_(getAllEffectivelyContainedNodes(getActiveRange())).filter(function (node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE; }, true)
						.map(function (node) { return getEffectiveCommandValue(node, command); });
					var i;
					for (i = 1; i < values.length; i++) {
						if (values[i] != values[i - 1]) {
							return true;
						}
					}
					return false;
				};

				commands[command].value = function (range) {
					var refNode = getAllEffectivelyContainedNodes(range, function (node) {
						return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
					})[0];

					if (typeof refNode == "undefined") {
						refNode = range.startContainer;
					}

					return getEffectiveCommandValue(refNode, command);
				};
			}
		});
	}());
	//@}
	return {
		commands: commands,
		execCommand: myExecCommand,
		queryCommandIndeterm: myQueryCommandIndeterm,
		queryCommandState: myQueryCommandState,
		queryCommandValue: myQueryCommandValue,
		queryCommandEnabled: myQueryCommandEnabled,
		queryCommandSupported: myQueryCommandSupported,
		copyAttributes: copyAttributes,
		createEndBreak: createEndBreak,
		isEndBreak: isEndBreak,
		ensureContainerEditable: ensureContainerEditable,
		isEditingHost: isEditingHost,
		isEditable: isEditable,
		getStateOverride: getStateOverride,
		setStateOverride: setStateOverride,
		resetOverrides: resetOverrides,
		unsetStateOverride: unsetStateOverride
	};
}); // end define
// vim: foldmarker=@{,@} foldmethod=marker
;
/* selection.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/selection',[
	'aloha/core',
	'jquery',
	'util/class',
	'util/range',
	'util/arrays',
	'util/strings',
	'aloha/console',
	'PubSub',
	'aloha/engine',
	'aloha/ecma5shims',
	'aloha/rangy-core'
], function (
	Aloha,
	jQuery,
	Class,
	Range,
	Arrays,
	Strings,
	console,
	PubSub,
	Engine,
	e5s
) {
	

	var GENTICS = window.GENTICS;

	function isCollapsedAndEmptyOrEndBr(rangeObject) {
		var firstChild;
		if (rangeObject.startContainer !== rangeObject.endContainer) {
			return false;
		}
		// check whether the container starts in an element node
		if (rangeObject.startContainer.nodeType != 1) {
			return false;
		}
		firstChild = rangeObject.startContainer.firstChild;
		return (!firstChild || (!firstChild.nextSibling && firstChild.nodeName == 'BR'));
	}

	function isCollapsedAndEndBr(rangeObject) {
		if (rangeObject.startContainer !== rangeObject.endContainer) {
			return false;
		}
		if (rangeObject.startContainer.nodeType != 1) {
			return false;
		}
		return Engine.isEndBreak(rangeObject.startContainer);
	}

	var prevStartContext = null;
	var prevEndContext = null;

	function makeContextHtml(node, parents) {
		var result = [],
			parent,
			len,
			i;
		if (1 === node.nodeType && node.nodeName !== 'BODY' && node.nodeName !== 'HTML') {
			result.push(node.cloneNode(false).outerHTML);
		} else {
			result.push('#' + node.nodeType);
		}
		for (i = 0, len = parents.length; i < len; i++) {
			parent = parents[i];
			if (parent.nodeName === 'BODY' || parent.nodeName === 'HTML') {
				// Although we limit the ancestors in most cases to the
				// active editable, in some cases (copy&paste) the
				// parent may be outside.
				// On IE7 this means the following code may clone the
				// HTML node too, which causes the browser to crash.
				// On other browsers, this is just an optimization
				// because the body and html elements should probably
				// not be considered part of the context of an edit
				// operation.
				break;
			}
			result.push(parent.cloneNode(false).outerHTML);
		}
		return result.join('');
	}

	function getChangedContext(node, context) {
		var until = Aloha.activeEditable ? Aloha.activeEditable.obj.parent()[0] : null;
		var parents = jQuery(node).parentsUntil(until).get();
		var html = makeContextHtml(node, parents);
		var equal = (context && node === context.node && Arrays.equal(context.parents, parents) && html === context.html);
		return equal ? null : {
			node: node,
			parents: parents,
			html: html
		};
	}

	function triggerSelectionContextChanged(rangeObject, event) {
		var startContainer = rangeObject.startContainer;
		var endContainer = rangeObject.endContainer;
		if (!startContainer || !endContainer) {
			console.warn("aloha/selection", "encountered range object without start or end container");
			return;
		}
		var startContext = getChangedContext(startContainer, prevStartContext);
		var endContext = getChangedContext(endContainer, prevEndContext);
		if (!startContext && !endContext) {
			return;
		}
		prevStartContext = startContext;
		prevEndContext = endContext;

		/**
		 * @api documented in the guides
		 */
		PubSub.pub('aloha.selection.context-change', {
			range: rangeObject,
			event: event
		});
	}

	/**
	 * @namespace Aloha
	 * @class Selection
	 * This singleton class always represents the current user selection
	 * @singleton
	 */
	var Selection = Class.extend({
		_constructor: function () {
			// Pseudo Range Clone being cleaned up for better HTML wrapping support
			this.rangeObject = {};

			this.preventSelectionChangedFlag = false; // will remember if someone urged us to skip the next aloha-selection-changed event

			// define basics first
			this.tagHierarchy = {
				'textNode': {},
				'abbr': {
					'textNode': true
				},
				'b': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true,
					'cite': true,
					'q': true,
					'code': true,
					'abbr': true,
					'strong': true
				},
				'pre': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true,
					'cite': true,
					'q': true,
					'code': true,
					'abbr': true
				},
				'blockquote': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true,
					'cite': true,
					'q': true,
					'code': true,
					'abbr': true,
					'p': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true
				},
				'ins': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'u': true,
					'p': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true
				},
				'ul': {
					'li': true
				},
				'ol': {
					'li': true
				},
				'li': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'ul': true,
					'ol': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true,
					'del': true,
					'ins': true,
					'u': true,
					'a': true
				},
				'tr': {
					'td': true,
					'th': true
				},
				'table': {
					'tr': true
				},
				'div': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'ul': true,
					'ol': true,
					'table': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true,
					'del': true,
					'ins': true,
					'u': true,
					'p': true,
					'div': true,
					'pre': true,
					'blockquote': true,
					'a': true
				},
				'h1': {
					'textNode': true,
					'b': true,
					'i': true,
					'em': true,
					'sup': true,
					'sub': true,
					'br': true,
					'span': true,
					'img': true,
					'a': true,
					'del': true,
					'ins': true,
					'u': true
				}
			};

			// now reference the basics for all other equal tags (important: don't forget to include
			// the basics itself as reference: 'b' : this.tagHierarchy.b
			this.tagHierarchy = {
				'textNode': this.tagHierarchy.textNode,
				'abbr': this.tagHierarchy.abbr,
				'br': this.tagHierarchy.textNode,
				'img': this.tagHierarchy.textNode,
				'b': this.tagHierarchy.b,
				'strong': this.tagHierarchy.b,
				'code': this.tagHierarchy.b,
				'q': this.tagHierarchy.b,
				'blockquote': this.tagHierarchy.blockquote,
				'cite': this.tagHierarchy.b,
				'i': this.tagHierarchy.b,
				'em': this.tagHierarchy.b,
				'sup': this.tagHierarchy.b,
				'sub': this.tagHierarchy.b,
				'span': this.tagHierarchy.b,
				'del': this.tagHierarchy.del,
				'ins': this.tagHierarchy.ins,
				'u': this.tagHierarchy.b,
				'p': this.tagHierarchy.b,
				'pre': this.tagHierarchy.pre,
				'a': this.tagHierarchy.b,
				'ul': this.tagHierarchy.ul,
				'ol': this.tagHierarchy.ol,
				'li': this.tagHierarchy.li,
				'td': this.tagHierarchy.li,
				'div': this.tagHierarchy.div,
				'h1': this.tagHierarchy.h1,
				'h2': this.tagHierarchy.h1,
				'h3': this.tagHierarchy.h1,
				'h4': this.tagHierarchy.h1,
				'h5': this.tagHierarchy.h1,
				'h6': this.tagHierarchy.h1,
				'table': this.tagHierarchy.table
			};

			// When applying this elements to selection they will replace the assigned elements
			this.replacingElements = {
				'h1': {
					'p': true,
					'h1': true,
					'h2': true,
					'h3': true,
					'h4': true,
					'h5': true,
					'h6': true,
					'pre': true,
					'blockquote': true
				}
			};
			this.replacingElements = {
				'h1': this.replacingElements.h1,
				'h2': this.replacingElements.h1,
				'h3': this.replacingElements.h1,
				'h4': this.replacingElements.h1,
				'h5': this.replacingElements.h1,
				'h6': this.replacingElements.h1,
				'pre': this.replacingElements.h1,
				'p': this.replacingElements.h1,
				'blockquote': this.replacingElements.h1
			};
			this.allowedToStealElements = {
				'h1': {
					'textNode': true
				}
			};
			this.allowedToStealElements = {
				'h1': this.allowedToStealElements.h1,
				'h2': this.allowedToStealElements.h1,
				'h3': this.allowedToStealElements.h1,
				'h4': this.allowedToStealElements.h1,
				'h5': this.allowedToStealElements.h1,
				'h6': this.allowedToStealElements.h1,
				'p': this.tagHierarchy.b
			};
		},

		/**
		 * Class definition of a SelectionTree (relevant for all formatting / markup changes)
		 * TODO: remove this (was moved to range.js)
		 * Structure:
		 * +
		 * |-domobj: <reference to the DOM Object> (NOT jQuery)
		 * |-selection: defines if this node is marked by user [none|partial|full]
		 * |-children: recursive structure like this
		 * @hide
		 */
		SelectionTree: function () {
			this.domobj = {};
			this.selection = undefined;
			this.children = [];
		},

		/**
		 * INFO: Method is used for integration with Gentics Aloha, has no use otherwise
		 * Updates the rangeObject according to the current user selection
		 * Method is always called on selection change
		 * @param objectClicked Object that triggered the selectionChange event
		 * @return true when rangeObject was modified, false otherwise
		 * @hide
		 */
		onChange: function (objectClicked, event, timeout) {
			if (this.updateSelectionTimeout) {
				window.clearTimeout(this.updateSelectionTimeout);
			}

			// We have to update the selection in a timeout due to an IE
			// bug that is is caused by selecting some text and then
			// clicking once inside the selection (which collapses the
			// selection inside the previous selection).
			var selection = this;
			this.updateSelectionTimeout = window.setTimeout(function () {
				var range = new Aloha.Selection.SelectionRange(true);
				// We have to work around an IE bug that causes the user
				// selection to be incorrectly set on the body element
				// when the updateSelectionTimeout triggers. The
				// selection corrects itself after waiting a while.
				if (!range.startContainer || 'HTML' === range.startContainer.nodeName || 'BODY' === range.startContainer.nodeName) {
					if (!this.updateSelectionTimeout) {
						// First wait 5 millis, then 20 millis, 50 millis, 110 millis etc.
						selection.onChange(objectClicked, event, 10 + (timeout || 5) * 2);
					}
					return;
				}
				Aloha.Selection._updateSelection(event, range);
			}, timeout || 5);
		},

		/**
		 * prevents the next aloha-selection-changed event from being triggered
		 */
		preventSelectionChanged: function () {
			this.preventSelectionChangedFlag = true;
		},

		/**
		 * will return wheter selection change event was prevented or not, and reset the preventSelectionChangedFlag
		 * @return {Boolean} true if aloha-selection-change event was prevented
		 */
		isSelectionChangedPrevented: function () {
			var prevented = this.preventSelectionChangedFlag;
			this.preventSelectionChangedFlag = false;
			return prevented;
		},

		/**
		 * Checks if the current rangeObject common ancector container is edtiable
		 * @return {Boolean} true if current common ancestor is editable
		 */
		isSelectionEditable: function () {
			return (this.rangeObject.commonAncestorContainer && jQuery(this.rangeObject.commonAncestorContainer).contentEditable());
		},

		/**
		 * This method checks, if the current rangeObject common ancestor container has a 'data-aloha-floatingmenu-visible' Attribute.
		 * Needed in Floating Menu for exceptional display of floatingmenu.
		 */
		isFloatingMenuVisible: function () {
			var visible = jQuery(Aloha.Selection.rangeObject.commonAncestorContainer).attr('data-aloha-floatingmenu-visible');
			if (visible !== 'undefined') {
				if (visible === 'true') {
					return true;
				}
				return false;
			}
			return false;
		},

		/**
		 * INFO: Method is used for integration with Gentics Aloha, has no use otherwise
		 * Updates the rangeObject according to the current user selection
		 * Method is always called on selection change
		 * @param event jQuery browser event object
		 * @return true when rangeObject was modified, false otherwise
		 * @hide
		 */
		updateSelection: function (event) {
			return this._updateSelection(event, null);
		},

		/**
		 * Internal version of updateSelection that adds the range parameter to be
		 * able to work around an IE bug that caused the current user selection
		 * sometimes to be on the body element.
		 * @param {Object} event
		 * @param {Object} range a substitute for the current user selection. if not provided,
		 *   the current user selection will be used.
		 * @hide
		 */
		_updateSelection: function (event, range) {
			if (event && event.originalEvent &&
					true === event.originalEvent.stopSelectionUpdate) {
				return false;
			}

			if (typeof range === 'undefined') {
				return false;
			}

			this.rangeObject = range =
					range || new Aloha.Selection.SelectionRange(true);

			// workaround for FF selection bug, where it is possible to move the selection INTO a hr
			if (range && range.startContainer
					&& 'HR' === range.startContainer.nodeName
					&& range.endContainer
					&& 'HR' === range.endContainer.nodeName) {
				Aloha.getSelection().removeAllRanges();
				return true;
			}

			// Determine the common ancestor container and update the selection
			// tree.
			range.update();

			// Workaround for nasty IE bug that allows the user to select
			// text nodes inside areas with contenteditable "false"
			if (range && range.startContainer && range.endContainer) {
				var inEditable =
						jQuery(range.commonAncestorContainer)
							.closest('.aloha-editable').length > 0;

				if (inEditable) {
					var validStartPosition = !(3 === range.startContainer.nodeType &&
							!jQuery(range.startContainer.parentNode).contentEditable());

					var validEndPosition = !(3 === range.endContainer.nodeType &&
							!jQuery(range.endContainer.parentNode).contentEditable());

					if (!validStartPosition || !validEndPosition) {
						Aloha.getSelection().removeAllRanges();
						return true;
					}
				}
			}

			// check if aloha-selection-changed event has been prevented
			if (this.isSelectionChangedPrevented()) {
				return true;
			}

			Aloha.trigger('aloha-selection-changed-before', [this.rangeObject, event]);

			// throw the event that the selection has changed. Plugins now have the
			// chance to react on the currentElements[childCount].children.lengthged selection
			Aloha.trigger('aloha-selection-changed', [this.rangeObject, event]);

			triggerSelectionContextChanged(this.rangeObject, event);

			Aloha.trigger('aloha-selection-changed-after', [this.rangeObject, event]);

			return true;
		},

		/**
		 * creates an object with x items containing all relevant dom objects.
		 * Structure:
		 * +
		 * |-domobj: <reference to the DOM Object> (NOT jQuery)
		 * |-selection: defines if this node is marked by user [none|partial|full]
		 * |-children: recursive structure like this ("x.." because it's then shown last in DOM Browsers...)
		 * TODO: remove this (was moved to range.js)
		 *
		 * @param rangeObject "Aloha clean" range object including a commonAncestorContainer
		 * @return obj selection
		 * @hide
		 */
		getSelectionTree: function (rangeObject) {
			if (!rangeObject) { // if called without any parameters, the method acts as getter for this.selectionTree
				return this.rangeObject.getSelectionTree();
			}
			if (!rangeObject.commonAncestorContainer) {
				Aloha.Log.error(this, 'the rangeObject is missing the commonAncestorContainer');
				return false;
			}

			this.inselection = false;

			// before getting the selection tree, we do a cleanup
			if (GENTICS.Utils.Dom.doCleanup({ 'merge': true }, rangeObject)) {
				rangeObject.update();
				rangeObject.select();
			}

			return this.recursiveGetSelectionTree(rangeObject, rangeObject.commonAncestorContainer);
		},

		/**
		 * Recursive inner function for generating the selection tree.
		 * TODO: remove this (was moved to range.js)
		 * @param rangeObject range object
		 * @param currentObject current DOM object for which the selection tree shall be generated
		 * @return array of SelectionTree objects for the children of the current DOM object
		 * @hide
		 */
		recursiveGetSelectionTree: function (rangeObject, currentObject) {
			// get all direct children of the given object
			var jQueryCurrentObject = jQuery(currentObject),
				childCount = 0,
				that = this,
				currentElements = [];

			jQueryCurrentObject.contents().each(function (index) {
				var selectionType = 'none',
					startOffset = false,
					endOffset = false,
					collapsedFound = false,
					i,
				    elementsLength,
					noneFound = false,
					partialFound = false,
					fullFound = false;

				// check for collapsed selections between nodes
				if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == index) {
					// insert an extra selectiontree object for the collapsed selection here
					currentElements[childCount] = new Aloha.Selection.SelectionTree();
					currentElements[childCount].selection = 'collapsed';
					currentElements[childCount].domobj = undefined;
					that.inselection = false;
					collapsedFound = true;
					childCount++;
				}

				if (!that.inselection && !collapsedFound) {
					// the start of the selection was not yet found, so look for it now
					// check whether the start of the selection is found here

					// Try to read the nodeType property and return if we do not have permission
					// ie.: frame document to an external URL
					var nodeType;
					try {
						nodeType = this.nodeType;
					} catch (e) {
						return;
					}

					// check is dependent on the node type
					switch (nodeType) {
					case 3:
						// text node
						if (this === rangeObject.startContainer) {
							// the selection starts here
							that.inselection = true;

							// when the startoffset is > 0, the selection type is only partial
							selectionType = rangeObject.startOffset > 0 ? 'partial' : 'full';
							startOffset = rangeObject.startOffset;
							endOffset = this.length;
						}
						break;
					case 1:
						// element node
						if (this === rangeObject.startContainer && rangeObject.startOffset === 0) {
							// the selection starts here
							that.inselection = true;
							selectionType = 'full';
						}
						if (currentObject === rangeObject.startContainer && rangeObject.startOffset === index) {
							// the selection starts here
							that.inselection = true;
							selectionType = 'full';
						}
						break;
					}
				}

				if (that.inselection && !collapsedFound) {
					if (selectionType == 'none') {
						selectionType = 'full';
					}
					// we already found the start of the selection, so look for the end of the selection now
					// check whether the end of the selection is found here

					switch (this.nodeType) {
					case 3:
						// text node
						if (this === rangeObject.endContainer) {
							// the selection ends here
							that.inselection = false;

							// check for partial selection here
							if (rangeObject.endOffset < this.length) {
								selectionType = 'partial';
							}
							if (startOffset === false) {
								startOffset = 0;
							}
							endOffset = rangeObject.endOffset;
						}
						break;
					case 1:
						// element node
						if (this === rangeObject.endContainer && rangeObject.endOffset === 0) {
							that.inselection = false;
						}
						break;
					}
					if (currentObject === rangeObject.endContainer && rangeObject.endOffset <= index) {
						that.inselection = false;
						selectionType = 'none';
					}
				}

				// create the current selection tree entry
				currentElements[childCount] = new Aloha.Selection.SelectionTree();
				currentElements[childCount].domobj = this;
				currentElements[childCount].selection = selectionType;
				if (selectionType == 'partial') {
					currentElements[childCount].startOffset = startOffset;
					currentElements[childCount].endOffset = endOffset;
				}

				// now do the recursion step into the current object
				currentElements[childCount].children = that.recursiveGetSelectionTree(rangeObject, this);
				elementsLength = currentElements[childCount].children.length;

				// check whether a selection was found within the children
				if (elementsLength > 0) {
					for (i = 0; i < elementsLength; ++i) {
						switch (currentElements[childCount].children[i].selection) {
						case 'none':
							noneFound = true;
							break;
						case 'full':
							fullFound = true;
							break;
						case 'partial':
							partialFound = true;
							break;
						}
					}

					if (partialFound || (fullFound && noneFound)) {
						// found at least one 'partial' selection in the children, or both 'full' and 'none', so this element is also 'partial' selected
						currentElements[childCount].selection = 'partial';
					} else if (fullFound && !partialFound && !noneFound) {
						// only found 'full' selected children, so this element is also 'full' selected
						currentElements[childCount].selection = 'full';
					}
				}

				childCount++;
			});

			// extra check for collapsed selections at the end of the current element
			if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == currentObject.childNodes.length) {
				currentElements[childCount] = new Aloha.Selection.SelectionTree();
				currentElements[childCount].selection = 'collapsed';
				currentElements[childCount].domobj = undefined;
			}

			return currentElements;
		},

		/**
		 * Get the currently selected range
		 * @return {Aloha.Selection.SelectionRange} currently selected range
		 * @method
		 */
		getRangeObject: function () {
			return this.rangeObject;
		},

		/**
		 * method finds out, if a node is within a certain markup or not
		 * @param rangeObj Aloha rangeObject
		 * @param startOrEnd boolean; defines, if start or endContainer should be used: false for start, true for end
		 * @param markupObject jQuery object of the markup to look for
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @param limitObject dom object which limits the search are within the dom. normally this will be the active Editable
		 * @return true, if the markup is effective on the range objects start or end node
		 * @hide
		 */
		isRangeObjectWithinMarkup: function (rangeObject, startOrEnd, markupObject, tagComparator, limitObject) {
			var domObj = !startOrEnd ? rangeObject.startContainer : rangeObject.endContainer,
				that = this,
				parents = jQuery(domObj).parents(),
				returnVal = false,
				i = -1;

			// check if a comparison method was passed as parameter ...
			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
				Aloha.Log.error(this, 'parameter tagComparator is not a function');
			}
			// ... if not use this as standard tag comparison method
			if (typeof tagComparator === 'undefined') {
				tagComparator = function (domobj, markupObject) {
					return that.standardTextLevelSemanticsComparator(domobj, markupObject); // TODO should actually be this.getStandardTagComparator(markupObject)
				};
			}

			if (parents.length > 0) {
				parents.each(function () {
					// the limit object was reached (normally the Editable Element)
					if (this === limitObject) {
						Aloha.Log.debug(that, 'reached limit dom obj');
						return false; // break() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
					}
					if (tagComparator(this, markupObject)) {
						if (returnVal === false) {
							returnVal = [];
						}
						Aloha.Log.debug(that, 'reached object equal to markup');
						i++;
						returnVal[i] = this;
						return true; // continue() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
					}
				});
			}
			return returnVal;
		},

		/**
		 * standard method, to compare a domobj and a jquery object for sections and grouping content (e.g. p, h1, h2, ul, ....).
		 * is always used when no other tag comparator is passed as parameter
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardSectionsAndGroupingContentComparator: function (domobj, markupObject) {
			if (domobj.nodeType !== 1) {
				Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
				return false;
			}
			if (!markupObject[0].nodeName) {
				return false;
			}
			var elemMap = Aloha.Selection.replacingElements[domobj.nodeName.toLowerCase()];
			return elemMap && elemMap[markupObject[0].nodeName.toLowerCase()];
		},

		/**
		 * standard method, to compare a domobj and a jquery object for their tagName (aka span elements, e.g. b, i, sup, span, ...).
		 * is always used when no other tag comparator is passed as parameter
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardTagNameComparator: function (domobj, markupObject) {
			if (domobj.nodeType === 1) {
				if (domobj.nodeName != markupObject[0].nodeName) {
					return false;
				}
				return true;
			}
			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
			return false;
		},

		/**
		 * standard method, to compare a domobj and a jquery object for text level semantics (aka span elements, e.g. b, i, sup, span, ...).
		 * is always used when no other tag comparator is passed as parameter
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardTextLevelSemanticsComparator: function (domobj, markupObject) {
			// only element nodes can be compared
			if (domobj.nodeType === 1) {
				if (domobj.nodeName != markupObject[0].nodeName) {
					return false;
				}
				if (!this.standardAttributesComparator(domobj, markupObject)) {
					return false;
				}
				return true;
			}
			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
			return false;
		},


		/**
		 * standard method, to compare attributes of one dom obj and one markup obj (jQuery)
		 * @param domobj domobject to compare with markup
		 * @param markupObject jQuery object of the markup to compare with domobj
		 * @return true if objects are equal and false if not
		 * @hide
		 */
		standardAttributesComparator: function (domobj, markupObject) {
			var classesA = Strings.words((domobj && domobj.className) || '');
			var classesB = Strings.words((markupObject.length && markupObject[0].className) || '');
			Arrays.sortUnique(classesA);
			Arrays.sortUnique(classesB);
			return Arrays.equal(classesA, classesB);
		},

		/**
		 * method finds out, if a node is within a certain markup or not
		 * @param rangeObj Aloha rangeObject
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return void; TODO: should return true if the markup applied successfully and false if not
		 * @hide
		 */
		changeMarkup: function (rangeObject, markupObject, tagComparator) {
			var tagName = markupObject[0].tagName.toLowerCase(),
				newCAC,
			    limitObject,
				backupRangeObject,
				relevantMarkupObjectsAtSelectionStart = this.isRangeObjectWithinMarkup(rangeObject, false, markupObject, tagComparator, limitObject),
				relevantMarkupObjectsAtSelectionEnd = this.isRangeObjectWithinMarkup(rangeObject, true, markupObject, tagComparator, limitObject),
				nextSibling,
			    relevantMarkupObjectAfterSelection,
				prevSibling,
			    relevantMarkupObjectBeforeSelection,
				extendedRangeObject;
			var parentElement;

			// if the element is a replacing element (like p/h1/h2/h3/h4/h5/h6...), which must not wrap each other
			// use a clone of rangeObject
			if (this.replacingElements[tagName]) {
				// backup rangeObject for later selection;
				backupRangeObject = rangeObject;

				// create a new range object to not modify the orginal
				rangeObject = new this.SelectionRange(rangeObject);

				// either select the active Editable as new commonAncestorContainer (CAC) or use the body
				if (Aloha.activeEditable) {
					newCAC = Aloha.activeEditable.obj.get(0);
				} else {
					newCAC = jQuery('body');
				}
				// update rangeObject by setting the newCAC and automatically recalculating the selectionTree
				rangeObject.update(newCAC);

				// store the information, that the markupObject can be replaced (not must be!!) inside the jQuery markup object
				markupObject.isReplacingElement = true;
			} else {
				// if the element is NOT a replacing element, then something needs to be selected, otherwise it can not be wrapped
				// therefor the method can return false, if nothing is selected ( = rangeObject is collapsed)
				if (rangeObject.isCollapsed()) {
					Aloha.Log.debug(this, 'early returning from applying markup because nothing is currently selected');
					return false;
				}
			}

			// is Start/End DOM Obj inside the markup to change
			if (Aloha.activeEditable) {
				limitObject = Aloha.activeEditable.obj[0];
			} else {
				limitObject = jQuery('body');
			}

			if (!markupObject.isReplacingElement && rangeObject.startOffset === 0) { // don't care about replacers, because they never extend
				if (null != (prevSibling = this.getTextNodeSibling(false, rangeObject.commonAncestorContainer.parentNode, rangeObject.startContainer))) {
					relevantMarkupObjectBeforeSelection = this.isRangeObjectWithinMarkup({
						startContainer: prevSibling,
						startOffset: 0
					}, false, markupObject, tagComparator, limitObject);
				}
			}
			if (!markupObject.isReplacingElement && (rangeObject.endOffset === rangeObject.endContainer.length)) { // don't care about replacers, because they never extend
				if (null != (nextSibling = this.getTextNodeSibling(true, rangeObject.commonAncestorContainer.parentNode, rangeObject.endContainer))) {
					relevantMarkupObjectAfterSelection = this.isRangeObjectWithinMarkup({
						startContainer: nextSibling,
						startOffset: 0
					}, false, markupObject, tagComparator, limitObject);
				}
			}

			// decide what to do (expand or reduce markup)
			// Alternative A: from markup to no-markup: markup will be removed in selection;
			// reapplied from original markup start to selection start
			if (!markupObject.isReplacingElement && (relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd)) {
				Aloha.Log.info(this, 'markup 2 non-markup');
				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
				jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
			} else if (!markupObject.isReplacingElement && relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) {
				// Alternative B: from markup to markup:
				// remove selected markup (=split existing markup if single, shrink if two different)
				Aloha.Log.info(this, 'markup 2 markup');
				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
				this.splitRelevantMarkupObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator);
			} else if (!markupObject.isReplacingElement && ((!relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) || relevantMarkupObjectAfterSelection || relevantMarkupObjectBeforeSelection)) { //
				// Alternative C: from no-markup to markup OR with next2markup:
				// new markup is wrapped from selection start to end of originalmarkup, original is remove afterwards
				Aloha.Log.info(this, 'non-markup 2 markup OR with next2markup');
				// move end of rangeObject to end of relevant markups
				if (relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
					extendedRangeObject.startOffset = 0;
					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectAfterSelection[relevantMarkupObjectAfterSelection.length - 1]).textNodes().last()[0];
					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
					extendedRangeObject.update();
					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
					Aloha.Log.info(this, 'double extending previous markup(previous and after selection), actually wrapping it ...');

				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && !relevantMarkupObjectsAtSelectionEnd) {
					this.extendExistingMarkupWithSelection(relevantMarkupObjectBeforeSelection, rangeObject, false, tagComparator);
					Aloha.Log.info(this, 'extending previous markup');

				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && relevantMarkupObjectsAtSelectionEnd) {
					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
					extendedRangeObject.startOffset = 0;
					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectsAtSelectionEnd[relevantMarkupObjectsAtSelectionEnd.length - 1]).textNodes().last()[0];
					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
					extendedRangeObject.update();
					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
					Aloha.Log.info(this, 'double extending previous markup(previous and relevant at the end), actually wrapping it ...');

				} else if (!relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
					this.extendExistingMarkupWithSelection(relevantMarkupObjectAfterSelection, rangeObject, true, tagComparator);
					Aloha.Log.info(this, 'extending following markup backwards');

				} else {
					this.extendExistingMarkupWithSelection(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
				}
			} else if (markupObject.isReplacingElement || (!relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd && !relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection)) {
				// Alternative D: no-markup to no-markup: easy
				Aloha.Log.info(this, 'non-markup 2 non-markup');

				// workaround to keep the caret at the right position if it's an empty element
				// applyMarkup was not working correctly and has a lot of overhead we don't need in that case
				if (isCollapsedAndEmptyOrEndBr(rangeObject)) {
					var newMarkup = markupObject.clone();

					if (isCollapsedAndEndBr(rangeObject)) {
						newMarkup[0].appendChild(Engine.createEndBreak());
					}

					// setting the focus is needed for mozilla and IE 7 to have a working rangeObject.select()
					if (Aloha.activeEditable && jQuery.browser.mozilla) {
						Aloha.activeEditable.obj.focus();
					}

					if (Engine.isEditable(rangeObject.startContainer)) {
						Engine.copyAttributes(rangeObject.startContainer, newMarkup[0]);
						jQuery(rangeObject.startContainer).after(newMarkup[0]).remove();
					} else if (Engine.isEditingHost(rangeObject.startContainer)) {
						jQuery(rangeObject.startContainer).append(newMarkup[0]);
						Engine.ensureContainerEditable(newMarkup[0]);
					}

					backupRangeObject.startContainer = newMarkup[0];
					backupRangeObject.endContainer = newMarkup[0];
					backupRangeObject.startOffset = 0;
					backupRangeObject.endOffset = 0;
					return;
				}
				this.applyMarkup(rangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator, {
					setRangeObject2NewMarkup: true
				});
				backupRangeObject.startContainer = rangeObject.startContainer;
				backupRangeObject.endContainer = rangeObject.endContainer;
				backupRangeObject.startOffset = rangeObject.startOffset;
				backupRangeObject.endOffset = rangeObject.endOffset;
			}

			if (markupObject.isReplacingElement) {
				//Check if the startContainer is one of the zapped elements
				if (backupRangeObject && backupRangeObject.startContainer.className && backupRangeObject.startContainer.className.indexOf('preparedForRemoval') > -1) {
					//var parentElement = jQuery(backupRangeObject.startContainer).closest(markupObject[0].tagName).get(0);
					parentElement = jQuery(backupRangeObject.startContainer).parents(markupObject[0].tagName).get(0);
					backupRangeObject.startContainer = parentElement;
					rangeObject.startContainer = parentElement;
				}
				//check if the endContainer is one of the zapped elements
				if (backupRangeObject && backupRangeObject.endContainer.className && backupRangeObject.endContainer.className.indexOf('preparedForRemoval') > -1) {
					//var parentElement = jQuery(backupRangeObject.endContainer).closest(markupObject[0].tagName).get(0);
					parentElement = jQuery(backupRangeObject.endContainer).parents(markupObject[0].tagName).get(0);
					backupRangeObject.endContainer = parentElement;
					rangeObject.endContainer = parentElement;
				}
			}
			// remove all marked items
			jQuery('.preparedForRemoval').zap();

			// recalculate cac and selectionTree

			// update selection
			if (markupObject.isReplacingElement) {
				//After the zapping we have to check for wrong offsets
				if (e5s.Node.ELEMENT_NODE === backupRangeObject.startContainer.nodeType && backupRangeObject.startContainer.childNodes && backupRangeObject.startContainer.childNodes.length < backupRangeObject.startOffset) {
					backupRangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
					rangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
				}
				if (e5s.Node.ELEMENT_NODE === backupRangeObject.endContainer.nodeType && backupRangeObject.endContainer.childNodes && backupRangeObject.endContainer.childNodes.length < backupRangeObject.endOffset) {
					backupRangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
					rangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
				}
				rangeObject.endContainer = backupRangeObject.endContainer;
				rangeObject.endOffset = backupRangeObject.endOffset;
				rangeObject.startContainer = backupRangeObject.startContainer;
				rangeObject.startOffset = backupRangeObject.startOffset;
				backupRangeObject.update();
				backupRangeObject.select();
			} else {
				rangeObject.update();
				rangeObject.select();
			}
		},

		/**
		 * method compares a JS array of domobjects with a range object and decides, if the rangeObject spans the whole markup objects. method is used to decide if a markup2markup selection can be completely remove or if it must be splitted into 2 separate markups
		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
		 * @param rangeObj Aloha rangeObject
		 * @return true, if rangeObjects and markup objects are identical, false otherwise
		 * @hide
		 */
		areMarkupObjectsAsLongAsRangeObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject) {
			var i, el, textNode, relMarkupEnd, relMarkupStart;

			if (rangeObject.startOffset !== 0) {
				return false;
			}

			for (i = 0, relMarkupStart = relevantMarkupObjectsAtSelectionStart.length; i < relMarkupStart; i++) {
				el = jQuery(relevantMarkupObjectsAtSelectionStart[i]);
				if (el.textNodes().first()[0] !== rangeObject.startContainer) {
					return false;
				}
			}

			for (i = 0, relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length; i < relMarkupEnd; i++) {
				el = jQuery(relevantMarkupObjectsAtSelectionEnd[i]);
				textNode = el.textNodes().last()[0];
				if (textNode !== rangeObject.endContainer || textNode.length != rangeObject.endOffset) {
					return false;
				}
			}

			return true;
		},

		/**
		 * method used to remove/split markup from a "markup2markup" selection
		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
		 * @param rangeObj Aloha rangeObject
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return true (always, since no "false" case is currently known...but might be added)
		 * @hide
		 */
		splitRelevantMarkupObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator) {
			// mark them to be deleted
			jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
			jQuery(relevantMarkupObjectsAtSelectionEnd).addClass('preparedForRemoval');

			// check if the rangeObject is identical with the relevantMarkupObjects (in this case the markup can simply be removed)
			if (this.areMarkupObjectsAsLongAsRangeObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject)) {
				return true;
			}

			// find intersection (this can always only be one dom element (namely the highest) because all others will be removed
			var relevantMarkupObjectAtSelectionStartAndEnd = this.intersectRelevantMarkupObjects(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd);

			if (relevantMarkupObjectAtSelectionStartAndEnd) {
				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, false, tagComparator);
				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, true, tagComparator);
			} else {
				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
			}
			return true;
		},

		/**
		 * method takes two arrays of bottom up dom objects, compares them and returns either the object closest to the root or false
		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects
		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects
		 * @return dom object closest to the root or false
		 * @hide
		 */
		intersectRelevantMarkupObjects: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd) {
			var intersection = false, i, elStart, j, elEnd, relMarkupStart, relMarkupEnd;
			if (!relevantMarkupObjectsAtSelectionStart || !relevantMarkupObjectsAtSelectionEnd) {
				return intersection; // we can only intersect, if we have to arrays!
			}
			relMarkupStart = relevantMarkupObjectsAtSelectionStart.length;
			relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length;
			for (i = 0; i < relMarkupStart; i++) {
				elStart = relevantMarkupObjectsAtSelectionStart[i];
				for (j = 0; j < relMarkupEnd; j++) {
					elEnd = relevantMarkupObjectsAtSelectionEnd[j];
					if (elStart === elEnd) {
						intersection = elStart;
					}
				}
			}
			return intersection;
		},

		/**
		 * method used to add markup to a nonmarkup2markup selection
		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
		 * @param rangeObject Aloha rangeObject the markups should be extended to
		 * @param startOrEnd boolean; defines, if the existing markups should be extended forwards or backwards (is propably redundant and could be found out by comparing start or end container with the markup array dom objects)
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return true
		 * @hide
		 */
		extendExistingMarkupWithSelection: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
			var extendMarkupsAtStart, extendMarkupsAtEnd, objects, i, relMarkupLength, el, textnodes, nodeNr;
			if (!startOrEnd) { // = Start
				// start part of rangeObject should be used, therefor existing markups are cropped at the end
				extendMarkupsAtStart = true;
			}
			if (startOrEnd) { // = End
				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
				extendMarkupsAtEnd = true;
			}
			objects = [];
			for (i = 0, relMarkupLength = relevantMarkupObjects.length; i < relMarkupLength; i++) {
				objects[i] = new this.SelectionRange();
				el = relevantMarkupObjects[i];
				if (extendMarkupsAtEnd && !extendMarkupsAtStart) {
					objects[i].startContainer = rangeObject.startContainer; // jQuery(el).contents()[0];
					objects[i].startOffset = rangeObject.startOffset;
					textnodes = jQuery(el).textNodes(true);

					nodeNr = textnodes.length - 1;
					objects[i].endContainer = textnodes[nodeNr];
					objects[i].endOffset = textnodes[nodeNr].length;
					objects[i].update();
					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2NewMarkup: true
					});
				}
				if (!extendMarkupsAtEnd && extendMarkupsAtStart) {
					textnodes = jQuery(el).textNodes(true);
					objects[i].startContainer = textnodes[0]; // jQuery(el).contents()[0];
					objects[i].startOffset = 0;
					objects[i].endContainer = rangeObject.endContainer;
					objects[i].endOffset = rangeObject.endOffset;
					objects[i].update();
					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2NewMarkup: true
					});
				}
			}
			return true;
		},

		/**
		 * method creates an empty markup jQuery object from a dom object passed as paramter
		 * @param domobj domobject to be cloned, cleaned and emptied
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return jQuery wrapper object to be passed to e.g. this.applyMarkup(...)
		 * @hide
		 */
		getClonedMarkup4Wrapping: function (domobj) {
			var wrapper = jQuery(domobj.outerHTML).removeClass('preparedForRemoval').empty();
			if (wrapper.attr('class').length === 0) {
				wrapper.removeAttr('class');
			}
			return wrapper;
		},

		/**
		 * method used to subtract the range object from existing markup. in other words: certain markup is removed from the selections defined by the rangeObject
		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
		 * @param rangeObject Aloha rangeObject the markups should be removed from
		 * @param startOrEnd boolean; defines, if the existing markups should be reduced at the beginning of the tag or at the end (is propably redundant and could be found out by comparing start or end container with the markup array dom objects)
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return true
		 * @hide
		 */
		insertCroppedMarkups: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
			var cropMarkupsAtEnd, cropMarkupsAtStart, textnodes, objects, i, el, textNodes;
			if (!startOrEnd) { // = Start
				// start part of rangeObject should be used, therefor existing markups are cropped at the end
				cropMarkupsAtEnd = true;
			} else { // = End
				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
				cropMarkupsAtStart = true;
			}
			objects = [];
			for (i = 0; i < relevantMarkupObjects.length; i++) {
				objects[i] = new this.SelectionRange();
				el = relevantMarkupObjects[i];
				if (cropMarkupsAtEnd && !cropMarkupsAtStart) {
					textNodes = jQuery(el).textNodes(true);
					objects[i].startContainer = textNodes[0];
					objects[i].startOffset = 0;
					// if the existing markup startContainer & startOffset are equal to the rangeObject startContainer and startOffset,
					// then markupobject does not have to be added again, because it would have no content (zero-length)
					if (objects[i].startContainer === rangeObject.startContainer && objects[i].startOffset === rangeObject.startOffset) {
						continue;
					}
					if (rangeObject.startOffset === 0) {
						objects[i].endContainer = this.getTextNodeSibling(false, el, rangeObject.startContainer);
						objects[i].endOffset = objects[i].endContainer.length;
					} else {
						objects[i].endContainer = rangeObject.startContainer;
						objects[i].endOffset = rangeObject.startOffset;
					}

					objects[i].update();

					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2NextSibling: true
					});
				}

				if (!cropMarkupsAtEnd && cropMarkupsAtStart) {
					objects[i].startContainer = rangeObject.endContainer; // jQuery(el).contents()[0];
					objects[i].startOffset = rangeObject.endOffset;
					textnodes = jQuery(el).textNodes(true);
					objects[i].endContainer = textnodes[textnodes.length - 1];
					objects[i].endOffset = textnodes[textnodes.length - 1].length;
					objects[i].update();
					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
						setRangeObject2PreviousSibling: true
					});
				}
			}
			return true;
		},

		/**
		 * apply a certain markup to the current selection
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return void
		 * @hide
		 */
		changeMarkupOnSelection: function (markupObject) {
			var rangeObject = this.getRangeObject();

			// change the markup
			this.changeMarkup(rangeObject, markupObject, this.getStandardTagComparator(markupObject));

			// merge text nodes
			GENTICS.Utils.Dom.doCleanup({
				'merge': true
			}, rangeObject);

			// update the range and select it
			rangeObject.update();
			rangeObject.select();
			this.rangeObject = rangeObject;
		},

		/**
		 * apply a certain markup to the selection Tree
		 * @param selectionTree SelectionTree Object markup should be applied to
		 * @param rangeObject Aloha rangeObject which will be modified to reflect the dom changes, after the markup was applied (only if activated via options)
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
		 * @return void
		 * @hide
		 */
		applyMarkup: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
			var optimizedSelectionTree, i, el, breakpoint;
			options = options || {};
			// first same tags from within fully selected nodes for removal
			this.prepareForRemoval(selectionTree, markupObject, tagComparator);

			// first let's optimize the selection Tree in useful groups which can be wrapped together
			optimizedSelectionTree = this.optimizeSelectionTree4Markup(selectionTree, markupObject, tagComparator);
			breakpoint = true;

			// now iterate over grouped elements and either recursively dive into object or wrap it as a whole
			for (i = 0; i < optimizedSelectionTree.length; i++) {
				el = optimizedSelectionTree[i];
				if (el.wrappable) {
					this.wrapMarkupAroundSelectionTree(el.elements, rangeObject, markupObject, tagComparator, options);
				} else {
					Aloha.Log.debug(this, 'dive further into non-wrappable object');
					this.applyMarkup(el.element.children, rangeObject, markupObject, tagComparator, options);
				}
			}
		},

		/**
		 * returns the type of the given markup (trying to match HTML5)
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return string name of the markup type
		 * @hide
		 */
		getMarkupType: function (markupObject) {
			var nn = jQuery(markupObject)[0].nodeName.toLowerCase();
			if (markupObject.outerHtml) {
				Aloha.Log.debug(this, 'Node name detected: ' + nn + ' for: ' + markupObject.outerHtml());
			}
			if (nn == '#text') {
				return 'textNode';
			}
			if (this.replacingElements[nn]) {
				return 'sectionOrGroupingContent';
			}
			if (this.tagHierarchy[nn]) {
				return 'textLevelSemantics';
			}
			Aloha.Log.warn(this, 'unknown markup passed to this.getMarkupType(...): ' + markupObject.outerHtml());
		},

		/**
		 * returns the standard tag comparator for the given markup object
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return function tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @hide
		 */
		getStandardTagComparator: function (markupObject) {
			var that = this,
				result;
			switch (this.getMarkupType(markupObject)) {
			case 'textNode':
				result = function (p1, p2) {
					return false;
				};
				break;

			case 'sectionOrGroupingContent':
				result = function (domobj, markupObject) {
					return that.standardSectionsAndGroupingContentComparator(domobj, markupObject);
				};
				break;

			//case 'textLevelSemantics' covered by default
			default:
				result = function (domobj, markupObject) {
					return that.standardTextLevelSemanticsComparator(domobj, markupObject);
				};
				break;
			}
			return result;
		},

		/**
		 * searches for fully selected equal markup tags
		 * @param selectionTree SelectionTree Object markup should be applied to
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @return void
		 * @hide
		 */
		prepareForRemoval: function (selectionTree, markupObject, tagComparator) {
			var that = this, i, el;

			// check if a comparison method was passed as parameter ...
			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
				Aloha.Log.error(this, 'parameter tagComparator is not a function');
			}
			// ... if not use this as standard tag comparison method
			if (typeof tagComparator === 'undefined') {
				tagComparator = this.getStandardTagComparator(markupObject);
			}
			for (i = 0; i < selectionTree.length; i++) {
				el = selectionTree[i];
				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
					// mark for removal
					if (el.domobj.nodeType === 1 && tagComparator(el.domobj, markupObject)) {
						Aloha.Log.debug(this, 'Marking for removal: ' + el.domobj.nodeName);
						jQuery(el.domobj).addClass('preparedForRemoval');
					}
				}
				if (el.selection != 'none' && el.children.length > 0) {
					this.prepareForRemoval(el.children, markupObject, tagComparator);
				}

			}
		},

		/**
		 * searches for fully selected equal markup tags
		 * @param selectionTree SelectionTree Object markup should be applied to
		 * @param rangeObject Aloha rangeObject the markup will be applied to
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
		 * @return void
		 * @hide
		 */
		wrapMarkupAroundSelectionTree: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
			// first let's find out if theoretically the whole selection can be wrapped with one tag and save it for later use
			var objects2wrap = [], // // this will be used later to collect objects
				j = -1, // internal counter,
				breakpoint = true,
				preText = '',
				postText = '',
				prevOrNext,
				textNode2Start,
				textnodes,
				newMarkup,
				i,
			    el,
			    middleText;

			Aloha.Log.debug(this, 'The formatting <' + markupObject[0].tagName + '> will be wrapped around the selection');

			// now lets iterate over the elements
			for (i = 0; i < selectionTree.length; i++) {
				el = selectionTree[i];

				// check if markup is allowed inside the elements parent
				if (el.domobj && !this.canTag1WrapTag2(el.domobj.parentNode.tagName.toLowerCase(), markupObject[0].tagName.toLowerCase())) {
					Aloha.Log.info(this, 'Skipping the wrapping of <' + markupObject[0].tagName.toLowerCase() + '> because this tag is not allowed inside <' + el.domobj.parentNode.tagName.toLowerCase() + '>');
					continue;
				}

				// skip empty text nodes
				if (el.domobj && el.domobj.nodeType === 3 && jQuery.trim(el.domobj.nodeValue).length === 0) {
					continue;
				}

				// partial element, can either be a textnode and therefore be wrapped (at least partially)
				// or can be a nodeType == 1 (tag) which must be dived into
				if (el.domobj && el.selection == 'partial' && !markupObject.isReplacingElement) {
					if (el.startOffset !== undefined && el.endOffset === undefined) {
						j++;
						preText += el.domobj.data.substr(0, el.startOffset);
						el.domobj.data = el.domobj.data.substr(el.startOffset, el.domobj.data.length - el.startOffset);
						objects2wrap[j] = el.domobj;
					} else if (el.endOffset !== undefined && el.startOffset === undefined) {
						j++;
						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
						el.domobj.data = el.domobj.data.substr(0, el.endOffset);
						objects2wrap[j] = el.domobj;
					} else if (el.endOffset !== undefined && el.startOffset !== undefined) {
						if (el.startOffset == el.endOffset) { // do not wrap empty selections
							Aloha.Log.debug(this, 'skipping empty selection');
							continue;
						}
						j++;
						preText += el.domobj.data.substr(0, el.startOffset);
						middleText = el.domobj.data.substr(el.startOffset, el.endOffset - el.startOffset);
						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
						el.domobj.data = middleText;
						objects2wrap[j] = el.domobj;
					} else {
						// a partially selected item without selectionStart/EndOffset is a nodeType 1 Element on the way to the textnode
						Aloha.Log.debug(this, 'diving into object');
						this.applyMarkup(el.children, rangeObject, markupObject, tagComparator, options);
					}
				}
				// fully selected dom elements can be wrapped as whole element
				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
					j++;
					objects2wrap[j] = el.domobj;
				}
			}

			if (objects2wrap.length > 0) {
				// wrap collected DOM object with markupObject
				objects2wrap = jQuery(objects2wrap);

				// make a fix for text nodes in <li>'s in ie
				jQuery.each(objects2wrap, function (index, element) {
					if (jQuery.browser.msie && element.nodeType == 3 && !element.nextSibling && !element.previousSibling && element.parentNode && element.parentNode.nodeName.toLowerCase() == 'li') {
						element.data = jQuery.trim(element.data);
					}
				});

				newMarkup = objects2wrap.wrapAll(markupObject).parent();
				newMarkup.before(preText).after(postText);

				if (options.setRangeObject2NewMarkup) { // this is used, when markup is added to normal/normal Text
					textnodes = objects2wrap.textNodes();

					if (textnodes.index(rangeObject.startContainer) != -1) {
						rangeObject.startOffset = 0;
					}
					if (textnodes.index(rangeObject.endContainer) != -1) {
						rangeObject.endOffset = rangeObject.endContainer.length;
					}
					breakpoint = true;
				}
				if (options.setRangeObject2NextSibling) {
					prevOrNext = true;
					textNode2Start = newMarkup.textNodes(true).last()[0];
					if (objects2wrap.index(rangeObject.startContainer) != -1) {
						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.startOffset = 0;
					}
					if (objects2wrap.index(rangeObject.endContainer) != -1) {
						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.endOffset = rangeObject.endOffset - textNode2Start.length;
					}
				}
				if (options.setRangeObject2PreviousSibling) {
					prevOrNext = false;
					textNode2Start = newMarkup.textNodes(true).first()[0];
					if (objects2wrap.index(rangeObject.startContainer) != -1) {
						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.startOffset = 0;
					}
					if (objects2wrap.index(rangeObject.endContainer) != -1) {
						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
						rangeObject.endOffset = rangeObject.endContainer.length;
					}
				}
			}
		},

		/**
		 * takes a text node and return either the next recursive text node sibling or the previous
		 * @param previousOrNext boolean, false for previous, true for next sibling
		 * @param commonAncestorContainer dom object to be used as root for the sibling search
		 * @param currentTextNode dom object of the originating text node
		 * @return dom object of the sibling text node
		 * @hide
		 */
		getTextNodeSibling: function (previousOrNext, commonAncestorContainer, currentTextNode) {
			var textNodes = jQuery(commonAncestorContainer).textNodes(true), newIndex, index;

			index = textNodes.index(currentTextNode);
			if (index == -1) { // currentTextNode was not found
				return false;
			}
			newIndex = index + (!previousOrNext ? -1 : 1);
			return textNodes[newIndex] || false;
		},

		/**
		 * takes a selection tree and groups it into markup wrappable selection trees
		 * @param selectionTree rangeObject selection tree
		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
		 * @return JS array of wrappable selection trees
		 * @hide
		 */
		optimizeSelectionTree4Markup: function (selectionTree, markupObject, tagComparator) {
			var groupMap = [],
				outerGroupIndex = 0,
				innerGroupIndex = 0,
				that = this,
				i,
			    j,
				endPosition,
			    startPosition;

			if (typeof tagComparator === 'undefined') {
				tagComparator = function (domobj, markupObject) {
					return that.standardTextLevelSemanticsComparator(markupObject);
				};
			}
			for (i = 0; i < selectionTree.length; i++) {
				// we are just interested in selected item, but not in non-selected items
				if (selectionTree[i].domobj && selectionTree[i].selection != 'none') {
					if (markupObject.isReplacingElement && tagComparator(markupObject[0], jQuery(selectionTree[i].domobj))) {
						if (groupMap[outerGroupIndex] !== undefined) {
							outerGroupIndex++;
						}
						groupMap[outerGroupIndex] = {};
						groupMap[outerGroupIndex].wrappable = true;
						groupMap[outerGroupIndex].elements = [];
						groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
						outerGroupIndex++;

					} else if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[i]], markupObject)) {
						// now check, if the children of our item could be wrapped all together by the markup object
						// if yes, add it to the current group
						if (groupMap[outerGroupIndex] === undefined) {
							groupMap[outerGroupIndex] = {};
							groupMap[outerGroupIndex].wrappable = true;
							groupMap[outerGroupIndex].elements = [];
						}
						if (markupObject.isReplacingElement) { //  && selectionTree[i].domobj.nodeType === 3
							/* we found the node to wrap for a replacing element. however there might
							 * be siblings which should be included as well
							 * although they are actually not selected. example:
							 * li
							 * |-textNode ( .selection = 'none')
							 * |-textNode (cursor inside, therefor .selection = 'partial')
							 * |-textNode ( .selection = 'none')
							 *
							 * in this case it would be useful to select the previous and following textNodes as well (they might result from a previous DOM manipulation)
							 * Think about other cases, where the parent is the Editable. In this case we propably only want to select from and until the next <br /> ??
							 * .... many possibilities, here I realize the two described cases
							 */

							// first find start element starting from the current element going backwards until sibling 0
							startPosition = i;
							for (j = i - 1; j >= 0; j--) {
								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
									startPosition = j;
								} else {
									break;
								}
							}

							// now find the end element starting from the current element going forward until the last sibling
							endPosition = i;
							for (j = i + 1; j < selectionTree.length; j++) {
								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
									endPosition = j;
								} else {
									break;
								}
							}

							// now add the elements to the groupMap
							innerGroupIndex = 0;
							for (j = startPosition; j <= endPosition; j++) {
								groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[j];
								groupMap[outerGroupIndex].elements[innerGroupIndex].selection = 'full';
								innerGroupIndex++;
							}
							innerGroupIndex = 0;
						} else {
							// normal text level semantics object, no siblings need to be selected
							groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
							innerGroupIndex++;
						}
					} else {
						// if no, isolate it in its own group
						if (groupMap[outerGroupIndex] !== undefined) {
							outerGroupIndex++;
						}
						groupMap[outerGroupIndex] = {};
						groupMap[outerGroupIndex].wrappable = false;
						groupMap[outerGroupIndex].element = selectionTree[i];
						innerGroupIndex = 0;
						outerGroupIndex++;
					}
				}
			}
			return groupMap;
		},

		/**
		 * very tricky method, which decides, if a certain markup (normally a replacing markup element like p, h1, blockquote)
		 * is allowed to extend the user selection to other dom objects (represented as selectionTreeElement)
		 * to understand the purpose: if the user selection is collapsed inside e.g. some text, which is currently not
		 * wrapped by the markup to be applied, and therefor the markup does not have an equal markup to replace, then the DOM
		 * manipulator has to decide which objects to wrap. real example:
		 * <div>
		 *	<h1>headline</h1>
		 *	some text blabla bla<br>
		 *	more text HERE THE | CURSOR BLINKING and <b>even more bold text</b>
		 * </div>
		 * when the user now wants to apply e.g. a <p> tag, what will be wrapped? it could be useful if the manipulator would actually
		 * wrap everything inside the div except the <h1>. but for this purpose someone has to decide, if the markup is
		 * allowed to wrap certain dom elements in this case the question would be, if the <p> is allowed to wrap
		 * textNodes, <br> and <b> and <h1>. therefore this tricky method should answer the question for those 3 elements
		 * with true, but for for the <h1> it should return false. and since the method does not know this, there is a configuration
		 * for this
		 *
		 * @param selectionTree rangeObject selection tree element (only one, not an array of)
		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
		 * @return true if the markup is allowed to wrap the selection tree element, false otherwise
		 * @hide
		 */
		isMarkupAllowedToStealSelectionTreeElement: function (selectionTreeElement, markupObject) {
			if (!selectionTreeElement.domobj) {
				return false;
			}
			var maybeTextNodeName = selectionTreeElement.domobj.nodeName.toLowerCase(),
				nodeName = (maybeTextNodeName == '#text') ? 'textNode' : maybeTextNodeName,
				markupName = markupObject[0].nodeName.toLowerCase(),
				elemMap = this.allowedToStealElements[markupName];
			return elemMap && elemMap[nodeName];
		},

		/**
		 * checks if a selection can be completey wrapped by a certain html tags (helper method for this.optimizeSelectionTree4Markup
		 * @param selectionTree rangeObject selection tree
		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
		 * @return true if selection can be applied as whole, false otherwise
		 * @hide
		 */
		canMarkupBeApplied2ElementAsWhole: function (selectionTree, markupObject) {
			var htmlTag, i, el, returnVal;

			if (markupObject.jquery) {
				htmlTag = markupObject[0].tagName;
			}
			if (markupObject.tagName) {
				htmlTag = markupObject.tagName;
			}

			returnVal = true;
			for (i = 0; i < selectionTree.length; i++) {
				el = selectionTree[i];
				if (el.domobj && (el.selection != "none" || markupObject.isReplacingElement)) {
					// Aloha.Log.debug(this, 'Checking, if  <' + htmlTag + '> can be applied to ' + el.domobj.nodeName);
					if (!this.canTag1WrapTag2(htmlTag, el.domobj.nodeName)) {
						return false;
					}
					if (el.children.length > 0 && !this.canMarkupBeApplied2ElementAsWhole(el.children, markupObject)) {
						return false;
					}
				}
			}
			return returnVal;
		},

		/**
		 * checks if a tag 1 (first parameter) can wrap tag 2 (second parameter).
		 * IMPORTANT: the method does not verify, if there have to be other tags in between
		 * Example: this.canTag1WrapTag2("table", "td") will return true, because the method does not take into account, that there has to be a "tr" in between
		 * @param t1 string: tagname of outer tag to verify, e.g. "b"
		 * @param t2 string: tagname of inner tag to verify, e.g. "b"
		 * @return true if tag 1 can wrap tag 2, false otherwise
		 * @hide
		 */
		canTag1WrapTag2: function (t1, t2) {
			t1 = (t1 == '#text') ? 'textNode' : t1.toLowerCase();
			t2 = (t2 == '#text') ? 'textNode' : t2.toLowerCase();
			var t1Map = this.tagHierarchy[t1];
			if (!t1Map) {
				return true;
			}
			if (!this.tagHierarchy[t2]) {
				return true;
			}
			return t1Map[t2];
		},

		/**
		 * Check whether it is allowed to insert the given tag at the start of the
		 * current selection. This method will check whether the markup effective for
		 * the start and outside of the editable part (starting with the editable tag
		 * itself) may wrap the given tag.
		 * @param tagName {String} name of the tag which shall be inserted
		 * @return true when it is allowed to insert that tag, false if not
		 * @hide
		 */
		mayInsertTag: function (tagName) {
			var i;
			if (typeof this.rangeObject.unmodifiableMarkupAtStart == 'object') {
				// iterate over all DOM elements outside of the editable part
				for (i = 0; i < this.rangeObject.unmodifiableMarkupAtStart.length; ++i) {
					// check whether an element may not wrap the given
					if (!this.canTag1WrapTag2(this.rangeObject.unmodifiableMarkupAtStart[i].nodeName, tagName)) {
						// found a DOM element which forbids to insert the given tag, we are done
						return false;
					}
				}

				// all of the found DOM elements allow inserting the given tag
				return true;
			}
			Aloha.Log.warn(this, 'Unable to determine whether tag ' + tagName + ' may be inserted');
			return true;
		},

		/**
		 * String representation
		 * @return "Aloha.Selection"
		 * @hide
		 */
		toString: function () {
			return 'Aloha.Selection';
		},

		/**
		 * @namespace Aloha.Selection
		 * @class SelectionRange
		 * @extends GENTICS.Utils.RangeObject
		 * Constructor for a range object.
		 * Optionally you can pass in a range object that's properties will be assigned to the new range object.
		 * @param rangeObject A range object thats properties will be assigned to the new range object.
		 * @constructor
		 */
		SelectionRange: GENTICS.Utils.RangeObject.extend({
			_constructor: function (rangeObject) {
				this._super(rangeObject);
				// If a range object was passed in we apply the values to the new range object
				if (rangeObject) {
					if (rangeObject.commonAncestorContainer) {
						this.commonAncestorContainer = rangeObject.commonAncestorContainer;
					}
					if (rangeObject.selectionTree) {
						this.selectionTree = rangeObject.selectionTree;
					}
					if (rangeObject.limitObject) {
						this.limitObject = rangeObject.limitObject;
					}
					if (rangeObject.markupEffectiveAtStart) {
						this.markupEffectiveAtStart = rangeObject.markupEffectiveAtStart;
					}
					if (rangeObject.unmodifiableMarkupAtStart) {
						this.unmodifiableMarkupAtStart = rangeObject.unmodifiableMarkupAtStart;
					}
					if (rangeObject.splitObject) {
						this.splitObject = rangeObject.splitObject;
					}
				}
			},

			/**
			 * DOM object of the common ancestor from startContainer and endContainer
			 * @hide
			 */
			commonAncestorContainer: undefined,

			/**
			 * The selection tree
			 * @hide
			 */
			selectionTree: undefined,

			/**
			 * Array of DOM objects effective for the start container and inside the
			 * editable part (inside the limit object). relevant for the button status
			 * @hide
			 */
			markupEffectiveAtStart: [],

			/**
			 * Array of DOM objects effective for the start container, which lies
			 * outside of the editable portion (starting with the limit object)
			 * @hide
			 */
			unmodifiableMarkupAtStart: [],

			/**
			 * DOM object being the limit for all markup relevant activities
			 * @hide
			 */
			limitObject: undefined,

			/**
			 * DOM object being split when enter key gets hit
			 * @hide
			 */
			splitObject: undefined,

			/**
			 * Sets the visible selection in the Browser based on the range object.
			 * If the selection is collapsed, this will result in a blinking cursor,
			 * otherwise in a text selection.
			 * @method
			 */
			select: function () {
				// Call Utils' select()
				this._super();

				// update the selection
				Aloha.Selection.updateSelection();
			},

			/**
			 * Method to update a range object internally
			 * @param commonAncestorContainer (DOM Object); optional Parameter; if set, the parameter
			 * will be used instead of the automatically calculated CAC
			 * @return void
			 * @hide
			 */
			update: function (commonAncestorContainer) {
				this.updatelimitObject();
				this.updateMarkupEffectiveAtStart();
				this.updateCommonAncestorContainer(commonAncestorContainer);

				// reset the selectiontree (must be recalculated)
				this.selectionTree = undefined;
			},

			/**
			 * Get the selection tree for this range
			 * TODO: remove this (was moved to range.js)
			 * @return selection tree
			 * @hide
			 */
			getSelectionTree: function () {
				// if not yet calculated, do this now
				if (!this.selectionTree) {
					this.selectionTree = Aloha.Selection.getSelectionTree(this);
				}

				return this.selectionTree;
			},

			/**
			 * TODO: move this to range.js
			 * Get an array of domobj (in dom tree order) of siblings of the given domobj, which are contained in the selection
			 * @param domobj dom object to start with
			 * @return array of siblings of the given domobj, which are also selected
			 * @hide
			 */
			getSelectedSiblings: function (domobj) {
				var selectionTree = this.getSelectionTree();

				return this.recursionGetSelectedSiblings(domobj, selectionTree);
			},

			/**
			 * TODO: move this to range.js
			 * Recursive method to find the selected siblings of the given domobj (which should be selected as well)
			 * @param domobj dom object for which the selected siblings shall be found
			 * @param selectionTree current level of the selection tree
			 * @return array of selected siblings of dom objects or false if none found
			 * @hide
			 */
			recursionGetSelectedSiblings: function (domobj, selectionTree) {
				var selectedSiblings = false,
					foundObj = false,
					i;

				for (i = 0; i < selectionTree.length; ++i) {
					if (selectionTree[i].domobj === domobj) {
						foundObj = true;
						selectedSiblings = [];
					} else if (!foundObj && selectionTree[i].children) {
						// do the recursion
						selectedSiblings = this.recursionGetSelectedSiblings(domobj, selectionTree[i].children);
						if (selectedSiblings !== false) {
							break;
						}
					} else if (foundObj && selectionTree[i].domobj && selectionTree[i].selection != 'collapsed' && selectionTree[i].selection != 'none') {
						selectedSiblings.push(selectionTree[i].domobj);
					} else if (foundObj && selectionTree[i].selection == 'none') {
						break;
					}
				}

				return selectedSiblings;
			},

			/**
			 * TODO: move this to range.js
			 * Method updates member var markupEffectiveAtStart and splitObject, which is relevant primarily for button status and enter key behaviour
			 * @return void
			 * @hide
			 */
			updateMarkupEffectiveAtStart: function () {
				// reset the current markup
				this.markupEffectiveAtStart = [];
				this.unmodifiableMarkupAtStart = [];

				var parents = this.getStartContainerParents(),
					limitFound = false,
					splitObjectWasSet,
					i,
				    el;

				for (i = 0; i < parents.length; i++) {
					el = parents[i];
					if (!limitFound && (el !== this.limitObject)) {
						this.markupEffectiveAtStart[i] = el;
						if (!splitObjectWasSet && GENTICS.Utils.Dom.isSplitObject(el)) {
							splitObjectWasSet = true;
							this.splitObject = el;
						}
					} else {
						limitFound = true;
						this.unmodifiableMarkupAtStart.push(el);
					}
				}
				if (!splitObjectWasSet) {
					this.splitObject = false;
				}
				return;
			},

			/**
			 * TODO: remove this
			 * Method updates member var markupEffectiveAtStart, which is relevant primarily for button status
			 * @return void
			 * @hide
			 */
			updatelimitObject: function () {
				if (Aloha.editables && Aloha.editables.length > 0) {
					var parents = this.getStartContainerParents(),
						editables = Aloha.editables,
						i,
					    el,
					    j,
					    editable;
					for (i = 0; i < parents.length; i++) {
						el = parents[i];
						for (j = 0; j < editables.length; j++) {
							editable = editables[j].obj[0];
							if (el === editable) {
								this.limitObject = el;
								return true;
							}
						}
					}
				}
				this.limitObject = jQuery('body');
				return true;
			},

			/**
			 * string representation of the range object
			 * @param	verbose	set to true for verbose output
			 * @return string representation of the range object
			 * @hide
			 */
			toString: function (verbose) {
				if (!verbose) {
					return 'Aloha.Selection.SelectionRange';
				}
				return 'Aloha.Selection.SelectionRange {start [' + this.startContainer.nodeValue + '] offset ' + this.startOffset + ', end [' + this.endContainer.nodeValue + '] offset ' + this.endOffset + '}';
			}

		}) // SelectionRange

	}); // Selection


	/**
	 * This method implements an ugly workaround for a selection problem in ie:
	 * when the cursor shall be placed at the end of a text node in a li element, that is followed by a nested list,
	 * the selection would always snap into the first li of the nested list
	 * therefore, we make sure that the text node ends with a space and place the cursor right before it
	 */
	function nestedListInIEWorkaround(range) {
		var nextSibling;
		if (jQuery.browser.msie && range.startContainer === range.endContainer && range.startOffset === range.endOffset && range.startContainer.nodeType == 3 && range.startOffset == range.startContainer.data.length && range.startContainer.nextSibling) {
			nextSibling = range.startContainer.nextSibling;
			if ('OL' === nextSibling.nodeName || 'UL' === nextSibling.nodeName) {
				if (range.startContainer.data[range.startContainer.data.length - 1] == ' ') {
					range.startOffset = range.endOffset = range.startOffset - 1;
				} else {
					range.startContainer.data = range.startContainer.data + ' ';
				}
			}
		}
	}

	function correctRange(range) {
		nestedListInIEWorkaround(range);
		return range;
	}

	/**
	 * Implements Selection http://html5.org/specs/dom-range.html#selection
	 * @namespace Aloha
	 * @class Selection This singleton class always represents the
	 *        current user selection
	 * @singleton
	 */
	var AlohaSelection = Class.extend({

		_constructor: function (nativeSelection) {

			this._nativeSelection = nativeSelection;
			this.ranges = [];

			// will remember if urged to not change the selection
			this.preventChange = false;

		},

		/**
		 * Returns the element that contains the start of the selection. Returns null if there's no selection.
		 * @readonly
		 * @type Node
		 */
		anchorNode: null,

		/**
		 * Returns the offset of the start of the selection relative to the element that contains the start 
		 * of the selection. Returns 0 if there's no selection.
		 * @readonly
		 * @type int
		 */
		anchorOffset: 0,

		/**
		 * Returns the element that contains the end of the selection.
		 * Returns null if there's no selection.
		 * @readonly
		 * @type Node
		 */
		focusNode: null,

		/**
		 * Returns the offset of the end of the selection relative to the element that contains the end 
		 * of the selection. Returns 0 if there's no selection.
		 * @readonly
		 * @type int
		 */
		focusOffset: 0,

		/**
		 * Returns true if there's no selection or if the selection is empty. Otherwise, returns false.
		 * @readonly
		 * @type boolean
		 */
		isCollapsed: false,

		/**
		 * Returns the number of ranges in the selection.
		 * @readonly
		 * @type int
		 */
		rangeCount: 0,

		/**
		 * Replaces the selection with an empty one at the given position.
		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
		 * @param parentNode Node of new selection
		 * @param offest offest of new Selection in parentNode
		 * @void
		 */
		collapse: function (parentNode, offset) {
			this._nativeSelection.collapse(parentNode, offset);
		},

		/**
		 * Replaces the selection with an empty one at the position of the start of the current selection.
		 * @throws an INVALID_STATE_ERR exception if there is no selection.
		 * @void
		 */
		collapseToStart: function () {
			throw "NOT_IMPLEMENTED";
		},

		/** 
		 * @void
		 */
		extend: function (parentNode, offset) {

		},

		/**
		 * @param alter DOMString 
		 * @param direction DOMString 
		 * @param granularity DOMString 
		 * @void
		 */
		modify: function (alter, direction, granularity) {

		},

		/**
		 * Replaces the selection with an empty one at the position of the end of the current selection.
		 * @throws an INVALID_STATE_ERR exception if there is no selection.
		 * @void
		 */
		collapseToEnd: function () {
			this._nativeSelection.collapseToEnd();
		},

		/**
		 * Replaces the selection with one that contains all the contents of the given element.
		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
		 * @param parentNode Node the Node fully select
		 * @void
		 */
		selectAllChildren: function (parentNode) {
			throw "NOT_IMPLEMENTED";
		},

		/**
		 * Deletes the contents of the selection
		 */
		deleteFromDocument: function () {
			throw "NOT_IMPLEMENTED";
		},

		/**
		 * NB!
		 * We have serious problem in IE.
		 * The range that we get in IE is not the same as the range we had set,
		 * so even if we normalize it during getRangeAt, in IE, we will be
		 * correcting the range to the "correct" place, but still not the place
		 * where it was originally set.
		 * 
		 * Returns the given range.
		 * The getRangeAt(index) method returns the indexth range in the list. 
		 * NOTE: Aloha Editor only support 1 range! index can only be 0
		 * @throws INDEX_SIZE_ERR DOM exception if index is less than zero or 
		 * greater or equal to the value returned by the rangeCount.
		 * @param index int 
		 * @return Range return the selected range from index
		 */
		getRangeAt: function (index) {
			return correctRange(this._nativeSelection.getRangeAt(index));
			//if ( index < 0 || this.rangeCount ) {
			//	throw "INDEX_SIZE_ERR DOM";
			//}
			//return this._ranges[index];
		},

		/**
		 * Adds the given range to the selection.
		 * The addRange(range) method adds the given range Range object to the list of
		 * selections, at the end (so the newly added range is the new last range). 
		 * NOTE: Aloha Editor only support 1 range! The added range will replace the 
		 * range at index 0
		 * see http://html5.org/specs/dom-range.html#selection note about addRange
		 * @throws an INVALID_NODE_TYPE_ERR exception if the given Range has a boundary point
		 * node that's not a Text or Element node, and an INVALID_MODIFICATION_ERR exception 
		 * if it has a boundary point node that doesn't descend from a Document.
		 * @param range Range adds the range to the selection
		 * @void
		 */
		addRange: function (range) {
			// set readonly attributes
			this._nativeSelection.addRange(range);
			// We will correct the range after rangy has processed the native
			// selection range, so that our correction will be the final fix on
			// the range according to the guarentee's that Aloha wants to make
			this._nativeSelection._ranges[0] = correctRange(range);

			// make sure, the old Aloha selection will be updated (until all implementations use the new AlohaSelection)
			Aloha.Selection.updateSelection();
		},

		/**
		 * Removes the given range from the selection, if the range was one of the ones in the selection.
		 * NOTE: Aloha Editor only support 1 range! The added range will replace the 
		 * range at with index 0
		 * @param range Range removes the range from the selection
		 * @void
		 */
		removeRange: function (range) {
			this._nativeSelection.removeRange();
		},

		/**
		 * Removes all the ranges in the selection.
		 * @viod
		 */
		removeAllRanges: function () {
			this._nativeSelection.removeAllRanges();
		},

		/**
		 * INFO: Method is used for integration with Gentics
		 * Aloha, has no use otherwise Updates the rangeObject
		 * according to the current user selection Method is
		 * always called on selection change
		 * 
		 * @param event
		 *            jQuery browser event object
		 * @return true when rangeObject was modified, false
		 *         otherwise
		 * @hide
		 */
		refresh: function (event) {

		},

		/**
		 * String representation
		 * 
		 * @return "Aloha.Selection"
		 * @hide
		 */
		toString: function () {
			return 'Aloha.Selection';
		},

		getRangeCount: function () {
			return this._nativeSelection.rangeCount;
		}

	});

	/**
	 * A wrapper for the function of the same name in the rangy core-depdency.
	 * This function should be preferred as it hides the global rangy object.
	 * For more information look at the following sites:
	 * http://html5.org/specs/dom-range.html
	 * @param window optional - specifices the window to get the selection of
	 */
	Aloha.getSelection = function (target) {
		target = (target !== document || target !== window) ? window : target;
		// Aloha.Selection.refresh()
		// implement Aloha Selection 
		// TODO cache
		return new AlohaSelection(window.rangy.getSelection(target));
	};

	/**
	 * A wrapper for the function of the same name in the rangy core-depdency.
	 * This function should be preferred as it hides the global rangy object.
	 * Please note: when the range object is not needed anymore,
	 *   invoke the detach method on it. It is currently unknown to me why
	 *   this is required, but that's what it says in the rangy specification.
	 * For more information look at the following sites:
	 * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
	 * @param document optional - specifies which document to create the range for
	 */
	Aloha.createRange = function (givenWindow) {
		return window.rangy.createRange(givenWindow);
	};

	var selection = new Selection();
	Aloha.Selection = selection;

	return selection;
});

/* block-jump.js is part of the Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Implements some logic related to moving the cursor keys across blocks.
 * 
 * In the following example
 *
 * "some text<span class="aloha-block ..." contenteditable="false" ...>...</span>[]some text"
 *
 * when one moves the cursor indicated by "[]" to the left, the entire
 * non-contenteditable block is skipped. The same for moving the cursor
 * right across the block.
 *
 * TODO: actually, the block shouldn't be skipped, it should be
 *       selected/highlighted first.
 * TODO: this file currently doesn't contain all the code to implement
 *       block jumping. Some of it is currently implemented in markup.js.
 */
define('aloha/block-jump',[
	'aloha/core',
	'jquery',
	'aloha/console'
], function (
	Aloha,
	$,
	console
) {
	

	var zeroWidthNode = null;

	/**
	 * Replaces the text in given text with the given text.
	 *
	 * @param node
	 *        A text node attached to the DOM.
	 * @param text
	 *        A string that is to replace the text of the given text node.
	 */
	function replaceMergeTextNode(node, text) {
		node.deleteData(0, node.length);
		if ('' !== text) {
			if (node.nextSibling && 3 === node.nextSibling.nodeType) {
				node.nextSibling.insertData(0, text);
			} else if (node.previousSibling && 3 === node.previousSibling.nodeType) {
				node.previousSibling.insertData(node.previousSibling.length, text);
			} else {
				node.insertData(0, text);
			}
		}
		// We don't remove the node immediately to avoid intefering with a
		// caller's range object that may have a start or end containers
		// equal to this node. Removing it in a timeout may still interfere
		// with the selection, but that was not a problem during testing.
		setTimeout(function () {
			if (0 === node.length) {
				$(node).remove();
			}
		}, 0);
	}

	/**
	 * Removes a previously inserted zero width text node.
	 * See insertZeroWidthTextNodeFix().
	 */
	function removeZeroWidthTextNodeFix() {
		if (!zeroWidthNode) {
			return;
		}
		// We want to only replace a single zero-width character to avoid
		// interfering with the other zero-width whitespace hack that makes
		// empty lines visible in IE7.
		var text = zeroWidthNode.nodeValue.replace(/\u200b/, '');
		if (text === zeroWidthNode.nodeValue) {
			console.warn('Expected to remove the zero width text node fix, but couldn\'t find it');
		}
		replaceMergeTextNode(zeroWidthNode, text);
		zeroWidthNode = null;
	}

	/**
	 * Inserts a zero width text node before or after a block.
	 *
	 * There is a problem where some browsers can't select the boundary
	 * between some contenteditable content and non-contenteditable
	 * content. For example, if in the example at the top of the file
	 * the selection were one step to the right "...</span>s[]ome..."
	 * and the left cursor key were pressed, then the selection would
	 * just disappear or be stuck between the span and the text node.
	 *
	 * To work around this problem a zero width text node is inserted
	 * before or after a block.
	 *
	 * The inserted zero width text node will be removed automatically
	 * when it isn't necessary any more (on selection change or on
	 * editable.getContents()).
	 *
	 * TODO: In retrospect, a better alternative may be to simply wrap
	 *       every inlin-block with an editable span.
	 * @param block
	 *        The DOM element for a block before or after which the zero
	 *        width text node will be inserted.
	 * @param isGoingLeft
	 *        True if the zero width text node is to be inserted after
	 *        the block element, or false if the zero width text node is
	 *        to be inserted before the block element.
	 * @return
	 *        The text node that was inserted.
	 */
	function insertZeroWidthTextNodeFix(block, isGoingLeft) {
		removeZeroWidthTextNodeFix();
		zeroWidthNode = document.createTextNode("\u200b");
		if (isGoingLeft) {
			$(block).after(zeroWidthNode);
		} else {
			$(block).before(zeroWidthNode);
		}
		Aloha.bind('aloha-selection-changed', function (event) {
			removeZeroWidthTextNodeFix();
			Aloha.unbind(event);
		});
		return zeroWidthNode;
	}

	return {
		removeZeroWidthTextNodeFix: removeZeroWidthTextNodeFix,
		insertZeroWidthTextNodeFix: insertZeroWidthTextNodeFix
	};
});

/* markup.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/markup',[
	'aloha/core',
	'util/class',
	'util/html',
	'jquery',
	'aloha/ecma5shims',
	'aloha/console',
	'aloha/block-jump'
], function (
	Aloha,
	Class,
	Html,
	jQuery,
	shims,
	console,
	BlockJump
) {
	

	var GENTICS = window.GENTICS;

	var isOldIE = !!(jQuery.browser.msie && 9 > parseInt(jQuery.browser.version, 10));

	function isBR(node) {
		return 'BR' === node.nodeName;
	}

	function isBlock(node) {
		return 'false' === jQuery(node).attr('contenteditable');
	}

	function isTextNode(node) {
		return node && 3 === node.nodeType; // Node.TEXT_NODE
	}

	function nodeLength(node) {
		return !node ? 0 : (isTextNode(node) ? node.length : node.childNodes.length);
	}

	/**
	 * Determines whether the given text node is visible to the the user,
	 * based on our understanding that browsers will not display
	 * superfluous white spaces.
	 *
	 * @param {HTMLEmenent} node The text node to be checked.
	 */
	function isVisibleTextNode(node) {
		return 0 < node.data.replace(/\s+/g, '').length;
	}

	function nextVisibleNode(node) {
		if (!node) {
			return null;
		}

		if (node.nextSibling) {
			// Skip over nodes that the user cannot see ...
			if (isTextNode(node.nextSibling) && !isVisibleTextNode(node.nextSibling)) {
				return nextVisibleNode(node.nextSibling);
			}

			// Skip over propping <br>s ...
			if (isBR(node.nextSibling) && node.nextSibling === node.parentNode.lastChild) {
				return nextVisibleNode(node.nextSibling);
			}

			// Skip over empty editable elements ...
			if ('' === node.nextSibling.innerHTML && !isBlock(node.nextSibling)) {
				return nextVisibleNode(node.nextSibling);
			}

			return node.nextSibling;
		}

		if (node.parentNode) {
			return nextVisibleNode(node.parentNode);
		}

		return null;
	}

	function prevVisibleNode(node) {
		if (!node) {
			return null;
		}

		if (node.previousSibling) {
			// Skip over nodes that the user cannot see...
			if (isTextNode(node.previousSibling) && !isVisibleTextNode(node.previousSibling)) {
				return prevVisibleNode(node.previousSibling);
			}

			// Skip over empty editable elements ...
			if ('' === node.previousSibling.innerHTML && !isBlock(node.previousSibling)) {
				return prevVisibleNode(node.previouSibling);
			}

			return node.previousSibling;
		}

		if (node.parentNode) {
			return prevVisibleNode(node.parentNode);
		}

		return null;
	}

	function isFrontPosition(node, offset) {
		return (0 === offset) || (offset <= node.data.length - node.data.replace(/^\s+/, '').length);
	}

	function isBlockInsideEditable($block) {
		return $block.parent().hasClass('aloha-editable');
	}

	function isEndPosition(node, offset) {
		var length = nodeLength(node);

		if (length === offset) {
			return true;
		}

		var isText = isTextNode(node);

		// If within a text node, then ignore superfluous white-spaces,
		// since they are invisible to the user.
		if (isText && node.data.replace(/\s+$/, '').length === offset) {
			return true;
		}

		if (1 === length && !isText) {
			return isBR(node.childNodes[0]);
		}

		return false;
	}

	function blink(node) {
		jQuery(node).stop(true).css({
			opacity: 0
		}).fadeIn(0).delay(100).fadeIn(function () {
			jQuery(node).css({
				opacity: 1
			});
		});

		return node;
	}

	function nodeContains(node1, node2) {
		return isOldIE ? (shims.compareDocumentPosition(node1, node2) & 16) : 0 < jQuery(node1).find(node2).length;
	}

	function isInsidePlaceholder(range) {
		var start = range.startContainer;
		var end = range.endContainer;
		var $placeholder = window.$_alohaPlaceholder;

		return $placeholder.is(start) || $placeholder.is(end) || nodeContains($placeholder[0], start) || nodeContains($placeholder[0], end);
	}

	function cleanupPlaceholders(range) {
		if (window.$_alohaPlaceholder && !isInsidePlaceholder(range)) {
			if (0 === window.$_alohaPlaceholder.html().replace(/^(&nbsp;)*$/, '').length) {
				window.$_alohaPlaceholder.remove();
			}

			window.$_alohaPlaceholder = null;
		}
	}

	/**
	 * @TODO(petro): We need to be more intelligent about whether we insert a
	 *               block-level placeholder or a phrasing level element.
	 * @TODO(petro): test with <pre>
	 * @TODO: move to block-jump.js
	 */
	function jumpBlock(block, isGoingLeft, currentRange) {
		var range = new GENTICS.Utils.RangeObject();
		var sibling = isGoingLeft ? prevVisibleNode(block) : nextVisibleNode(block);

		if (!sibling || isBlock(sibling)) {
			var $landing = jQuery('<div>&nbsp;</div>');

			if (isGoingLeft) {
				jQuery(block).before($landing);
			} else {
				jQuery(block).after($landing);
			}

			range.startContainer = range.endContainer = $landing[0];
			range.startOffset = range.endOffset = 0;

			// Clear out any old placeholder first ...
			cleanupPlaceholders(range);

			window.$_alohaPlaceholder = $landing;
		} else {

			// Don't jump the block yet if the cursor is moving to the
			// beginning or end of a text node, or if it is about to leave
			// an element node. Both these cases require a hack in some
			// browsers.
			var moveToBoundaryPositionInIE = ( // To the beginning or end of a text node?
				(currentRange.startContainer.nodeType === 3
				 && currentRange.startContainer === currentRange.endContainer
				 && currentRange.startContainer.nodeValue !== ""
				 && (isGoingLeft ? currentRange.startOffset === 1 : currentRange.endOffset + 1 === currentRange.endContainer.length))
				// Leaving an element node?
					|| (currentRange.startContainer.nodeType === 1
						&& (!currentRange.startOffset
							|| (currentRange.startContainer.childNodes[currentRange.startOffset] && currentRange.startContainer.childNodes[currentRange.startOffset].nodeType === 1)))
			);

			if (moveToBoundaryPositionInIE) {
				// The cursor is moving to the beginning or end of a text
				// node, or is leaving an element node, which requires a
				// hack in some browsers.
				var zeroWidthNode = BlockJump.insertZeroWidthTextNodeFix(block, isGoingLeft);
				range.startContainer = range.endContainer = zeroWidthNode;
				range.startOffset = range.endOffset = isGoingLeft ? 1 : 0;
			} else {
				// The selection is already at the boundary position - jump
				// the block.
				range.startContainer = range.endContainer = sibling;
				range.startOffset = range.endOffset = isGoingLeft ? nodeLength(sibling) : 0;
				if (!isGoingLeft) {
					// Just as above, jumping to the first position right of
					// a block requires a hack in some browsers. Jumping
					// left seems to be fine.
					BlockJump.insertZeroWidthTextNodeFix(block, true);
				}
			}
			cleanupPlaceholders(range);
		}

		range.select();

		Aloha.trigger('aloha-block-selected', block);
		Aloha.Selection.preventSelectionChanged();
	}

	/**
	 * recursively search through parent nodes to find if
	 * node is child of a specific node.
	 *
	 * @param {DOMNode} starting node
	 * @param {Array[String]} Array of UPPERCASE (!) node names to search for, eg. ["TD"] or ["TD", "TH"].
	 * @return true if node is child of a node of nodeName, false otherwise
	 */
	function isChildOf(node, nodeNames) {
		var i;
		if (node.parentNode) {
			for (i = 0; i < nodeNames.length; i++) {
				if (nodeNames[i] === node.parentNode.nodeName) {
					return true;
				}
			}
			return isChildOf(node.parentNode, nodeNames);
		} else {
			return false;
		}
	}

	/**
	 * Will recursively check if the current node is the first node in
	 * it's hierarchy up it's ancestor tree until the stopNode is reached.
	 * Useful to find eg. if you're in the first td within a table.
	 * Will stop if stopNodeName is encountered or the root node is reached.
	 *
	 * @param {DOMnode} node to start from
	 * @param {String} UPPERCASE node name to stop search at
	 * @return true if node is the first node, false otherwise
	 */
	function isFirstNode(node, stopNodeName) {
		if (!node.parentNode) {
			return true;
		}

		// firstChild will also find textNodes while children[0] will only return non-text nodes
		var isTextNode = ((node.nodeType === 3 && node.parentNode.firstChild === node) || (node.parentNode.children[0] === node));

		// unfortunately we need to take care of the aloha-table-selectrow and aloha-table-selectcolumn
		var isTableSelectRow = node.nodeName === 'TR' && node.parentNode.children[0].className.indexOf('aloha-table-selectcolumn') !== -1;
		var isTableSelectColumn = node.nodeName === 'TD' && node.parentNode.children[0].className.indexOf('aloha-table-selectrow') !== -1;
		var isFirstNodeOfTable = ((isTableSelectColumn || isTableSelectRow) && node.parentNode.children[1] === node);

		if (isTextNode || isFirstNodeOfTable) {
			if (node.parentNode.nodeName === stopNodeName) {
				return true;
			} else {
				return isFirstNode(node.parentNode, stopNodeName);
			}
		} else {
			return false;
		}
	}

	/**
	 * Will recurseively check if the current node is the last node in
	 * it's hierarchy up it's ancestor tree until the stopNode is reached.
	 * Useful to find eg. if you're in the last td within a table.
	 * Will stop if stopNodeName is encountered or the root node is reached.
	 * Will ignore whitespace text nodes and caption nodes
	 *
	 * @param {DOMnode} node to start from
	 * @param {String} UPPERCASE node name to stop search at
	 * @return true if node is the last node, false otherwise
	 */
	// implemented as an IIFE because the local helper getLast() should only be defined once
	var isLastNode = (function () {
		// get the last node that is not empty text or a table caption
		function getLast(node) {
			var last, i;

			for (i = node.childNodes.length - 1; i > -1; i--) {
				last = node.childNodes[i];
				if (last.nodeName !== 'CAPTION' && !(last.nodeType === 3 && /^[\t\n\r ]+$/.test(last.data))) {
					return last;
				}
			}

			return node.lastChild;
		}

		return function (node, stopNodeName) {
			if (!node.parentNode) {
				return true;
			}

			if (getLast(node.parentNode) === node) {
				if (node.parentNode.nodeName === stopNodeName) {
					return true;
				} else {
					return isLastNode(node.parentNode, stopNodeName);
				}
			} else {
				return false;
			}
		};
	}());

	/**
	 * Markup object
	 */
	Aloha.Markup = Class.extend({

		/**
		 * Key handlers for special key codes
		 */
		keyHandlers: {},

		/**
		 * Add a key handler for the given key code
		 * @param keyCode key code
		 * @param handler handler function
		 */
		addKeyHandler: function (keyCode, handler) {
			if (!this.keyHandlers[keyCode]) {
				this.keyHandlers[keyCode] = [];
			}

			this.keyHandlers[keyCode].push(handler);
		},

		/**
		 * Removes a key handler for the given key code
		 * @param keyCode key code
		 */
		removeKeyHandler: function (keyCode) {
			if (this.keyHandlers[keyCode]) {
				this.keyHandlers[keyCode] = null;
			}
		},

		insertBreak: function () {
			var range = Aloha.Selection.rangeObject,
				nonWSIndex,
				nextTextNode,
				newBreak;

			if (!range.isCollapsed()) {
				this.removeSelectedMarkup();
			}

			newBreak = jQuery('<br/>');
			GENTICS.Utils.Dom.insertIntoDOM(newBreak, range, Aloha.activeEditable.obj);

			nextTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(
				newBreak.parent().get(0),
				GENTICS.Utils.Dom.getIndexInParent(newBreak.get(0)) + 1,
				false
			);

			if (nextTextNode) {
				// trim leading whitespace
				nonWSIndex = nextTextNode.data.search(/\S/);
				if (nonWSIndex > 0) {
					nextTextNode.data = nextTextNode.data.substring(nonWSIndex);
				}
			}

			range.startContainer = range.endContainer = newBreak.get(0).parentNode;
			range.startOffset = range.endOffset = GENTICS.Utils.Dom.getIndexInParent(newBreak.get(0)) + 1;
			range.correctRange();
			range.clearCaches();
			range.select();
		},

		/**
		 * first method to handle key strokes
		 * @param event DOM event
		 * @param rangeObject as provided by Aloha.Selection.getRangeObject();
		 * @return "Aloha.Selection"
		 */
		preProcessKeyStrokes: function (event) {
			if (event.type !== 'keydown') {
				return false;
			}

			var rangeObject,
			    handlers,
			    i;

			if (this.keyHandlers[event.keyCode]) {
				handlers = this.keyHandlers[event.keyCode];
				for (i = 0; i < handlers.length; ++i) {
					if (!handlers[i](event)) {
						return false;
					}
				}
			}

			// LEFT (37), RIGHT (39) keys for block detection
			if (event.keyCode === 37 || event.keyCode === 39) {
				if (Aloha.getSelection().getRangeCount()) {
					rangeObject = Aloha.getSelection().getRangeAt(0);

					if (this.processCursor(rangeObject, event.keyCode)) {
						cleanupPlaceholders(Aloha.Selection.rangeObject);
						return true;
					}
				}

				return false;
			}

			// UP (38), DOWN (40) keys for table navigation
			if (event.keyCode === 38 || event.keyCode === 40) {
				if (Aloha.getSelection().getRangeCount()) {
					rangeObject = Aloha.getSelection().getRangeAt(0);
					if (this.processCursorUpDown(rangeObject, event.keyCode)) {
						return false;
					}
				}
				return true;
			}

			// BACKSPACE
			if (event.keyCode === 8) {
				event.preventDefault(); // prevent history.back() even on exception
				Aloha.execCommand('delete', false);
				return false;
			}

			// DELETE
			if (event.keyCode === 46) {
				Aloha.execCommand('forwarddelete', false);
				return false;
			}

			// ENTER
			if (event.keyCode === 13) {
				if (!event.shiftKey && Html.allowNestedParagraph(Aloha.activeEditable)) {
					Aloha.execCommand('insertparagraph', false);
					return false;
				// if the shift key is pressed, or if the active editable is not allowed
				// to contain paragraphs, a linebreak is inserted instead
				} else {
					Aloha.execCommand('insertlinebreak', false);
					return false;
				}
			}
			return true;
		},

		/**
		 * processing up and down cursor keys inside tables
		 * will only try to figure out if cursor is at first
		 * or last position in table and exit to the next
		 * editable node from there
		 *
		 * won't do anything if range is not collapsed
		 * 
		 * @param {RangyRange} range A range object for the current selection.
		 * @param {number} keyCode Code of the currently pressed key.
		 * @return {boolean} true if something was done, false if browser should 
		 * continue handling the event
		 */
		processCursorUpDown: function (range, keyCode) {
			if (!range.collapsed) {
				return false;
			}

			var node = range.startContainer,
				tableWrapper,
				cursorNode;

			// UP
			if (keyCode === 38 &&
					isFrontPosition(node, range.startOffset) &&
					isChildOf(node, ['TD', 'TH']) &&
					isFirstNode(node, 'TABLE')) {

				// we want to position the cursor now in the first 
				// element before the table, so we need to find the
				// table wrapper first ...
				tableWrapper = jQuery(node).parents('div.aloha-table-wrapper').get(0);
				if (!tableWrapper) {
					return false;
				}

				// ... and then find it's previousSibling
				// which we will descend down to its deepest
				// nested child node, where we will put the
				// cursor
				// prefer previousElemntSibling because Firefox will land you in a
				// whitespace text node between a preceding <p> and the table otherwise
				if (tableWrapper.previousElementSibling) {
					cursorNode = tableWrapper.previousElementSibling;
				} else {
					cursorNode = tableWrapper.previousSibling;
				}
				while (cursorNode.nodeType !== 3) {
					cursorNode = cursorNode.lastChild;
					if (cursorNode === null) {
						// stop if there is no element to be entered before the table
						return false;
					}
				}

				Aloha.Selection.rangeObject.startContainer = cursorNode;
				Aloha.Selection.rangeObject.endContainer = cursorNode;
				Aloha.Selection.rangeObject.startOffset = cursorNode.length;
				Aloha.Selection.rangeObject.endOffset = cursorNode.length;
				Aloha.Selection.rangeObject.select();

				// Mozilla needs this fix or else the selection will not work
				if (Aloha.activeEditable && jQuery.browser.mozilla) {
					Aloha.activeEditable.obj.focus();
				}

				return true;

			// DOWN
			} else if (keyCode === 40 &&
					isEndPosition(node, range.startOffset) &&
					isChildOf(node, ['TD', 'TH']) &&
					isLastNode(node, 'TABLE')) {

				// we want to put the cursor in the first element right 
				// after the table so we need to find the table wrapper first
				tableWrapper = jQuery(node).parents('div.aloha-table-wrapper').get(0);
				if (!tableWrapper) {
					return false;
				}

				// and now find its following sibling where we will put
				// the cursor in the first position
				// the next elementSibling is preffered over the nextSibling
				// because Mozilla will sometimes have an empty text node
				// right next to the table - but we most likely want to put 
				// the cursor into the next paragraph
				if (tableWrapper.nextElementSibling) {
					cursorNode = tableWrapper.nextElementSibling;
				} else {
					cursorNode = tableWrapper.nextSibling;
				}

				while (cursorNode.nodeType !== 3) {
					cursorNode = cursorNode.firstChild;
					if (cursorNode === null) {
						return false;
					}
				}

				Aloha.Selection.rangeObject.startContainer = cursorNode;
				Aloha.Selection.rangeObject.endContainer = cursorNode;
				Aloha.Selection.rangeObject.startOffset = 0;
				Aloha.Selection.rangeObject.endOffset = 0;
				Aloha.Selection.rangeObject.select();

				// Mozilla needs this fix or else the selection will not work
				if (Aloha.activeEditable && jQuery.browser.mozilla) {
					Aloha.activeEditable.obj.focus();
				}

				return true;

			} else {
				return false;
			}
		},

		/**
		 * Processing of cursor keys.
		 * Detect blocks (elements with contenteditable=false) and will select them
		 * (normally the cursor would simply jump right past them).
		 *
		 * For each block that is selected, an 'aloha-block-selected' event will be
		 * triggered.
		 *
		 * TODO: the above is what should happen. Currently we just skip past blocks.
		 *
		 * @param {RangyRange} range A range object for the current selection.
		 * @param {number} keyCode Code of the currently pressed key.
		 * @return {boolean} False if a block was found, to prevent further events,
		 *                   true otherwise.
		 * @TODO move to block-jump.js
		 */
		processCursor: function (range, keyCode) {
			if (!range.collapsed) {
				return true;
			}

			BlockJump.removeZeroWidthTextNodeFix();

			var node = range.startContainer,
				selection = Aloha.getSelection();

			if (!node) {
				return true;
			}

			var sibling, offset;

			// special handling for moving Cursor around zero-width whitespace in IE7
			if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) <= 7 && isTextNode(node)) {
				if (keyCode == 37) {
					// moving left -> skip zwsp to the left
					offset = range.startOffset;
					while (offset > 0 && node.data.charAt(offset - 1) === '\u200b') {
						offset--;
					}
					if (offset != range.startOffset) {
						range.setStart(range.startContainer, offset);
						range.setEnd(range.startContainer, offset);
						selection = Aloha.getSelection();
						selection.removeAllRanges();
						selection.addRange(range);
					}
				} else if (keyCode == 39) {
					// moving right -> skip zwsp to the right
					offset = range.startOffset;
					while (offset < node.data.length && node.data.charAt(offset) === '\u200b') {
						offset++;
					}
					if (offset != range.startOffset) {
						range.setStart(range.startContainer, offset);
						range.setEnd(range.startContainer, offset);
						selection.removeAllRanges();
						selection.addRange(range);
					}
				}
			}

			// Versions of Internet Explorer that are older that 9, will
			// erroneously allow you to enter and edit inside elements which have
			// their contenteditable attribute set to false...
			if (isOldIE && !jQuery(node).contentEditable()) {
				var $parentBlock = jQuery(node).parents('[contenteditable=false]');
				var isInsideBlock = $parentBlock.length > 0;

				if (isInsideBlock) {
					if (isBlockInsideEditable($parentBlock)) {
						sibling = $parentBlock[0];
					} else {
						return true;
					}
				}
			}

			var isLeft;
			if (!sibling) {
				// True if keyCode denotes LEFT or UP arrow key, otherwise they
				// keyCode is for RIGHT or DOWN in which this value will be false.
				isLeft = (37 === keyCode || 38 === keyCode);
				offset = range.startOffset;

				if (isTextNode(node)) {
					if (isLeft) {
						var isApproachingFrontPosition = (1 === offset);
						if (!isApproachingFrontPosition && !isFrontPosition(node, offset)) {
							return true;
						}
					} else if (!isEndPosition(node, offset)) {
						return true;
					}

				} else {
					node = node.childNodes[offset === nodeLength(node) ? offset - 1 : offset];
				}

				sibling = isLeft ? prevVisibleNode(node) : nextVisibleNode(node);
			}

			if (isBlock(sibling)) {
				jumpBlock(sibling, isLeft, range);
				return false;
			}

			return true;
		},

		/**
		 * method handling shiftEnter
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @return void
		 */
		processShiftEnter: function (rangeObject) {
			this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject);
		},

		/**
		 * method handling Enter
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @return void
		 */
		processEnter: function (rangeObject) {
			if (rangeObject.splitObject) {
				// now comes a very evil hack for ie, when the enter is pressed in a text node in an li element, we just append an empty text node
				// if ( jQuery.browser.msie
				//      && GENTICS.Utils.Dom
				//           .isListElement( rangeObject.splitObject ) ) {
				//  jQuery( rangeObject.splitObject ).append(
				//          jQuery( document.createTextNode( '' ) ) );
				//  }
				this.splitRangeObject(rangeObject);
			} else { // if there is no split object, the Editable is the paragraph type itself (e.g. a p or h2)
				this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject);
			}
		},

		/**
		 * Insert the given html markup at the current selection
		 * @param html html markup to be inserted
		 */
		insertHTMLCode: function (html) {
			var rangeObject = Aloha.Selection.rangeObject;
			this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject, jQuery(html));
		},

		/**
		 * insert an HTML Break <br /> into current selection
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @return void
		 */
		insertHTMLBreak: function (selectionTree, rangeObject, inBetweenMarkup) {
			var i,
			    treeLength,
			    el,
			    jqEl,
			    jqElBefore,
			    jqElAfter,
			    tmpObject,
			    offset,
			    checkObj;

			inBetweenMarkup = inBetweenMarkup || jQuery('<br/>');

			for (i = 0, treeLength = selectionTree.length; i < treeLength; ++i) {
				el = selectionTree[i];
				jqEl = el.domobj ? jQuery(el.domobj) : undefined;

				if (el.selection !== 'none') { // before cursor, leave this part inside the splitObject
					if (el.selection == 'collapsed') {
						// collapsed selection found (between nodes)
						if (i > 0) {
							// not at the start, so get the element to the left
							jqElBefore = jQuery(selectionTree[i - 1].domobj);

							// and insert the break after it
							jqElBefore.after(inBetweenMarkup);

						} else {
							// at the start, so get the element to the right
							jqElAfter = jQuery(selectionTree[1].domobj);

							// and insert the break before it
							jqElAfter.before(inBetweenMarkup);
						}

						// now set the range
						rangeObject.startContainer = rangeObject.endContainer = inBetweenMarkup[0].parentNode;
						rangeObject.startOffset = rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(inBetweenMarkup[0]) + 1;
						rangeObject.correctRange();

					} else if (el.domobj && el.domobj.nodeType === 3) { // textNode
						// when the textnode is immediately followed by a blocklevel element (like p, h1, ...) we need to add an additional br in between
						if (el.domobj.nextSibling && el.domobj.nextSibling.nodeType == 1 && Aloha.Selection.replacingElements[el.domobj.nextSibling.nodeName.toLowerCase()]) {
							// TODO check whether this depends on the browser
							jqEl.after('<br/>');
						}

						if (this.needEndingBreak()) {
							// when the textnode is the last inside a blocklevel element
							// (like p, h1, ...) we need to add an additional br as very
							// last object in the blocklevel element
							checkObj = el.domobj;

							while (checkObj) {
								if (checkObj.nextSibling) {
									checkObj = false;
								} else {
									// go to the parent
									checkObj = checkObj.parentNode;

									// found a blocklevel or list element, we are done
									if (GENTICS.Utils.Dom.isBlockLevelElement(checkObj) || GENTICS.Utils.Dom.isListElement(checkObj)) {
										break;
									}

									// reached the limit object, we are done
									if (checkObj === rangeObject.limitObject) {
										checkObj = false;
									}
								}
							}

							// when we found a blocklevel element, insert a break at the
							// end. Mark the break so that it is cleaned when the
							// content is fetched.
							if (checkObj) {
								jQuery(checkObj).append('<br class="aloha-cleanme" />');
							}
						}

						// insert the break
						jqEl.between(inBetweenMarkup, el.startOffset);

						// correct the range
						// count the number of previous siblings
						offset = 0;
						tmpObject = inBetweenMarkup[0];
						while (tmpObject) {
							tmpObject = tmpObject.previousSibling;
							++offset;
						}

						rangeObject.startContainer = inBetweenMarkup[0].parentNode;
						rangeObject.endContainer = inBetweenMarkup[0].parentNode;
						rangeObject.startOffset = offset;
						rangeObject.endOffset = offset;
						rangeObject.correctRange();

					} else if (el.domobj && el.domobj.nodeType === 1) { // other node, normally a break
						if (jqEl.parent().find('br.aloha-ephemera').length === 0) {
							// but before putting it, remove all:
							jQuery(rangeObject.limitObject).find('br.aloha-ephemera').remove();

							//  now put it:
							jQuery(rangeObject.commonAncestorContainer).append(this.getFillUpElement(rangeObject.splitObject));
						}

						jqEl.after(inBetweenMarkup);

						// now set the selection. Since we just added one break do the currect el
						// the new position must be el's position + 1. el's position is the index
						// of the el in the selection tree, which is i. then we must add
						// another +1 because we want to be AFTER the object, not before. therefor +2
						rangeObject.startContainer = rangeObject.commonAncestorContainer;
						rangeObject.endContainer = rangeObject.startContainer;
						rangeObject.startOffset = i + 2;
						rangeObject.endOffset = i + 2;
						rangeObject.update();
					}
				}
			}
			rangeObject.select();
		},

		/**
		 * Check whether blocklevel elements need breaks at the end to visibly render a newline
		 * @return true if an ending break is necessary, false if not
		 */
		needEndingBreak: function () {
			// currently, all browser except IE need ending breaks
			return !jQuery.browser.msie;
		},

		/**
		 * Get the currently selected text or false if nothing is selected (or the selection is collapsed)
		 * @return selected text
		 */
		getSelectedText: function () {
			var rangeObject = Aloha.Selection.rangeObject;

			if (rangeObject.isCollapsed()) {
				return false;
			}

			return this.getFromSelectionTree(rangeObject.getSelectionTree(), true);
		},

		/**
		 * Recursive function to get the selected text from the selection tree starting at the given level
		 * @param selectionTree array of selectiontree elements
		 * @param astext true when the contents shall be fetched as text, false for getting as html markup
		 * @return selected text from that level (incluiding all sublevels)
		 */
		getFromSelectionTree: function (selectionTree, astext) {
			var text = '', i, treeLength, el, clone;
			for (i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
				el = selectionTree[i];
				if (el.selection == 'partial') {
					if (el.domobj.nodeType === 3) {
						// partial text node selected, get the selected part
						text += el.domobj.data.substring(el.startOffset, el.endOffset);
					} else if (el.domobj.nodeType === 1 && el.children) {
						// partial element node selected, do the recursion into the children
						if (astext) {
							text += this.getFromSelectionTree(el.children, astext);
						} else {
							// when the html shall be fetched, we create a clone of
							// the element and remove all the children
							clone = jQuery(el.domobj.outerHTML).empty();
							// then we do the recursion and add the selection into the clone
							clone.html(this.getFromSelectionTree(el.children, astext));
							// finally we get the html of the clone
							text += clone.outerHTML();
						}
					}
				} else if (el.selection == 'full') {
					if (el.domobj.nodeType === 3) {
						// full text node selected, get the text
						text += jQuery(el.domobj).text();
					} else if (el.domobj.nodeType === 1 && el.children) {
						// full element node selected, get the html of the node and all children
						text += astext ? jQuery(el.domobj).text() : jQuery(el.domobj).outerHTML();
					}
				}
			}

			return text;
		},

		/**
		 * Get the currently selected markup or false if nothing is selected (or the selection is collapsed)
		 * @return {?String}
		 */
		getSelectedMarkup: function () {
			var rangeObject = Aloha.Selection.rangeObject;
			return rangeObject.isCollapsed() ? null : this.getFromSelectionTree(rangeObject.getSelectionTree(), false);
		},

		/**
		 * Remove the currently selected markup
		 */
		removeSelectedMarkup: function () {
			var rangeObject = Aloha.Selection.rangeObject,
				newRange;

			if (rangeObject.isCollapsed()) {
				return;
			}

			newRange = new Aloha.Selection.SelectionRange();
			// remove the selection
			this.removeFromSelectionTree(rangeObject.getSelectionTree(), newRange);

			// do a cleanup now (starting with the commonancestorcontainer)
			newRange.update();
			GENTICS.Utils.Dom.doCleanup({
				'merge': true,
				'removeempty': true
			}, Aloha.Selection.rangeObject);
			Aloha.Selection.rangeObject = newRange;

			// need to set the collapsed selection now
			newRange.correctRange();
			newRange.update();
			newRange.select();
			Aloha.Selection.updateSelection();
		},

		/**
		 * Recursively remove the selected items, starting with the given level in the selectiontree
		 * @param selectionTree current level of the selectiontree
		 * @param newRange new collapsed range to be set after the removal
		 */
		removeFromSelectionTree: function (selectionTree, newRange) {
			// remember the first found partially selected element node (in case we need
			// to merge it with the last found partially selected element node)
			var firstPartialElement, newdata, i, el, adjacentTextNode, treeLength;

			// iterate through the selection tree
			for (i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
				el = selectionTree[i];

				// check the type of selection
				if (el.selection == 'partial') {
					if (el.domobj.nodeType === 3) {
						// partial text node selected, so remove the selected portion
						newdata = '';
						if (el.startOffset > 0) {
							newdata += el.domobj.data.substring(0, el.startOffset);
						}
						if (el.endOffset < el.domobj.data.length) {
							newdata += el.domobj.data.substring(el.endOffset, el.domobj.data.length);
						}
						el.domobj.data = newdata;

						// eventually set the new range (if not done before)
						if (!newRange.startContainer) {
							newRange.startContainer = newRange.endContainer = el.domobj;
							newRange.startOffset = newRange.endOffset = el.startOffset;
						}
					} else if (el.domobj.nodeType === 1 && el.children) {
						// partial element node selected, so do the recursion into the children
						this.removeFromSelectionTree(el.children, newRange);

						if (firstPartialElement) {
							// when the first parially selected element is the same type
							// of element, we need to merge them
							if (firstPartialElement.nodeName == el.domobj.nodeName) {
								// merge the nodes
								jQuery(firstPartialElement).append(jQuery(el.domobj).contents());

								// and remove the latter one
								jQuery(el.domobj).remove();
							}

						} else {
							// remember this element as first partially selected element
							firstPartialElement = el.domobj;
						}
					}

				} else if (el.selection == 'full') {
					// eventually set the new range (if not done before)
					if (!newRange.startContainer) {
						adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(
							el.domobj.parentNode,
							GENTICS.Utils.Dom.getIndexInParent(el.domobj) + 1,
							false,
							{
								'blocklevel': false
							}
						);

						if (adjacentTextNode) {
							newRange.startContainer = newRange.endContainer = adjacentTextNode;
							newRange.startOffset = newRange.endOffset = 0;
						} else {
							newRange.startContainer = newRange.endContainer = el.domobj.parentNode;
							newRange.startOffset = newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(el.domobj) + 1;
						}
					}

					// full node selected, so just remove it (will also remove all children)
					jQuery(el.domobj).remove();
				}
			}
		},

		/**
		 * split passed rangeObject without or with optional markup
		 * @param Aloha.Selection.SelectionRange of the current selection
		 * @param markup object (jQuery) to insert in between the split elements
		 * @return void
		 */
		splitRangeObject: function (rangeObject, markup) {
			// UAAAA: first check where the markup can be inserted... *grrrrr*, then decide where to split
			// object which is split up
			var splitObject = jQuery(rangeObject.splitObject),
				selectionTree,
			    insertAfterObject,
			    followUpContainer;

			// update the commonAncestor with the splitObject (so that the selectionTree is correct)
			rangeObject.update(rangeObject.splitObject); // set the splitObject as new commonAncestorContainer and update the selectionTree

			// calculate the selection tree. NOTE: it is necessary to do this before
			// getting the followupcontainer, since getting the selection tree might
			// possibly merge text nodes, which would lead to differences in the followupcontainer
			selectionTree = rangeObject.getSelectionTree();

			// object to be inserted after the splitObject
			followUpContainer = this.getSplitFollowUpContainer(rangeObject);

			// now split up the splitObject into itself AND the followUpContainer
			this.splitRangeObjectHelper(selectionTree, rangeObject, followUpContainer); // split the current object into itself and the followUpContainer

			// check whether the followupcontainer is still marked for removal
			if (followUpContainer.hasClass('preparedForRemoval')) {
				// TODO shall we just remove the class or shall we not use the followupcontainer?
				followUpContainer.removeClass('preparedForRemoval');
			}

			// now let's find the place, where the followUp is inserted afterwards. normally that's the splitObject itself, but in
			// some cases it might be their parent (e.g. inside a list, a <p> followUp must be inserted outside the list)
			insertAfterObject = this.getInsertAfterObject(rangeObject, followUpContainer);

			// now insert the followUpContainer
			jQuery(followUpContainer).insertAfter(insertAfterObject); // attach the followUpContainer right after the insertAfterObject

			// in some cases, we want to remove the "empty" splitObject (e.g. LIs, if enter was hit twice)
			if (rangeObject.splitObject.nodeName.toLowerCase() === 'li' && !Aloha.Selection.standardTextLevelSemanticsComparator(rangeObject.splitObject, followUpContainer)) {
				jQuery(rangeObject.splitObject).remove();
			}

			rangeObject.startContainer = null;
			// first check whether the followUpContainer starts with a <br/>
			// if so, place the cursor right before the <br/>
			var followContents = followUpContainer.contents();
			if (followContents.length > 0 && followContents.get(0).nodeType == 1 && followContents.get(0).nodeName.toLowerCase() === 'br') {
				rangeObject.startContainer = followUpContainer.get(0);
			}

			if (!rangeObject.startContainer) {
				// find a possible text node in the followUpContainer and set the selection to it
				// if no textnode is available, set the selection to the followup container itself
				rangeObject.startContainer = followUpContainer.textNodes(true, true).first().get(0);
			}
			if (!rangeObject.startContainer) { // if no text node was found, select the parent object of <br class="aloha-ephemera" />
				rangeObject.startContainer = followUpContainer.textNodes(false).first().parent().get(0);
			}
			if (rangeObject.startContainer) {
				// the cursor is always at the beginning of the followUp
				rangeObject.endContainer = rangeObject.startContainer;
				rangeObject.startOffset = 0;
				rangeObject.endOffset = 0;
			} else {
				rangeObject.startContainer = rangeObject.endContainer = followUpContainer.parent().get(0);
				rangeObject.startOffset = rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(followUpContainer.get(0));
			}

			// finally update the range object again
			rangeObject.update();

			// now set the selection
			rangeObject.select();
		},

		/**
		 * method to get the object after which the followUpContainer can be inserted during splitup
		 * this is a helper method, not needed anywhere else
		 * @param rangeObject Aloha.Selection.SelectionRange of the current selection
		 * @param followUpContainer optional jQuery object; if provided the rangeObject will be split and the second part will be insert inside of this object
		 * @return object after which the followUpContainer can be inserted
		 */
		getInsertAfterObject: function (rangeObject, followUpContainer) {
			var passedSplitObject, i, el;

			for (i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				el = rangeObject.markupEffectiveAtStart[i];

				// check if we have already passed the splitObject (some other markup might come before)
				if (el === rangeObject.splitObject) {
					passedSplitObject = true;
				}

				// if not passed splitObject, skip this markup
				if (!passedSplitObject) {
					continue;
				}

				// once we are passed, check if the followUpContainer is allowed to be inserted into the currents el's parent
				if (Aloha.Selection.canTag1WrapTag2(jQuery(el).parent()[0].nodeName, followUpContainer[0].nodeName)) {
					return el;
				}
			}

			return false;
		},

		/**
		 * @fixme: Someone who knows what this function does, please refactor it.
		 *			1. splitObject arg is not used at all
		 *			2. Would be better to use ternary operation would be better than if else statement
		 *
		 * method to get the html code for a fillUpElement. this is needed for empty paragraphs etc., so that they take up their expected height
		 * @param splitObject split object (dom object)
		 * @return fillUpElement HTML Code
		 */
		getFillUpElement: function (splitObject) {
			if (jQuery.browser.msie) {
				return false;
			}
			return jQuery('<br class="aloha-cleanme"/>');
		},

		/**
		 * removes textNodes from passed array, which only contain contentWhiteSpace (e.g. a \n between two tags)
		 * @param domArray array of domObjects
		 * @return void
		 */
		removeElementContentWhitespaceObj: function (domArray) {
			var correction = 0,
				removeLater = [],
				i,
				el,
			    removeIndex;

			for (i = 0; i < domArray.length; ++i) {
				el = domArray[i];
				if (el.isElementContentWhitespace) {
					removeLater[removeLater.length] = i;
				}
			}

			for (i = 0; i < removeLater.length; ++i) {
				removeIndex = removeLater[i];
				domArray.splice(removeIndex - correction, 1);
				++correction;
			}
		},

		/**
		 * recursive method to parallelly walk through two dom subtrees, leave elements before startContainer in first subtree and move rest to other
		 * @param selectionTree tree to iterate over as contained in rangeObject. must be passed separately to allow recursion in the selection tree, but not in the rangeObject
		 * @param rangeObject Aloha.Selection.SelectionRange of the current selection
		 * @param followUpContainer optional jQuery object; if provided the rangeObject will be split and the second part will be insert inside of this object
		 * @param inBetweenMarkup jQuery object to be inserted between the two split parts. will be either a <br> (if no followUpContainer is passed) OR e.g. a table, which must be inserted between the splitobject AND the follow up
		 * @return void
		 */
		splitRangeObjectHelper: function (selectionTree, rangeObject, followUpContainer, inBetweenMarkup) {
			if (!followUpContainer) {
				Aloha.Log.warn(this, 'no followUpContainer, no inBetweenMarkup, nothing to do...');
			}

			var fillUpElement = this.getFillUpElement(rangeObject.splitObject),
				splitObject = jQuery(rangeObject.splitObject),
				startMoving = false,
				el,
				i,
				completeText,
				jqObj,
				mirrorLevel,
				parent,
				treeLength;

			if (selectionTree.length > 0) {
				mirrorLevel = followUpContainer.contents();

				// if length of mirrorLevel and selectionTree are not equal, the mirrorLevel must be corrected. this happens, when the mirrorLevel contains whitespace textNodes
				if (mirrorLevel.length !== selectionTree.length) {
					this.removeElementContentWhitespaceObj(mirrorLevel);
				}

				for (i = 0, treeLength = selectionTree.length; i < treeLength; ++i) {
					el = selectionTree[i];

					// remove all objects in the mirrorLevel, which are BEFORE the cursor
					// OR if the cursor is at the last position of the last Textnode (causing an empty followUpContainer to be appended)
					if ((el.selection === 'none' && startMoving === false) || (el.domobj && el.domobj.nodeType === 3 && el === selectionTree[(selectionTree.length - 1)] && el.startOffset === el.domobj.data.length)) {
						// iteration is before cursor, leave this part inside the splitObject, remove from followUpContainer
						// however if the object to remove is the last existing textNode within the followUpContainer, insert a BR instead
						// otherwise the followUpContainer is invalid and takes up no vertical space

						if (followUpContainer.textNodes().length > 1 || (el.domobj.nodeType === 1 && el.children.length === 0)) {
							// note: the second part of the if (el.domobj.nodeType === 1 && el.children.length === 0) covers a very special condition,
							// where an empty tag is located right before the cursor when pressing enter. In this case the empty tag would not be
							// removed correctly otherwise
							mirrorLevel.eq(i).remove();

						} else if (GENTICS.Utils.Dom.isSplitObject(followUpContainer[0])) {
							if (fillUpElement) {
								followUpContainer.html(fillUpElement); // for your zoological german knowhow: ephemera = Eintagsfliege
							} else {
								followUpContainer.empty();
							}

						} else {
							followUpContainer.empty();
							followUpContainer.addClass('preparedForRemoval');
						}

						continue;

					} else {
						// split objects, which are AT the cursor Position or directly above
						if (el.selection !== 'none') { // before cursor, leave this part inside the splitObject
							// TODO better check for selection == 'partial' here?
							if (el.domobj && el.domobj.nodeType === 3 && el.startOffset !== undefined) {
								completeText = el.domobj.data;
								if (el.startOffset > 0) { // first check, if there will be some text left in the splitObject
									el.domobj.data = completeText.substr(0, el.startOffset);
								} else if (selectionTree.length > 1) { // if not, check if the splitObject contains more than one node, because then it can be removed. this happens, when ENTER is pressed inside of a textnode, but not at the borders
									jQuery(el.domobj).remove();
								} else { // if the "empty" textnode is the last node left in the splitObject, replace it with a ephemera break
									// if the parent is a blocklevel element, we insert the fillup element
									parent = jQuery(el.domobj).parent();
									if (GENTICS.Utils.Dom.isSplitObject(parent[0])) {
										if (fillUpElement) {
											parent.html(fillUpElement);
										} else {
											parent.empty();
										}

									} else {
										// if the parent is no blocklevel element and would be empty now, we completely remove it
										parent.remove();
									}
								}
								if (completeText.length - el.startOffset > 0) {
									// first check if there is text left to put in the followUpContainer's textnode. this happens, when ENTER is pressed inside of a textnode, but not at the borders
									mirrorLevel[i].data = completeText.substr(el.startOffset, completeText.length);
								} else if (mirrorLevel.length > 1) {
									// if not, check if the followUpContainer contains more than one node, because if yes, the "empty" textnode can be removed
									mirrorLevel.eq((i)).remove();
								} else if (GENTICS.Utils.Dom.isBlockLevelElement(followUpContainer[0])) {
									// if the "empty" textnode is the last node left in the followUpContainer (which is a blocklevel element), replace it with a ephemera break
									if (fillUpElement) {
										followUpContainer.html(fillUpElement);
									} else {
										followUpContainer.empty();
									}

								} else {
									// if the "empty" textnode is the last node left in a non-blocklevel element, mark it for removal
									followUpContainer.empty();
									followUpContainer.addClass('preparedForRemoval');
								}
							}

							startMoving = true;

							if (el.children.length > 0) {
								this.splitRangeObjectHelper(el.children, rangeObject, mirrorLevel.eq(i), inBetweenMarkup);
							}

						} else {
							// remove all objects in the origin, which are AFTER the cursor
							if (el.selection === 'none' && startMoving === true) {
								// iteration is after cursor, remove from splitObject and leave this part inside the followUpContainer
								jqObj = jQuery(el.domobj).remove();
							}
						}
					}
				}
			} else {
				Aloha.Log.error(this, 'can not split splitObject due to an empty selection tree');
			}

			// and finally cleanup: remove all fillUps > 1
			splitObject.find('br.aloha-ephemera:gt(0)').remove(); // remove all elements greater than (gt) 0, that also means: leave one
			followUpContainer.find('br.aloha-ephemera:gt(0)').remove(); // remove all elements greater than (gt) 0, that also means: leave one

			// remove objects prepared for removal
			splitObject.find('.preparedForRemoval').remove();
			followUpContainer.find('.preparedForRemoval').remove();

			// if splitObject / followUp are empty, place a fillUp inside
			if (splitObject.contents().length === 0 && GENTICS.Utils.Dom.isSplitObject(splitObject[0]) && fillUpElement) {
				splitObject.html(fillUpElement);
			}

			if (followUpContainer.contents().length === 0 && GENTICS.Utils.Dom.isSplitObject(followUpContainer[0]) && fillUpElement) {
				followUpContainer.html(fillUpElement);
			}
		},

		/**
		 * returns a jQuery object fitting the passed splitObject as follow up object
		 * examples,
		 * - when passed a p it will return an empty p (clone of the passed p)
		 * - when passed an h1, it will return either an h1 (clone of the passed one) or a new p (if the collapsed selection was at the end)
		 * @param rangeObject Aloha.RangeObject
		 * @return void
		 */
		getSplitFollowUpContainer: function (rangeObject) {
			var tagName = rangeObject.splitObject.nodeName.toLowerCase(),
				returnObj,
				inside,
				lastObj;

			switch (tagName) {
			case 'h1':
			case 'h2':
			case 'h3':
			case 'h4':
			case 'h5':
			case 'h6':
				// get the last textnode in the splitobject, but don't consider aloha-cleanme elements
				lastObj = jQuery(rangeObject.splitObject).textNodes(':not(.aloha-cleanme)').last()[0];
				// special case: when enter is hit at the end of a heading, the followUp should be a <p>
				if (lastObj && rangeObject.startContainer === lastObj && rangeObject.startOffset === lastObj.length) {
					returnObj = jQuery('<p></p>');
					inside = jQuery(rangeObject.splitObject.outerHTML).contents();
					returnObj.append(inside);
					return returnObj;
				}
				break;

			case 'li':
				// TODO check whether the li is the last one
				// special case: if enter is hit twice inside a list, the next item should be a <p> (and inserted outside the list)
				if (rangeObject.startContainer.nodeName.toLowerCase() === 'br' && jQuery(rangeObject.startContainer).hasClass('aloha-ephemera')) {
					returnObj = jQuery('<p></p>');
					inside = jQuery(rangeObject.splitObject.outerHTML).contents();
					returnObj.append(inside);
					return returnObj;
				}
				// when the li is the last one and empty, we also just return a <p>
				if (!rangeObject.splitObject.nextSibling && jQuery.trim(jQuery(rangeObject.splitObject).text()).length === 0) {
					returnObj = jQuery('<p></p>');
					return returnObj;
				}
				break;
			}

			return jQuery(rangeObject.splitObject.outerHTML);
		},

		/**
		 * Transform the given domobj into an object with the given new nodeName.
		 * Preserves the content and all attributes. If a range object is given, also the range will be preserved
		 * @param domobj dom object to transform
		 * @param nodeName new node name
		 * @param range range object
		 * @api
		 * @return new object as jQuery object
		 */
		transformDomObject: function (domobj, nodeName, range) {
			// first create the new element
			var jqOldObj = jQuery(domobj),
				jqNewObj = jQuery('<' + nodeName + '>'),
				i,
				attributes = jqOldObj[0].cloneNode(false).attributes;

			// TODO what about events?
			// copy attributes
			if (attributes) {
				for (i = 0; i < attributes.length; ++i) {
					if (typeof attributes[i].specified === 'undefined' || attributes[i].specified) {
						jqNewObj.attr(attributes[i].nodeName, attributes[i].nodeValue);
					}
				}
			}

			// copy inline CSS
			if (jqOldObj[0].style && jqOldObj[0].style.cssText) {
				jqNewObj[0].style.cssText = jqOldObj[0].style.cssText;
			}

			// now move the contents of the old dom object into the new dom object
			jqOldObj.contents().appendTo(jqNewObj);

			// finally replace the old object with the new one
			jqOldObj.replaceWith(jqNewObj);

			// preserve the range
			if (range) {
				if (range.startContainer == domobj) {
					range.startContainer = jqNewObj.get(0);
				}

				if (range.endContainer == domobj) {
					range.endContainer = jqNewObj.get(0);
				}
			}

			return jqNewObj;
		},

		/**
		 * String representation
		 * @return {String}
		 */
		toString: function () {
			return 'Aloha.Markup';
		}

	});

	Aloha.Markup = new Aloha.Markup();
	return Aloha.Markup;
});

/* observable.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/observable',[
	'jquery'
], function (
	jQuery
) {
	

	var $ = jQuery;

	return {
		_eventHandlers: null,

		/**
		 * Attach a handler to an event
		 *
		 * @param {String} eventType A string containing the event name to bind to
		 * @param {Function} handler A function to execute each time the event is triggered
		 * @param {Object} scope Optional. Set the scope in which handler is executed
		 */
		bind: function (eventType, handler, scope) {
			this._eventHandlers = this._eventHandlers || {};
			if (!this._eventHandlers[eventType]) {
				this._eventHandlers[eventType] = [];
			}
			this._eventHandlers[eventType].push({
				handler: handler,
				scope: (scope || window)
			});
		},

		/**
		 * Remove a previously-attached event handler
		 *
		 * @param {String} eventType A string containing the event name to unbind
		 * @param {Function} handler The function that is to be no longer executed. Optional. If not given, unregisters all functions for the given event.
		 */
		unbind: function (eventType, handler) {
			this._eventHandlers = this._eventHandlers || {};
			if (!this._eventHandlers[eventType]) {
				return;
			}
			if (!handler) {
				// No handler function given, unbind all event handlers for the eventType
				this._eventHandlers[eventType] = [];
			} else {
				this._eventHandlers[eventType] = $.grep(this._eventHandlers[eventType], function (element) {
					if (element.handler === handler) {
						return false;
					}
					return true;
				});
			}
		},

		/**
		 * Execute all handlers attached to the given event type.
		 * All arguments except the eventType are directly passed to the callback function.
		 *
		 * @param (String} eventType A string containing the event name for which the event handlers should be invoked.
		 */
		trigger: function (eventType) {
			this._eventHandlers = this._eventHandlers || {};
			if (!this._eventHandlers[eventType]) {
				return;
			}

			// preparedArguments contains all arguments except the first one.
			var preparedArguments = [];
			$.each(arguments, function (i, argument) {
				if (i > 0) {
					preparedArguments.push(argument);
				}
			});

			$.each(this._eventHandlers[eventType], function (index, element) {
				element.handler.apply(element.scope, preparedArguments);
			});
		},

		/**
		 * Clears all event handlers. Call this method when cleaning up.
		 */
		unbindAll: function () {
			this._eventHandlers = null;
		}
	};
});

/* registry.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/*global define:true */
/**
 * Registry base class.
 * TODO: document that it also contains Observable.
 *
 */
define('aloha/registry',[
	'jquery',
	'aloha/observable',
	'util/class'
], function (
	jQuery,
	Observable,
	Class
) {
	

	return Class.extend(Observable, {

		/**
		 * Object containing the registered entries by key.
		 */
		_entries: null,

		/**
		 * Array containing the registered ids in order
		 * of registry
		 */
		_ids: null,

		_constructor: function () {
			this._entries = {};
			this._ids = [];
		},

		/**
		 * Register an entry with an id
		 * 
		 * @event register
		 * @param id id of the registered entry
		 * @param entry registered entry
		 */
		register: function (id, entry) {
			// TODO check whether an entry with the id is already registered
			this._entries[id] = entry;
			this._ids.push(id);
			this.trigger('register', entry, id);
		},

		/**
		 * Unregister the entry with given id
		 * 
		 * @event unregister
		 * @param id id of the registered entry
		 */
		unregister: function (id) {
			// TODO check whether an entry was registered
			var i, oldEntry = this._entries[id];
			delete this._entries[id];
			for (i in this._ids) {
				if (this._ids.hasOwnProperty(i) && this._ids[i] === id) {
					this._ids.splice(i, 1);
					break;
				}
			}
			this.trigger('unregister', oldEntry, id);
		},

		/**
		 * Get the entry registered with the given id
		 * 
		 * @param id id of the registered entry
		 * @return registered entry
		 */
		get: function (id) {
			return this._entries[id];
		},

		/**
		 * Check whether an entry was registered with given id
		 * 
		 * @param id id to check
		 * @return true if an entry was registered, false if not
		 */
		has: function (id) {
			return (this._entries[id] ? true : false);
		},

		/**
		 * Get an object mapping the ids (properties) to the registered entries
		 * Note, that iterating over the properties of the returned object
		 * will return the entries in an unspecified order
		 * 
		 * @return object containing the registered entries
		 */
		getEntries: function () {
			// clone the entries so the user does not accidentally modify our _entries object.
			return jQuery.extend({}, this._entries);
		},

		/**
		 * Get the ids of the registered objects as array.
		 * The array will contain the ids in order of registry
		 * 
		 * @return array if registered ids
		 */
		getIds: function () {
			return jQuery.extend([], this._ids);
		}
	});
});

/* contenthandlermanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/contenthandlermanager',[
	'aloha/core',
	'aloha/registry',
	'util/class'
], function (
	Aloha,
	Registry,
	Class
) {
	

	var ContentHandlerManager = Registry.extend({

		/**
		 * Create a contentHandler from the given definition.  Acts as a factory
		 * method for contentHandler.
		 *
		 * @param {object} definition
		 */
		createHandler: function (definition) {
			if (typeof definition.handleContent !== 'function') {
				throw 'ContentHandler has no function handleContent().';
			}
			var AbstractContentHandler = Class.extend({
				handleContent: function (content) {
					// Implement in subclass!
				}
			}, definition);
			return new AbstractContentHandler();
		},

		/**
		 * Manipulates the given contents of an editable by invoking content
		 * handlers over it.
		 *
		 * @param {string} content The content of an editable which will be
		 *                         handled.
		 * @param {object} options Used to filter limit which content handlers
		 *                         should be used.
		 * @param {Aloha.Editable} The editable whose content is being handled.
		 * @return {string} The handled content.
		 */
		handleContent: function (content, options, editable) {
			var manager = this;

			// Because if no options are specified, to indicate which content
			// handler to use, then all that are available are used.
			var handlers = options ? options.contenthandler : manager.getIds();

			if (!handlers) {
				return content;
			}

			var i;
			var handler;
			for (i = 0; i < handlers.length; i++) {
				handler = manager.get(handlers[i]);
				if (handler) {
					content = handler.handleContent(
						content,
						options,
						editable || Aloha.activeEditable
					);
				}

				// FIXME: Is it ever valid for content to be null?  This would
				//        break the handleContent(string):string contract.
				if (null === content) {
					break;
				}
			}

			return content;
		}
	});

	return new ContentHandlerManager();
});

/* trees.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Tree walking functions.
 *
 * prewalk(form, fn, inplace)
 *
 *     Descend into the given form, which is a tree of arrays andmaps
 *     (javascript Object), and build a new tree with the result of
 *     applying the given fn to each branch and leaf. Only arrays and
 *     maps are descended into, everything else is considered a leaf.
 *
 *     The given fn is applied as the tree is descended into - the
 *     function application (pre)cedes descending into the tree.
 * 
 *     By default, an entirely new structure is returned. If the
 *     optional inplace argument is true, the algorithm will not
 *     allocate any new structures, but modify the given form in-place.
 *     The benefit of this is more performance due to less allocation,
 *     and reduced memory overhead, but see the "Note" below.
 *
 * postwalk(form, fn, inplace)
 *
 *     the same as prewalk, except the given fn is applied as the tree
 *     is ascended.
 *
 * preprune(form, pred, inplace)
 *
 *     the same as prewalk, except pred is a predicate function and any
 *     branch or leaf that is encountered and for which pred returns
 *     true is removed from the tree.
 *
 * postprune(form, pred, inplace)
 *
 *     the same as preprune, except the predicate function is applied as
 *     the tree is ascended.
 *
 *     Postpruning is potentially slower than prepruning since it always
 *     descendes into the whole tree, even into pruned nodes, while
 *     prepruning skips any pruned nodes.
 *
 * leaves(form, leaf, inplace)
 *
 *     Like postwalk, except the leaf function is applied only to
 *     leaves, and not to the arrays or maps that make up the tree
 *     structure of form.
 *
 *     Useful when one is only interested in tranforming leaves.
 *
 * flatten(form)
 *
 *     Makes an array of all of the given form's leaves.
 *
 * clone(form)
 *
 *     Constructs a deep clone of the given form.
 *
 * prepruneDom(form, pred, inplace)
 *
 *     Like preprune() except:
 *
 *     - the given form may be either an element or other DOM node, and
 *       only elements are descended into, all other node types are
 *       considered leaves.
 *
 *     - the given form will be cloned before it is being traversed, unless
 *       inplace is true.
 *
 *       This is different from prewalk, where the subforms that are
 *       passed to fn are not clones. Making a deep clone first
 *       simplifies some things, basically because an array or map can
 *       be the child of multiple arrays and maps at the same time,
 *       while a node can only be the child of a single parent node at
 *       any one time.
 *   
 * postpruneDom(form, pred, inplace)
 *
 *     Like prepruneDom(), except the given function is applied as the tree
 *     is ascended.
 *
 * walk(form, recurse, inplace)
 *
 *     If form is an array or map, calls recurse on each of its items.
 *     If inplace is true, modifies the form and sets each item to the
 *     result of the call to recurse. If inplace is false, creates a new
 *     array/map containing the results of calling recurse. Returns
 *     either form if inplace is true, or the newly created array/map.
 *
 *     If form is not an array or map, it is simply returned.
 *
 *     An example using walk() in a custom recursive traversal function:
 * 
 *     function doSomething(root) {
 *         function step(form) {
 *             form = Trees.walk(form, step);
 *             // do something with form
 *             return form ? [form] : [];
 *         }
 *         return step(root)[0] || null;
 *     }
 *
 * walk(form, recurse)
 *
 *     Short for walk(form, recurse, true)
 *
 * walkDomInplace(form, recurse)
 *
 *     Similar to walk() but operates on DOM nodes.
 *
 *     Elements are considered non-leaf, and everything else is
 *     considerd a leaf.
 *
 * Note: All functions work on array+map trees, unless they are suffixed
 *       with Dom, in which case they only work on DOM nodes.
 *
 * Note: When walking arrays and maps, if the fn and leaf functions
 *       modify the parent or any ancestor of the passed form, the
 *       resulting behaviour is undefined. Only modification of the
 *       passed form and descendants of the passed form is valid.
 *
 *       During DOM traversal, it is allowed to insert-into/remove-from
 *       the children of the parent of the given form, as long the given
 *       form itself is not removed.
 *
 * Note: the algorithms are recursive and the maximum nesting level of
 *       the input set is therefore bound to the maximum stack depth.
 *       IE7 and IE8 for example have a maximum stack depth of greater
 *       than 1000, so the maximum input nesting level should not exceed
 *       about 300 (3 stack frames are needed per nesting level).
 */
define('util/trees',['jquery'], function ($) {
	

	function walk(form, step, inplace) {
		var type = $.type(form),
			subResult,
			result,
			resultOff,
			len,
			i,
			key;
		if ('array' === type) {
			result = (inplace ? form : []);
			resultOff = 0;
			for (i = 0, len = form.length; i < len; i++) {
				subResult = step(form[i]);
				if (subResult.length) {
					result[resultOff++] = subResult[0];
				}
			}
			if (resultOff !== result.length) {
				// TODO is result.length = resultOff better?
				result = result.slice(0, resultOff);
			}
		} else if ('object' === type) {
			result = (inplace ? form : {});
			for (key in form) {
				if (form.hasOwnProperty(key)) {
					subResult = step(form[key]);
					if (subResult.length) {
						result[key] = subResult[0];
					} else {
						delete result[key];
					}
				}
			}
		} else {
			result = form;
		}
		return result;
	}

	function walkInplace(form, step) {
		return walk(form, step, true);
	}

	function walkDomInplace(form, step) {
		var subResult,
		    child,
		    nextChild;
		if (1 === form.nodeType) {
			child = form.firstChild;
			while (child) {
				subResult = step(child);
				// Advance to the next child _after stepping into child_
				// to pick up modifications of the DOM.
				nextChild = child.nextSibling;
				if (subResult.length) {
					if (subResult[0] !== child) {
						form.replaceChild(subResult[0], child);
					}
				} else {
					form.removeChild(child);
				}
				child = nextChild;
			}
		}
		return form;
	}

	function prewalkStep(step, fn, walk, form) {
		return [walk(fn(form), step)];
	}

	function postwalkStep(step, fn, walk, form) {
		return [fn(walk(form, step))];
	}

	function prepruneStep(step, fn, walk, form) {
		return fn(form) ? [] : [walk(form, step)];
	}

	function postpruneStep(step, fn, walk, form) {
		var subForm = walk(form, step);
		return fn(subForm) ? [] : [subForm];
	}

	function prepost(step, fnOrPred, walk, form) {
		function prepostStep(form) {
			return step(prepostStep, fnOrPred, walk, form);
		}
		return prepostStep(form)[0];
	}

	function prewalk(form, fn, inplace) {
		return prepost(prewalkStep, fn, inplace ? walkInplace : walk, form);
	}

	function postwalk(form, fn, inplace) {
		return prepost(postwalkStep, fn, inplace ? walkInplace : walk, form);
	}

	function preprune(form, pred, inplace) {
		return prepost(prepruneStep, pred, inplace ? walkInplace : walk, form);
	}

	function postprune(form, pred, inplace) {
		return prepost(postpruneStep, pred, inplace ? walkInplace : walk, form);
	}

	function prewalkDom(form, fn, inplace) {
		return prepost(prewalkStep, fn, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function postwalkDom(form, fn, inplace) {
		return prepost(postwalkStep, fn, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function prepruneDom(form, pred, inplace) {
		return prepost(prepruneStep, pred, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function postpruneDom(form, pred, inplace) {
		return prepost(postpruneStep, pred, walkDomInplace, inplace ? form : form.cloneNode(true));
	}

	function isLeaf(form) {
		var type = $.type(form);
		return type !== 'object' && type !== 'array';
	}

	function identityStep(step, walk, form) {
		return [walk(form, step)];
	}

	function leaves(form, leaf, inplace) {
		var leafWalk = inplace ? walkInplace : walk;

		function leafStep(form) {
			if (isLeaf(form)) {
				return [leaf(form)];
			}
			return identityStep(leafStep, leafWalk, form);
		}
		return leafStep(form)[0];
	}

	function clone(form) {
		function cloneStep(form) {
			return identityStep(cloneStep, walk, form);
		}
		return cloneStep(form)[0];
	}

	function flatten(form) {
		var inplace = true;
		var result = [];
		leaves(form, function (leaf) {
			result.push(leaf);
			return leaf;
		}, inplace);
		return result;
	}

	return {
		prewalk: prewalk,
		postwalk: postwalk,
		preprune: preprune,
		postprune: postprune,
		prewalkDom: prewalkDom,
		postwalkDom: postwalkDom,
		prepruneDom: prepruneDom,
		postpruneDom: postpruneDom,
		isLeaf: isLeaf,
		leaves: leaves,
		clone: clone,
		flatten: flatten,
		walk: walk,
		walkInplace: walkInplace,
		walkDomInplace: walkDomInplace
	};
});

/* ephemera.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/browser',['jquery'], function ($) {
	
	return {
		ie7: $.browser.msie && parseInt($.browser.version, 10) < 8,
		ie : $.browser.msie
	};
});

/* dom2.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/dom2',[
	'aloha/core',
	'jquery',
	'util/functions',
	'util/maps',
	'util/arrays',
	'util/strings',
	'util/browser',
	'util/dom',
	'util/range'
], function (
	Aloha,
	$,
	Fn,
	Maps,
	Arrays,
	Strings,
	Browser,
	Dom1,
	RangeObject
) {
	

	var spacesRx = /\s+/;
	var attrRegex = /\s([^\/<>\s=]+)(?:=(?:"[^"]*"|'[^']*'|[^>\/\s]+))?/g;

	/**
	 * Like insertBefore, inserts firstChild into parent before
	 * refChild, except also inserts all the following siblings of
	 * firstChild.
	 */
	function moveNextAll(parent, firstChild, refChild) {
		while (firstChild) {
			var nextChild = firstChild.nextSibling;
			parent.insertBefore(firstChild, refChild);
			firstChild = nextChild;
		}
	}

	/**
	 * Used to serialize outerHTML of DOM elements in older (pre-HTML5) Gecko,
	 * Safari, and Opera browsers.
	 *
	 * Beware that XMLSerializer generates an XHTML string (<div class="team" />
	 * instead of <div class="team"></div>).  It is noted here:
	 * http://stackoverflow.com/questions/1700870/how-do-i-do-outerhtml-in-firefox
	 * that some browsers (like older versions of Firefox) have problems with
	 * XMLSerializer, and an alternative, albeit more expensive option, is
	 * described.
	 *
	 * @type {XMLSerializer|null}
	 */
	var Serializer = window.XMLSerializer && new window.XMLSerializer();

	/**
	 * Gets the serialized HTML that describes the given DOM element and its
	 * innerHTML.
	 *
	 * Polyfill for older versions of Gecko, Safari, and Opera browsers.
	 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=92264 for background.
	 *
	 * @param {HTMLElement} node DOM Element.
	 * @return {String}
	 */
	function outerHtml(node) {
		var html = node.outerHTML;
		if (typeof html !== 'undefined') {
			return html;
		}
		try {
			return Serializer ? Serializer.serializeToString(node) : node.xml;
		} catch (e) {
			return node.xml;
		}
	}

	/**
	 * Retrieves the names of all attributes from the given elmenet.
	 *
	 * Correctly handles the case that IE7 and IE8 have approx 70-90
	 * default attributes on each and every element.
	 *
	 * This implementation does not iterate over the elem.attributes
	 * property since that is much slower on IE7 (even when
	 * checking the attrNode.specified property). Instead it parses the
	 * HTML of the element. For elements with few attributes the
	 * performance on IE7 is improved by an order of magnitued.
	 *
	 * On IE7, when you clone a <button disabled="disabled"/> or an
	 * <input checked="checked"/> element the boolean properties will
	 * not be set on the cloned node. We choose the speed optimization
	 * over correctness in this case. The dom-to-xhtml plugin has a
	 * workaround for this case.
	 */
	function attrNames(elem) {
		var names = [];
		var html = outerHtml(elem.cloneNode(false));
		var match;
		while (null != (match = attrRegex.exec(html))) {
			names.push(match[1]);
		}
		return names;
	}

	/**
	 * Gets the attributes of the given element.
	 *
	 * See attrNames() for an edge case on IE7.
	 *
	 * @param elem
	 *        An element to get the attributes for.
	 * @return
	 *        An array containing [name, value] tuples for each attribute.
	 *        Attribute values will always be strings, but possibly empty strings.
	 */
	function attrs(elem) {
		var as = [];
		var names = attrNames(elem);
		var i;
		var len;
		for (i = 0, len = names.length; i < len; i++) {
			var name = names[i];
			var value = $.attr(elem, name);
			if (null == value) {
				value = "";
			} else {
				value = value.toString();
			}
			as.push([name, value]);
		}
		return as;
	}

	/**
	 * Like indexByClass() but operates on a list of elements instead.
	 * The given list may be a NodeList, HTMLCollection, or an array.
	 */
	function indexByClassHaveList(elems, classMap) {
		var index = {},
		    indexed,
		    classes,
		    elem,
		    cls,
		    len,
		    i,
		    j;
		for (i = 0, len = elems.length; i < len; i++) {
			elem = elems[i];
			if (elem.className) {
				classes = Strings.words(elem.className);
				for (j = 0; j < classes.length; j++) {
					cls = classes[j];
					if (classMap[cls]) {
						indexed = index[cls];
						if (indexed) {
							indexed.push(elem);
						} else {
							index[cls] = [elem];
						}
					}
				}
			}
		}
		return index;
	}

	/**
	 * Indexes descendant elements based on the individual classes in
	 * the class attribute.
	 *
	 * Based on these observations;
	 * 
	 * * $('.class1, .class2') takes twice as long as $('.class1') on IE7.
	 *
	 * * $('.class1, .class2') is fast on IE8 (approx the same as
	 *   $('.class'), no matter how many classes), but if the individual
	 *   elements in the result set should be handled differently, the
	 *   subsequent hasClass('.class1') and hasClass('.class2') calls
	 *   slow things down again.
	 *
	 * * DOM traversal with elem.firstChild elem.nextSibling is very
	 *   slow on IE7 compared to just iterating over
	 *   root.getElementsByTagName('*').
	 *
	 * * $('name.class') is much faster than just $('.class'), but as
	 *   soon as you need a single class in classMap that may be present
	 *   on any element, that optimization doesn't gain anything since
	 *   then you have to examine every element.
	 *
	 * This function will always take approx. the same amount of time
	 * (on IE7 approx. equivalent to a single call to $('.class')) no
	 * matter how many entries there are in classMap to index.
	 *
	 * This function only makes sense for multiple entries in
	 * classMap. For a single class lookup, $('.class') or
	 * $('name.class') is fine (even better in the latter case).
	 *
	 * @param root
	 *        The root element to search for elements to index
	 *        (will not be included in search).
	 * @param classMap
	 *        A map from class name to boolean true.
	 * @return
	 *        A map from class name to an array of elements with that class.
	 *        Every entry in classMap for which elements have been found
	 *        will have a corresponding entry in the returned
	 *        map. Entries for which no elements have been found, may or
	 *        may not have an entry in the returned map.
	 */
	function indexByClass(root, classMap) {
		var elems;
		if (Browser.ie7) {
			elems = root.getElementsByTagName('*');
		} else {
			// Optimize for browsers that support querySelectorAll/getElementsByClassName.
			// On IE8 for example, if there is a relatively high
			// elems/resultSet ratio, performance can improve by a factor of 2.
			elems = $(root).find('.' + Maps.keys(classMap).join(',.'));
		}
		return indexByClassHaveList(elems, classMap);
	}

	/**
	 * Indexes descendant elements based on elem.nodeName.
	 *
	 * Based on these observations:
	 *
	 * * On IE8, for moderate values of names.length, individual calls to
	 *   getElementsByTagName is just as fast as $root.find('name, name,
	 *   name, name').
	 *
	 * * On IE7, $root.find('name, name, name, name') is extemely slow
	 *   (can be an order of magnitude slower than individual calls to
	 *    getElementsByTagName, why is that?).
	 *
	 * * Although getElementsByTagName is very fast even on IE7, when
	 *   names.length > 7 an alternative implementation that iterates
	 *   over all tags and checks names from a hashmap (similar to how
	 *   indexByClass does it) may become interesting, but
	 *   names.length > 7 is unlikely.
	 *
	 * This function only makes sense if the given names array has many
	 * entries. For only one or two different names, calling $('name')
	 * or context.getElementsByTagName(name) directly is fine (but
	 * beware of $('name, name, ...') as explained above).
	 *
	 * The signature of this function differs from indexByClass by not
	 * taking a map but instead an array of names.
	 *
	 * @param root
	 *        The root element to search for elements to index
	 *        (will not be included in search).
	 * @param names
	 *        An array of element names to look for.
	 *        Names must be in all-uppercase (the same as elem.nodeName).
	 * @return
	 *        A map from element name to an array of elements with that name.
	 *        Names will be all-uppercase.
	 *        Arrays will be proper arrays, not NodeLists.
	 *        Every entry in classMap for which elements have been found
	 *        will have a corresponding entry in the returned
	 *        map. Entries for which no elements have been found, may or
	 *        may not have an entry in the returned map.
	 */
	function indexByName(root, names) {
		var i,
		    index = {},
		    len;
		for (i = 0, len = names.length; i < len; i++) {
			var name = names[i];
			index[name] = $.makeArray(root.getElementsByTagName(name));
		}
		return index;
	}

	function nodeIndex(node) {
		var ret = 0;
		while (node.previousSibling) {
			ret++;
			node = node.previousSibling;
		}
		return ret;
	}

	/**
	 * Can't use elem.childNodes.length because
	 * http://www.quirksmode.org/dom/w3c_core.html
	 * "IE up to 8 does not count empty text nodes."
	 */
	function numChildren(elem) {
		var count = 0;
		var child = elem.firstChild;
		while (child) {
			count += 1;
			child = child.nextSibling;
		}
		return count;
	}

	function nodeLength(node) {
		if (1 === node.nodeType) {
			return numChildren(node);
		}
		if (3 === node.nodeType) {
			return node.length;
		}
		return 0;
	}

	function isAtEnd(node, offset) {
		return (1 === node.nodeType
				&& offset >= numChildren(node))
			|| (3 === node.nodeType
				&& offset === node.length
				&& !node.nextSibling);
	}

	/**
	 * @param node if a text node, should have a parent node.
	 */
	function nodeAtOffset(node, offset) {
		if (1 === node.nodeType && offset < numChildren(node)) {
			node = node.childNodes[offset];
		} else if (3 === node.nodeType && offset === node.length) {
			node = node.nextSibling || node.parentNode;
		}
		return node;
	}

	function removeShallow(node) {
		var parent = node.parentNode;
		moveNextAll(parent, node.firstChild, node);
		parent.removeChild(node);
	}

	function wrap(node, wrapper) {
		node.parentNode.replaceChild(wrapper, node);
		wrapper.appendChild(node);
	}

	function insert(node, ref, atEnd) {
		if (atEnd) {
			ref.appendChild(node);
		} else {
			ref.parentNode.insertBefore(node, ref);
		}
	}

	function Cursor(node, atEnd) {
		this.node = node;
		this.atEnd = atEnd;
	}

	/**
	 * A cursor has the added utility over other iteration methods of
	 * iterating over the end position of an element. The start and end
	 * positions of an element are immediately before the element and
	 * immediately after the last child respectively. All node positions
	 * except end positions can be identified just by a node. To
	 * distinguish between element start and end positions, the
	 * additional atEnd boolean is necessary.
	 */
	function cursor(node, atEnd) {
		return new Cursor(node, atEnd);
	}

	Cursor.prototype.next = function () {
		var node = this.node;
		var next;
		if (this.atEnd || 1 !== node.nodeType) {
			next = node.nextSibling;
			if (next) {
				this.atEnd = false;
			} else {
				next = node.parentNode;
				if (!next) {
					return false;
				}
				this.atEnd = true;
			}
			this.node = next;
		} else {
			next = node.firstChild;
			if (next) {
				this.node = next;
			} else {
				this.atEnd = true;
			}
		}
		return true;
	};

	Cursor.prototype.prev = function () {
		var node = this.node;
		var prev;
		if (this.atEnd) {
			prev = node.lastChild;
			if (prev) {
				this.node = prev;
			} else {
				this.atEnd = false;
			}
		} else {
			prev = node.previousSibling;
			if (prev) {
				if (1 === node.nodeType) {
					this.atEnd = true;
				}
			} else {
				prev = node.parentNode;
				if (!prev) {
					return false;
				}
			}
			this.node = prev;
		}
		return true;
	};

	Cursor.prototype.equals = function (cursor) {
		return cursor.node === this.node && cursor.atEnd === this.atEnd;
	};

	Cursor.prototype.clone = function (cursor) {
		return cursor(cursor.node, cursor.atEnd);
	};

	Cursor.prototype.insert = function (node) {
		return insert(node, this.node, this.atEnd);
	};

	/**
	 * @param offset if node is a text node, the offset will be ignored.
	 * @param node if a text node, should have a parent node.
	 */
	function cursorFromBoundaryPoint(node, offset) {
		return cursor(nodeAtOffset(node, offset), isAtEnd(node, offset));
	}

	function parentsUntil(node, pred) {
		var parents = [];
		var parent = node.parentNode;
		while (parent && !pred(parent)) {
			parents.push(parent);
			parent = parent.parentNode;
		}
		return parents;
	}

	function parentsUntilIncl(node, pred) {
		var parents = parentsUntil(node, pred);
		var topmost = parents.length ? parents[parents.length - 1] : node;
		if (topmost.parentNode) {
			parents.push(topmost.parentNode);
		}
		return parents;
	}

	function childAndParentsUntil(node, pred) {
		if (pred(node)) {
			return [];
		}
		var parents = parentsUntil(node, pred);
		parents.unshift(node);
		return parents;
	}

	function childAndParentsUntilIncl(node, pred) {
		if (pred(node)) {
			return [node];
		}
		var parents = parentsUntilIncl(node, pred);
		parents.unshift(node);
		return parents;
	}

	function childAndParentsUntilNode(node, untilNode) {
		return childAndParentsUntil(node, function (nextNode) {
			return nextNode === untilNode;
		});
	}

	function childAndParentsUntilInclNode(node, untilInclNode) {
		return childAndParentsUntilIncl(node, function (nextNode) {
			return nextNode === untilInclNode;
		});
	}

	function next(node, until, arg) {
		while (node && !until(node, arg)) {
			node = node.nextSibling;
		}
		return node;
	}

	function parent(node, until, arg) {
		while (node && !until(node, arg)) {
			node = node.parentNode;
		}
		return node;
	}

	function isTextNode(node) {
		return 3 === node.nodeType;
	}

	function splitTextNode(node, offset) {
		// Because node.splitText() is buggy on IE, split it manually.
		// http://www.quirksmode.org/dom/w3c_core.html
		var parent = node.parentNode;
		var text = node.nodeValue;
		if (0 === offset || offset >= text.length) {
			return node;
		}
		var before = document.createTextNode(text.substring(0, offset));
		var after = document.createTextNode(text.substring(offset, text.length));
		parent.insertBefore(before, node);
		parent.insertBefore(after, node);
		parent.removeChild(node);
		return before;
	}

	function adjustRangeAfterSplit(range, container, offset, setProp, splitNode, newNodeBeforeSplit) {
		if (container !== splitNode) {
			return;
		}
		var newNodeLength = newNodeBeforeSplit.length;
		if (offset === 0) {
			container = newNodeBeforeSplit.parentNode;
			offset = nodeIndex(newNodeBeforeSplit);
		} else if (offset < newNodeLength) {
			container = newNodeBeforeSplit;
		} else if (offset === newNodeLength) {
			container = newNodeBeforeSplit.parentNode;
			offset = nodeIndex(newNodeBeforeSplit) + 1;
		} else {// offset > newNodeLength
			var newNodeAfterSplit = newNodeBeforeSplit.nextSibling;
			container = newNodeAfterSplit;
			offset -= newNodeLength;
		}
		range[setProp].call(range, container, offset);
	}

	/**
	 * Splits the given text node at the given offset and, if the given
	 * range happens to have start or end containers equal to the given
	 * text node, adjusts it such that start and end position will point
	 * at the same position in the new text nodes.
	 *
	 * It is guaranteed that an adjusted boundary point will not point
	 * to the end of a text node. Instead, it will point to the next
	 * node. This guarantee often happens to be useful.
	 *
	 * If splitNode is not a text node, does nothing.
	 */
	function splitTextNodeAdjustRange(splitNode, splitOffset, range) {
		if (3 !== splitNode.nodeType) {
			return;
		}
		var sc = range.startContainer;
		var so = range.startOffset;
		var ec = range.endContainer;
		var eo = range.endOffset;
		var newNodeBeforeSplit = splitTextNode(splitNode, splitOffset);
		adjustRangeAfterSplit(range, sc, so, 'setStart', splitNode, newNodeBeforeSplit);
		adjustRangeAfterSplit(range, ec, eo, 'setEnd', splitNode, newNodeBeforeSplit);
	}

	function splitTextContainers(range) {
		var sc = range.startContainer;
		var so = range.startOffset;
		splitTextNodeAdjustRange(sc, so, range);
		// Because the range may have been adjusted.
		var ec = range.endContainer;
		var eo = range.endOffset;
		splitTextNodeAdjustRange(ec, eo, range);
	}

	function walkUntil(node, fn, until, arg) {
		while (node && !until(node, arg)) {
			var next = node.nextSibling;
			fn(node, arg);
			node = next;
		}
	}

	function walk(node, fn, arg) {
		walkUntil(node, fn, Fn.returnFalse, arg);
	}

	/**
	 * Depth-first postwalk of the given DOM node.
	 */
	function walkRec(node, fn, arg) {
		if (1 === node.nodeType) {
			walk(node.firstChild, function (node) {
				walkRec(node, fn, arg);
			});
		}
		fn(node, arg);
	}

	function walkUntilNode(node, fn, untilNode, arg) {
		walkUntil(node, fn, function (nextNode) {
			return nextNode === untilNode;
		}, arg);
	}

	function StableRange(range) {
		if (!range) {
			return;
		}
		this.startContainer = range.startContainer;
		this.startOffset = range.startOffset;
		this.endContainer = range.endContainer;
		this.endOffset = range.endOffset;
		this.commonAncestorContainer = range.commonAncestorContainer;
		this.collapsed = range.collapsed;
	}

	StableRange.prototype.update = function () {
		if (!this.startContainer || !this.endContainer) {
			return;
		}
		this.collapsed = (this.startContainer === this.endContainer
						  && this.startOffset === this.endOffset);
		var start = childAndParentsUntil(this.startContainer, Fn.returnFalse);
		var end   = childAndParentsUntil(this.endContainer, Fn.returnFalse);
		this.commonAncestorContainer = Arrays.intersect(start, end)[0];
	};

	StableRange.prototype.setStart = function (sc, so) {
		this.startContainer = sc;
		this.startOffset = so;
		this.update();
	};

	StableRange.prototype.setEnd = function (ec, eo) {
		this.endContainer = ec;
		this.endOffset = eo;
		this.update();
	};

	function setRangeStartFromCursor(range, cursor) {
		if (cursor.atEnd) {
			range.setStart(cursor.node, numChildren(cursor.node));
		} else {
			range.setStart(cursor.node.parentNode, nodeIndex(cursor.node));
		}
	}

	function setRangeEndFromCursor(range, cursor) {
		if (cursor.atEnd) {
			range.setEnd(cursor.node, numChildren(cursor.node));
		} else {
			range.setEnd(cursor.node.parentNode, nodeIndex(cursor.node));
		}
	}

	function setRangeFromRef(range, ref) {
		range.setStart(ref.startContainer, ref.startOffset);
		range.setEnd(ref.endContainer, ref.endOffset);
	}

	/**
	 * A native range is live, which means that modifying the DOM may
	 * mutate the range. Also, using setStart/setEnd may not set the
	 * properties correctly (the browser may perform its own
	 * normalization of boundary points). The behaviour of a native
	 * range is very erratic and should be converted to a stable range
	 * as the first thing in any algorithm.
	 */
	function stableRange(range) {
		return new StableRange(range);
	}

	/**
	 * The dom cursor passed to ignoreLeft and ignoreRight does not
	 * traverse positions inside text nodes. The exact rules for when
	 * text node containers are passed are as follows: If the left
	 * boundary point is inside a text node, trimming will start before
	 * it. If the right boundary point is inside a text node, trimming
	 * will start after it.
	 */
	function trimRange(range, ignoreLeft, ignoreRight) {
		if (range.collapsed) {
			return;
		}
		var start = cursorFromBoundaryPoint(range.startContainer, range.startOffset);
		var end = cursorFromBoundaryPoint(range.endContainer, range.endOffset);
		var setStart = false;
		while (!start.equals(end) && ignoreLeft(start) && start.next()) {
			setStart = true;
		}
		ignoreRight = ignoreRight || ignoreLeft;
		var setEnd = false;
		// Because if the right boundary points is inside a text node,
		// trimming starts after it.
		if (3 === range.endContainer.nodeType
			    && range.endOffset > 0
			    // Because the cursor already normalizes
			    // endOffset == endContainer.length to the node next after it.
			    && range.endOffset < range.endContainer.length
			    && end.next()) {
			if (ignoreRight(end)) {
				end.prev();
			}
		}
		while (!end.equals(start) && ignoreRight(end) && end.prev()) {
			setEnd = true;
		}
		if (setStart) {
			setRangeStartFromCursor(range, start);
		}
		if (setEnd) {
			setRangeEndFromCursor(range, end);
		}
	}

	function trimRangeClosingOpening(range, ignoreLeft, ignoreRight) {
		ignoreRight = ignoreRight || ignoreLeft;
		trimRange(range, function (cursor) {
			return cursor.atEnd || ignoreLeft(cursor.node);
		}, function (cursor) {
			var prev = cursor.atEnd ? cursor.node.lastChild : cursor.node.previousSibling;
			return !prev || ignoreRight(prev);
		});
	}

	function areRangesEq(a, b) {
		return a.startContainer === b.startContainer
			&& a.startOffset    === b.startOffset
			&& a.endContainer   === b.endContainer
			&& a.endOffset      === b.endOffset;
	}

	function insertSelectText(text, range) {
		// Because empty text nodes are generally not nice and even
		// cause problems with IE8 (elem.childNodes).
		if (!text.length) {
			return;
		}
		splitTextNodeAdjustRange(range.startContainer, range.startOffset, range);
		var node = nodeAtOffset(range.startContainer, range.startOffset);
		var atEnd = isAtEnd(range.startContainer, range.startOffset);
		// Because if the node following the insert position is already
		// a text node we can just reuse it.
		if (!atEnd && 3 === node.nodeType) {
			node.insertData(0, text);
			range.setStart(node, 0);
			range.setEnd(node, text.length);
			return;
		}
		// Because if the node preceding the insert position is already
		// a text node we can just reuse it.
		var prev;
		if (!atEnd) {
			prev = node.previousSibling;
		} else {
			prev = node.lastChild;
		}
		if (prev && 3 === prev.nodeType) {
			prev.insertData(prev.length, text);
			range.setStart(prev, prev.length - text.length);
			range.setEnd(prev, prev.length);
			return;
		}
		// Because if we can't reuse any text nodes, we have to insert a
		// new one.
		var textNode = document.createTextNode(text);
		insert(textNode, node, atEnd);
		range.setStart(textNode, 0);
		range.setEnd(textNode, textNode.length);
	}

	function collapseToEnd(range) {
		range.setStart(range.endContainer, range.endOffset);
	}

	function rangeFromRangeObject(alohaRange) {
		var range = Aloha.createRange();
		range.setStart(alohaRange.startContainer, alohaRange.startOffset);
		range.setEnd(alohaRange.endContainer, alohaRange.endOffset);
		return range;
	}

	function extendToWord(range) {
		var rangeObject = new RangeObject(range);
		Dom1.extendToWord(rangeObject);
		setRangeFromRef(range, rangeObject);
	}

	function cloneShallow(node) {
		return node.cloneNode(false);
	}

	/**
	 * Sets a style on the given element by modifying it's style attribute.
	 */
	function setStyle(node, name, value) {
		// Because only the empty string removes a style.
		$(node).css(name, null == value ? '' : value);
	}

	/**
	 * Gets a style from the given element's style attribute.
	 * Note that this is different from the computed/inherited style.
	 */
	function getStyle(node, name) {
		// Because IE7 needs dashesToCamelCase().
		name = Strings.dashesToCamelCase(name);
		return node.nodeType === 1 ? node.style[name] : null;
	}

	/**
	 * Gets the computed/inherited style of the given node.
	 * @param node may be a text node.
	 */
	function getComputedStyle(node, name) {
		if (node.currentStyle) {
			return node.currentStyle[name];
		}
		var doc = node.ownerDocument;
		if (doc.defaultView && doc.defaultView.getComputedStyle) {
			var styles = doc.defaultView.getComputedStyle(node, null);
			if (styles) {
				return styles[name] || styles.getPropertyValue(name);
			}
		}
		return null;
	}

	/**
	 * Given a node, will return node that succeeds it in the document order.
	 *
	 * For example, if this function is called recursively, starting from the
	 * text node "one" in the below DOM tree:
	 *
	 *	"one"
	 *	<b>
	 *		"two"
	 *		<u>
	 *			<i>
	 *				"three"
	 *			</i>
	 *		</u>
	 *		"four"
	 *	</b>
	 *	"five"
	 *
	 * forward() will return nodes in the following order:
	 *
	 * <b>...</b>, "two", <u>...</u>, <i>...</i>, "three", "four", "five"
	 *
	 * @param {DOMObject} node
	 * @return {DOMObject}
	 *         The succeeding node or null if the given node has no previous
	 *         siblings and no parent.
	 */
	function forward(node) {
		if (node.firstChild) {
			return node.firstChild;
		}
		var next = node;
		while (next && !next.nextSibling) {
			next = next.parentNode;
		}
		return next && next.nextSibling;
	}

	/**
	 * Given a node, will return node that preceeds it in the document order.
	 *
	 * For example, if this function is called recursively, starting from the
	 * text node "five" in the below DOM tree:
	 *
	 *	"one"
	 *	<b>
	 *		"two"
	 *		<u>
	 *			<i>
	 *				"three"
	 *			</i>
	 *		</u>
	 *		"four"
	 *	</b>
	 *	"five"
	 *
	 * backward() will return nodes in the following order:
	 *
	 * "four", "three", <i>...</i>, <u>...</u>, "two", <b>...</b>, "one"
	 *
	 * @param {DOMObject} node
	 * @return {DOMObject}
	 *         The preceeding node or null if the given node has no previous
	 *         siblings and no parent.
	 */
	function backward(node) {
		var prev = node.previousSibling;
		while (prev && prev.lastChild) {
			prev = prev.lastChild;
		}
		return prev || node.parentNode;
	}

	/**
	 * Starting from the given node, and moving forwards through the DOM tree,
	 * searches for a node which returns `true` when applied to the predicate
	 * `match()`.
	 *
	 * @param {DOMObject} node
	 * @param {Function(DOMObject):Boolean} match
	 * @param {Function(DOMObject):Boolean} until
	 * @return {DOMObject}
	 */
	function findForward(node, match, until) {
		while (node && !until(node)) {
			if (match(node)) {
				return node;
			}
			node = forward(node);
		}
		return null;
	}

	/**
	 * Starting from the given node, and moving backwards through the DOM tree,
	 * searches for a node which returns `true` when applied to the predicate
	 * `match()`.
	 *
	 * @param {DOMObject} node
	 * @param {Function(DOMObject):Boolean} match
	 * @param {Function(DOMObject):Boolean} until
	 * @return {DOMObject}
	 */
	function findBackward(node, match, until) {
		while (node && !until(node)) {
			if (match(node)) {
				return node;
			}
			node = backward(node);
		}
		return null;
	}

	return {
		backward: backward,
		forward: forward,
		findForward: findForward,
		findBackward: findBackward,
		moveNextAll: moveNextAll,
		attrNames: attrNames,
		attrs: attrs,
		indexByClass: indexByClass,
		indexByName: indexByName,
		indexByClassHaveList: indexByClassHaveList,
		outerHtml: outerHtml,
		removeShallow: removeShallow,
		wrap: wrap,
		insert: insert,
		cursor: cursor,
		cursorFromBoundaryPoint: cursorFromBoundaryPoint,
		nodeAtOffset: nodeAtOffset,
		isAtEnd: isAtEnd,
		parentsUntil: parentsUntil,
		parentsUntilIncl: parentsUntilIncl,
		childAndParentsUntil: childAndParentsUntil,
		childAndParentsUntilIncl: childAndParentsUntilIncl,
		childAndParentsUntilNode: childAndParentsUntilNode,
		childAndParentsUntilInclNode: childAndParentsUntilInclNode,
		next: next,
		parent: parent,
		isTextNode: isTextNode,
		nodeIndex: nodeIndex,
		splitTextNode: splitTextNode,
		splitTextContainers: splitTextContainers,
		walk: walk,
		walkRec: walkRec,
		walkUntil: walkUntil,
		walkUntilNode: walkUntilNode,
		stableRange: stableRange,
		trimRange: trimRange,
		trimRangeClosingOpening: trimRangeClosingOpening,
		setRangeFromRef: setRangeFromRef,
		setRangeStartFromCursor: setRangeStartFromCursor,
		setRangeEndFromCursor: setRangeEndFromCursor,
		splitTextNodeAdjustRange: splitTextNodeAdjustRange,
		insertSelectText: insertSelectText,
		areRangesEq: areRangesEq,
		collapseToEnd: collapseToEnd,
		extendToWord: extendToWord,
		rangeFromRangeObject: rangeFromRangeObject,
		cloneShallow: cloneShallow,
		setStyle: setStyle,
		getStyle: getStyle,
		getComputedStyle: getComputedStyle,
		nodeLength: nodeLength
	};
});

/* misc.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Contains miscellaneous utility functions that don't fit anywhere else.
 */
define('util/misc',[], function () {
	

	/**
	 * Returns true if any regex in the given rxs array tests true
	 * against str.
	 */
	function anyRx(rxs, str) {
		var i,
		    len;
		for (i = 0, len = rxs.length; i < len; i++) {
			if (rxs[i].test(str)) {
				return true;
			}
		}
		return false;
	}

	return {
		anyRx: anyRx
	};
});

/* ephemera.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Provides functions to mark the contents of editables as ephemeral. An
 * editable's ephemeral content will be pruned before it is being
 * returned by editable.getContents().
 * 
 * It is planned to replace most instances of makeClean() with this
 * implementation for improved performance and more importantly, in
 * order to have a centralized place that has the control over all
 * ephemeral content, which can be leveraged by plugins to provide more
 * advanced functionality.
 *
 * Some examples that would be possible:
 * * a HTML source code text box, an interactive tree structure, or
 *   other kind of DOM visualization, next to the editable, that
 *   contains just the content of the editable (without ephemeral data)
 *   and which is updated efficiently in real time after each keystroke.
 *
 * * change detection algorithms that are able to intelligently ignore
 *   ephemeral data and which would not trigger unless non-ephemeral
 *   data is added to the editable.
 *
 * * When a plugin provides very general functionality over all nodes of
 *   the DOM, somtimes the plugin may not know what is and what isn't
 *   supposed to be real content. The functionality provided here makes
 *   it possible for the plugin to exaclty distinguish real content from
 *   ephemeral content.
 *
 * TODO: currently only simple transformations are suppored, like
 *       marking classes, attributes and elements as ephemeral and removing
 *       them during the pruning process.
 *       In the future, support for the block-plugin and custom pruning
 *       functions should be added. This may be done by letting implementations
 *       completely control the pruning of a DOM element through a
 *       function that takes the content+ephemeral-data and returns only
 *       content - similar to make clean, but for single elements to reduce
 *       overhead.
 */
define('aloha/ephemera',[
	'jquery',
	'aloha/core',
	'aloha/console',
	'util/strings',
	'util/trees',
	'util/arrays',
	'util/maps',
	'util/dom2',
	'util/functions',
	'util/misc',
	'util/browser',
	'PubSub'
], function (
	$,
	Aloha,
	console,
	Strings,
	Trees,
	Arrays,
	Maps,
	Dom,
	Functions,
	Misc,
	Browser,
	PubSub
) {
	

	var ephemeraMap = {
		classMap: {
			'aloha-ephemera-wrapper': true,
			'aloha-ephemera-filler': true,
			'aloha-ephemera-attr': true,
			'aloha-ephemera': true,
			// aloha-cleanme is the same as aloha-ephemera.
			// TODO: should be replaced with aloha-ephemera throughout
			//       the codebase and removed here.
			'aloha-cleanme': true
		},
		attrMap: {
			'hidefocus': true,
			'hideFocus': true,
			'tabindex': true,
			'tabIndex': true,
			'contenteditable': ['TABLE'],
			'contentEditable': ['TABLE']
		},
		attrRxs: [/^(?:nodeIndex|sizcache|sizset|jquery)[\w\d]*$/i],
		pruneFns: []
	};

	var commonClsSubstr = 'aloha-';

	/**
	 * Checks whether the given classes contain the substring common to
	 * all ephemeral classes. If the check fails, an warning will be
	 * logged and the substring will be set to the empty string which
	 * voids the performance improvement the common substring would
	 * otherwise have gained.
	 */
	function checkCommonSubstr(clss) {
		var i, len;
		for (i = 0, len = clss.length; i < len; i++) {
			if (-1 === clss[i].indexOf(commonClsSubstr)) {
				console.warn('Class "' + clss[i] + '" was set to be ephemeral,' + 'which hurts peformance.' + ' Add the common substring "' + commonClsSubstr + '" to the class to fix this problem.');
				commonClsSubstr = '';
			}
		}
	}

	/**
	 * Registers ephemeral classes.
	 *
	 * An ephemeral class is a non-content class that will be pruned
	 * from the from the result of editable.getContents().
	 *
	 * The given classes should contain the string 'aloha-' to get the
	 * benefit of a performance optimization.
	 *
	 * Returns a map that contains all classes that were ever registered
	 * with this function.
	 *
	 * Multiple classes may be specified. If none are specified, just
	 * returns the current ephemeral classes map without modifying it.
	 *
	 * Also see ephemera().
	 */
	function classes() {
		var clss = Array.prototype.slice.call(arguments);
		Maps.fillKeys(ephemeraMap.classMap, clss, true);
		checkCommonSubstr(clss);
		PubSub.pub('aloha.ephemera.classes', {
			ephemera: ephemeraMap,
			newClasses: clss
		});
	}

	/**
	 * Registers ephemeral attributes by attribute name.
	 *
	 * Similar to classes() except applies to entire attributes instead
	 * of individual classes in the class attribute.
	 */
	function attributes() {
		var attrs = Array.prototype.slice.call(arguments);
		Maps.fillKeys(ephemeraMap.attrMap, attrs, true);
		PubSub.pub('aloha.ephemera.attributes', {
			ephemera: ephemeraMap,
			newAttributes: attrs
		});
	}

	/**
	 * Provides access to the global ephemera registry.
	 *
	 * If the given argument is not null, sets the global ephemera
	 * registry to the given value and returns it. Otherwise, just
	 * returns the global registry.
	 *
	 * The given/returned value has the following properties:
	 *
	 * The given map may have the following entries
	 *
	 * classMap - a map from class name to the value true.
	 *            all classes must have a "aloha-" prefix.
	 *            Use Ehpemera.attributes() to set classes without "aloha-" prefix.
	 *
	 * attrMap  - a map from attribute name to the value true or to an array
	 *            of element names. If an array of elements is specified, the
	 *            attribute will only be considered ephemeral if it is
	 *            found on an element in the array.
	 *
	 * attrRxs  - an array of regexes (in object - not string - form: /[a-z].../)
	 *
	 * pruneFns - an array of functions that will be called at each pruning step.
	 *
	 * When a DOM tree is pruned with prune(elem) without an emap
	 * argument, the global registry maintained with classes()
	 * attributes() and ephemera() is used as a default map. If an emap
	 * argument is specified, the global registry will be ignored and
	 * the emap argument will be used instead.
	 *
	 * When a DOM tree is pruned with prune()
	 * - classes specified by classMap will be removed
	 * - attributes specified by attrMap or attrRxs will be removed
	 * - functions specified by pruneFns will be called as the DOM tree
	 *   is descended into (pre-order), with each node (element, text,
	 *   etc.) as a single argument. The function is free to modify the
	 *   element and return it, or return a new element which will
	 *   replace the given element in the pruned tree. If null or
	 *   undefined is returned, the element will be removed from the
	 *   tree. As per contract of Maps.walkDomInplace, it is allowed to
	 *   insert/remove children in the parent node as long as the given
	 *   node is not removed.
	 *
	 * Also see classes() and attributes().
	 *
	 * Note that removal of attributes doesn't always work on IE7 (in
	 * rare special cases). The dom-to-xhtml plugin can reliably remove
	 * ephemeral attributes during the serialization step.
	 */
	function ephemera(emap) {
		if (emap) {
			ephemeraMap = emap;
		}
		PubSub.pub('aloha.ephemera', {
			ephemera: ephemeraMap
		});
		return ephemeraMap;
	}

	/**
	 * Marks an element as ephemeral.
	 *
	 * The element will be completely removed when the prune function is
	 * called on it.
	 *
	 * Adds the class 'aloha-ephemera' to the given element.
	 *
	 * The class 'aloha-ephemera' can also be added directly without
	 * recurse to this function, if that is more convenient.
	 */
	function markElement(elem) {
		$(elem).addClass('aloha-ephemera');
	}

	/**
	 * Marks the attribute of an element as ephemeral.
	 *
	 * The attribute will be removed from the element when the prune
	 * function is called on it.
	 *
	 * Multiple attributes can be passed at the same time be separating
	 * them with a space.
	 *
	 * Adds the class 'aloha-ephemera-attr' to the given element. Also
	 * adds or modifies the 'data-aloha-ephemera-attr' attribute,
	 * and adds to it the name of the given attribute.
	 *
	 * These modifications can be made directly without recurse to this
	 * function, if that is more convenient.
	 */
	function markAttr(elem, attr) {
		elem = $(elem);
		var data = elem.attr('data-aloha-ephemera-attr');
		if (null == data || '' === data) {
			data = attr;
		} else if (-1 === Arrays.indexOf(Strings.words(data), attr)) {
			data += ' ' + attr;
		}
		elem.attr('data-aloha-ephemera-attr', data);
		elem.addClass('aloha-ephemera-attr');
	}

	/**
	 * Marks an element as a ephemeral, excluding subnodes.
	 *
	 * The element will be removed when the prune function is called on
	 * it, but any children of the wrapper element will remain in its
	 * place.
	 *
	 * A wrapper is an element that wraps a single non-ephemeral
	 * element. A filler is an element that is wrapped by a single
	 * non-ephemeral element. This distinction is not important for the
	 * prune function, which behave the same for both wrappers and
	 * fillers, but it makes it easier to build more advanced content
	 * inspection algorithms (also see note at the header of ephemeral.js).
	 * 
	 * Adds the class 'aloha-ephemera-wrapper' to the given element.
	 *
	 * The class 'aloha-ephemera-wrapper' may also be added directly,
	 * without recurse to this function, if that is more convenient.
	 *
	 * NB: a wrapper element must not wrap a filler element. Wrappers
	 *     and fillers are ephermeral. A wrapper must always wrap a
	 *     single _non-ephemeral_ element, and a filler must always fill
	 *     a single _non-ephemeral_ element.
	 */
	function markWrapper(elem) {
		$(elem).addClass('aloha-ephemera-wrapper');
	}

	/**
	 * Marks an element as ephemeral, excluding subnodes.
	 *
	 * Adds the class 'aloha-ephemera-filler' to the given element.
	 *
	 * The class 'aloha-ephemera-filler' may also be added directly,
	 * without recurse to this function, if that is more convenient.
	 *
	 * See wrapper()
	 */
	function markFiller(elem) {
		$(elem).addClass('aloha-ephemera-filler');
	}

	/**
	 * Prunes attributes marked as ephemeral with Ephemera.attributes()
	 * from the given element.
	 */
	function pruneMarkedAttrs(elem) {
		var $elem = $(elem);
		var data = $elem.attr('data-aloha-ephemera-attr');
		var i;
		var attrs;
		// Because IE7 crashes if we remove this attribute. If the
		// dom-to-xhtml plugin is turned on, it will handle the removal
		// of this attribute during serialization.
		if (!Browser.ie7) {
			$elem.removeAttr('data-aloha-ephemera-attr');
		}
		if (typeof data === 'string') {
			attrs = Strings.words(data);
			for (i = 0; i < attrs.length; i++) {
				$elem.removeAttr(attrs[i]);
			}
		}
	}

	/**
	 * Determines whether the given attribute of the given element is
	 * ephemeral according to the given emap.
	 * See Ephemera.ephemera() for an explanation of attrMap and attrRxs.
	 */
	function isAttrEphemeral(elem, attrName, attrMap, attrRxs) {
		var mapped = attrMap[attrName];
		if (mapped) {
			// The attrMap may either contain boolean true or an array of element names.
			if (true === mapped) {
				return true;
			}
			if (-1 !== Arrays.indexOf(mapped, elem.nodeName)) {
				return true;
			}
		}
		return Misc.anyRx(attrRxs, attrName);
	}

	/**
	 * Prunes attributes specified with either emap.attrMap or emap.attrRxs.
	 * See ephemera().
	 */
	function pruneEmapAttrs(elem, emap) {
		var $elem = null,
			attrs = Dom.attrNames(elem),
		    name,
		    i,
		    len;
		for (i = 0, len = attrs.length; i < len; i++) {
			name = attrs[i];
			if (isAttrEphemeral(elem, name, emap.attrMap, emap.attrRxs)) {
				$elem = $elem || $(elem);
				$elem.removeAttr(name);
			}
		}
	}

	/**
	 * Prunes an element of attributes and classes or removes the
	 * element by returning false.
	 *
	 * Elements attributes and classes can either be marked as
	 * ephemeral, in which case the element itself will contain the
	 * prune-info, or they can be specified as ephemeral with the given
	 * emap.
	 *
	 * See ephemera() for an explanation of the emap argument.
	 */
	function pruneElem(elem, emap) {
		var className = elem.className;
		if (className && -1 !== className.indexOf(commonClsSubstr)) {
			var classes = Strings.words(className);

			// Ephemera.markElement()
			if (-1 !== Arrays.indexOf(classes, 'aloha-cleanme') || -1 !== Arrays.indexOf(classes, 'aloha-ephemera')) {
				$.removeData(elem); // avoids memory leak
				return false; // removes the element
			}

			// Ephemera.markWrapper() and Ephemera.markFiller()
			if (-1 !== Arrays.indexOf(classes, 'aloha-ephemera-wrapper') || -1 !== Arrays.indexOf(classes, 'aloha-ephemera-filler')) {
				Dom.moveNextAll(elem.parentNode, elem.firstChild, elem.nextSibling);
				$.removeData(elem);
				return false;
			}

			// Ephemera.markAttr()
			if (-1 !== Arrays.indexOf(classes, 'aloha-ephemera-attr')) {
				pruneMarkedAttrs(elem);
			}

			// Ephemera.classes() and Ehpemera.ephemera({ classMap: {} })
			var persistentClasses = Arrays.filter(classes, function (cls) {
				return !emap.classMap[cls];
			});
			if (persistentClasses.length !== classes.length) {
				if (0 === persistentClasses.length) {
					// Removing the attributes is dangerous. Aloha has a
					// jquery patch in place to fix some issue.
					$(elem).removeAttr('class');
				} else {
					elem.className = persistentClasses.join(' ');
				}
			}
		}

		// Ephemera.attributes() and Ephemera.ephemera({ attrMap: {}, attrRxs: {} })
		pruneEmapAttrs(elem, emap);

		return true;
	}

	/**
	 * Called for each node during the pruning of a DOM tree.
	 */
	function pruneStep(emap, step, node) {
		if (1 === node.nodeType) {
			if (!pruneElem(node, emap)) {
				return [];
			}
			node = Trees.walkDomInplace(node, step);
		}

		// Ephemera.ephemera({ pruneFns: [] })
		node = Arrays.reduce(emap.pruneFns, node, Arrays.applyNotNull);
		if (!node) {
			return [];
		}

		return [node];
	}

	/**
	 * Prunes the given element of all ephemeral data.
	 *
	 * Elements marked with Ephemera.markElement() will be removed.
	 * Attributes marked with Ephemera.markAttr() will be removed.
	 * Elements marked with Ephemera.markWrapper() or
	 * Ephemera.markFiller() will be replaced with their children.
	 *
	 * See ephemera() for an explanation of the emap argument.
	 *
	 * All properties of emap, if specified, are required, but may be
	 * empty.
	 *
	 * The element is modified in-place and returned.
	 */
	function prune(elem, emap) {
		emap = emap || ephemeraMap;

		function pruneStepClosure(node) {
			return pruneStep(emap, pruneStepClosure, node);
		}
		return pruneStepClosure(elem)[0];
	}

	return {
		ephemera: ephemera,
		classes: classes,
		attributes: attributes,
		markElement: markElement,
		markAttr: markAttr,
		markWrapper: markWrapper,
		markFiller: markFiller,
		prune: prune,
		isAttrEphemeral: isAttrEphemeral
	};
});

/* copypaste.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @overview:
 * Various utility functions that are useful when working with selections, and
 * ranges for copy/paste functionality.
 */
define('aloha/copypaste', [
	'jquery',
	'aloha/core'
], function (
	$,
	Aloha
) {
	

	/**
	 * Retrieve the editable host in which the given range is contained.
	 *
	 * @param {WrappedRange} range
	 * @return {jQuery.<HTMLElement>|null} The editable host element, null if
	 *                                     non can be determinded from the given
	 *                                     range.
	 */
	function getEditableAt(range) {
		if (!range || !range.commonAncestorContainer) {
			return null;
		}
		var $container = $(range.commonAncestorContainer);
		return $container.length ? Aloha.getEditableHost($container) : null;
	}

	/**
	 * Retrieves the current range.
	 *
	 * @return {WrappedRange|null} Range at current selection or null of non
	 *                             exists.
	 */
	function getRange() {
		var selection = Aloha.getSelection();
		return selection.getRangeCount() ? selection.getRangeAt(0) : null;
	}

	/**
	 * Set the selection to the given range
	 *
	 * @param {object} range An object that must container the following
	 *                       essential range properties: ~ startContainer
	 *                                                   ~ endContainer
	 *                                                   ~ startOffset
	 *                                                   ~ endOffset
	 */
	function setSelectionAt(range) {
		var newRange = Aloha.createRange();
		var selection = Aloha.getSelection();
		newRange.setStart(range.startContainer, range.startOffset);
		newRange.setEnd(range.endContainer, range.endOffset);
		selection.removeAllRanges();
		selection.addRange(newRange);
	}

	/**
	 * Creates a selection that encompasses the contents of the given element.
	 *
	 * @param {HTMLElement} element Editable DOM element.
	 */
	function selectAllOf(element) {
		setSelectionAt({
			startContainer: element,
			endContainer: element,
			startOffset: 0,
			endOffset: element.childNodes ? element.childNodes.length
		                                  : element.length
		});
		$(element).focus();
	}

	return {
		getEditableAt: getEditableAt,
		getRange: getRange,
		selectAllOf: selectAllOf,
		setSelectionAt: setSelectionAt
	};
});

/* command.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/command',[
	'aloha/core',
	'aloha/registry',
	'aloha/engine',
	'util/dom',
	'aloha/contenthandlermanager'
], function (
	Aloha,
	Registry,
	Engine,
	Dom,
	ContentHandlerManager
) {
	

	//			Action: What the command does when executed via execCommand(). Every command defined
	//			in CommandManager specification has an action defined for it in the relevant section. For example,
	//			the bold command's action generally makes the current selection bold, or removes bold if
	//			the selection is already bold. An editing toolbar might provide buttons that execute the
	//			action for a command if clicked, or a script might run an action without user interaction
	//			to achieve some particular effect.
	//
	//			Indeterminate: A boolean value returned by queryCommandIndeterm(), depending on the
	//			current state of the document. Generally, a command that has a state defined will be
	//			indeterminate if the state is true for part but not all of the current selection, and a
	//			command that has a value defined will be indeterminate if different parts of the
	//			selection have different values. An editing toolbar might display a button or control
	//			in a special way if the command is indeterminate, like showing a "bold" button as
	//			partially depressed, or leaving a font size selector blank instead of showing the font
	//			size of the current selection. As a rule, a command can only be indeterminate if its
	//			state is false, supposing it has a state.
	//
	//			State: A boolean value returned by queryCommandState(), depending on the current state
	//			of the document. The state of a command is true if it is already in effect, in some
	//			sense specific to the command. Most commands that have a state defined will take opposite
	//			actions depending on whether the state is true or false, such as making the selection
	//			bold if the state is false and removing bold if the state is true. Others will just
	//			have no effect if the state is true, like the justifyCenter command. Still others will
	//			have the same effect regardless, like the styleWithCss command. An editing toolbar might
	//			display a button or control differently depending on the state and indeterminacy of the
	//			command.
	//
	//			Value: A string returned by queryCommandValue(), depending on the current state of the
	//			document. A command usually has a value instead of a state if the property it modifies
	//			can take more than two different values, like the foreColor command. If the command is
	//			indeterminate, its value is generally based on the start of the selection. Otherwise,
	//			in most cases the value holds true for the entire selection, but see the justifyCenter
	//			command and its three companions for an exception. An editing toolbar might display the
	//			value of a command as selected in a drop-down or filled in in a text box, if the command
	//			isn't indeterminate.
	//
	//			Relevant CSS property: CommandManager is defined for certain inline formatting commands, and
	//			is used in algorithms specific to those commands. It is an implementation detail, and
	//			is not exposed to authors. If a command does not have a relevant CSS property
	//			specified, it defaults to null.

	var CommandManager = {

		execCommand: function (commandId, showUi, value, range) {
			var eventData = {
				commandId: commandId,
				preventDefault: false
			};

			Aloha.trigger('aloha-command-will-execute', eventData);

			if (eventData.preventDefault === true) {
				return;
			}

			var selection = Aloha.getSelection();
			if (!range) {
				if (!selection.getRangeCount()) {
					return;
				}
				range = selection.getRangeAt(0);
			}

			if (commandId.toLowerCase() === 'inserthtml') {
				value = ContentHandlerManager.handleContent(value, {
					contenthandler: Aloha.settings.contentHandler.insertHtml,
					command: 'insertHtml'
				});
			}

			Engine.execCommand(commandId, showUi, value, range);

			// Because there is never a situation where it will be necessary to
			// do any further cleanup (merging of similar adjacent nodes)
			if ('insertparagraph' !== commandId.toLowerCase()
					&& selection.getRangeCount()) {

				range = selection.getRangeAt(0);

				// FIX: doCleanup should work with W3C range
				var start = range.commonAncestorContainer;
				if (start.parentNode) {
					start = start.parentNode;
				}

				var rangeObject = new window.GENTICS.Utils.RangeObject();
				rangeObject.startContainer = range.startContainer;
				rangeObject.startOffset = range.startOffset;
				rangeObject.endContainer = range.endContainer;
				rangeObject.endOffset = range.endOffset;

				Dom.doCleanup({
					merge: true,
					removeempty: false
				}, rangeObject, start);

				rangeObject.select();
			}

			Aloha.trigger('aloha-command-executed', commandId);
		},

		// If command is available and not disabled or the active range is not null
		// the command is enabled
		queryCommandEnabled: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}
			return Engine.queryCommandEnabled(commandId, range);
		},

		// "Return true if command is indeterminate, otherwise false."
		queryCommandIndeterm: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}
			return Engine.queryCommandIndeterm(commandId, range);

		},

		queryCommandState: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}
			return Engine.queryCommandState(commandId, range);

		},

		// "When the queryCommandSupported(command) method on the HTMLDocument
		// interface is invoked, the user agent must return true if command is
		// supported, and false otherwise."
		queryCommandSupported: function (commandId) {

			return Engine.queryCommandSupported(commandId);
		},

		queryCommandValue: function (commandId, range) {

			// Take current selection if not passed
			if (!range) {
				if (!Aloha.getSelection().getRangeCount()) {
					return;
				}
				range = Aloha.getSelection().getRangeAt(0);
			}

			// "Return command's value."
			return Engine.queryCommandValue(commandId, range);
		},
		querySupportedCommands: function () {

			var commands = [],
				command;

			for (command in Engine.commands) {
				if (Engine.commands.hasOwnProperty(command)) {
					commands.push(command);
				}
			}
			return commands;
		},
		getStateOverride: Engine.getStateOverride,
		setStateOverride: Engine.setStateOverride,
		resetOverrides: Engine.resetOverrides,
		unsetStateOverride: Engine.unsetStateOverride
	};

	// create an instance
	CommandManager = new (Registry.extend(CommandManager))();

	/**
	 * Executes a registered command.
	 * http://aryeh.name/spec/editing/editing.html#methods-of-the-htmldocument-interface
	 * @method
	 * @param command name of the command
	 * @param showUI has no effect for Aloha Editor and is only here because in spec...
	 * @param value depends on the used command and it impementation
	 * @range optional a range on which the command will be executed if not specified
	 *        the current selection will be used as range
	 */
	Aloha.execCommand = CommandManager.execCommand;

	/**
	 * Check wheater the command in enabled.
	 * If command is not supported, raise a NOT_SUPPORTED_ERR exception.
	 * @param command name of the command
	 * @return true if command is enabled, false otherwise.
	 */
	Aloha.queryCommandEnabled = CommandManager.queryCommandEnabled;

	/**
	 * Check if the command has an indetermed state.
	 * If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
	 * If command has no indeterminacy, INVALID_ACCESS_ERR exception is thrown
	 * If command is not enabled, return false.
	 * @param command name of the command
	 * @range optional a range on which the command will be executed if not specified
	 *        the current selection will be used as range
	 * @return true if command is indeterminate, otherwise false.
	 */
	Aloha.queryCommandIndeterm = CommandManager.queryCommandIndeterm;

	/**
	 * Returns the state of a given command
	 * If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
	 * If command has no state, an INVALID_ACCESS_ERR exception is thrown
	 * If command is not enabled, return false
	 * If the state override for command is set, it returns the state
	 * @param command name of the command
	 * @return state override or true if command's state is true, otherwise false.
	 */
	Aloha.queryCommandState = CommandManager.queryCommandState;

	/**
	 * Check if a given command is supported
	 * @return true if command is supported, and false otherwise.
	 */
	Aloha.queryCommandSupported = CommandManager.queryCommandSupported;

	/**
	 * Returns the Value of a given Command
	 * If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
	 * If command is not enabled, returns an empty string
	 * If command is "fontSize" and its value override is set, an integer
	 * number of pixels is returned as font size for the result.
	 * If the value override for command is set, it returns that.
	 * @return command's value.
	 */
	Aloha.queryCommandValue = CommandManager.queryCommandValue;

	Aloha.querySupportedCommands = CommandManager.querySupportedCommands;

	return CommandManager;
});

/* state-override.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/state-override',[
	'aloha/core',
	'jquery',
	'aloha/command',
	'util/dom2',
	'util/maps',
	'util/range',
	'PubSub'
], function (
	Aloha,
	jQuery,
	Command,
	Dom,
	Maps,
	RangeObject,
	PubSub
) {
	

	// Because we want to provide an easy way to disable the state-override feature.
	var enabled = Aloha.settings.stateOverride !== false;
	var overrides = null;
	var overrideRange = null;

	function rangeObjectFromRange(range) {
		return new RangeObject(range);
	}

	function clear() {
		overrideRange = null;
		overrides = null;
	}

	function keyPressHandler(event) {
		if (!overrides) {
			return;
		}
		if (event.altKey || event.ctrlKey || !event.which) {
			return;
		}
		var selection = Aloha.getSelection();
		if (!selection.getRangeCount()) {
			return;
		}
		var text = String.fromCharCode(event.which);
		var range = selection.getRangeAt(0);
		Dom.insertSelectText(text, range);
		Maps.forEach(overrides, function (formatFn, command) {
			formatFn(command, range);
		});
		Dom.collapseToEnd(range);
		selection.removeAllRanges();
		selection.addRange(range);
		// Because we handled the character insert ourselves via
		// insertText we must not let the browser's default action
		// insert the character a second time.
		event.preventDefault();
	}

	function set(command, range, formatFn) {
		if (!enabled) {
			return;
		}
		overrideRange = range;
		overrides = overrides || {};
		overrides[command] = formatFn;
	}

	function setWithRangeObject(command, rangeObject, formatFn) {
		if (!enabled) {
			return;
		}
		set(command, Dom.rangeFromRangeObject(rangeObject), function (command, range) {
			var rangeObject = rangeObjectFromRange(range);
			formatFn(command, rangeObject);
			Dom.setRangeFromRef(range, rangeObject);
		});
		// Because without doing rangeObject.select(), the
		// next insertText command (see editable.js) will
		// not be reached and instead the browsers default
		// insert behaviour will be applied (which doesn't
		// know anything about state overrides). I don't
		// know the exact reasons why; probably some
		// stopPropagation somewhere by some plugin.
		rangeObject.select();
	}

	function enabledAccessor(trueFalse) {
		if (null != trueFalse) {
			enabled = trueFalse;
		}
		return enabled;
	}

	// https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#state-override
	// "Whenever the number of ranges in the selection changes to
	// something different, and whenever a boundary point of the range
	// at a given index in the selection changes to something different,
	// the state override and value override must be unset for every
	// command."
	Aloha.bind('aloha-selection-changed', function (event, range) {
		if (overrideRange && !Dom.areRangesEq(overrideRange, range)) {
			clear();
			// Because the UI may reflect the any potentially state
			// overrides that are now no longer in effect, we must
			// redraw the UI according to the current selection.
			PubSub.pub('aloha.selection.context-change', {
				range: range,
				event: event
			});
		}
	});

	return {
		enabled: enabledAccessor,
		keyPressHandler: keyPressHandler,
		setWithRangeObject: setWithRangeObject,
		set: set,
		clear: clear
	};
});

/* editable.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/editable',[
	'aloha/core',
	'util/class',
	'jquery',
	'aloha/pluginmanager',
	'aloha/selection',
	'aloha/markup',
	'aloha/contenthandlermanager',
	'aloha/console',
	'aloha/block-jump',
	'aloha/ephemera',
	'util/dom2',
	'PubSub',
	'aloha/copypaste',
	'aloha/command',
	'aloha/state-override'
], function (
	Aloha,
	Class,
	$,
	PluginManager,
	Selection,
	Markup,
	ContentHandlerManager,
	console,
	BlockJump,
	Ephemera,
	Dom,
	PubSub,
	CopyPaste,
	Command,
	StateOverride
) {
	

	var jQuery = $;
	var unescape = window.unescape,
		GENTICS = window.GENTICS,

		// True, if the next editable activate event should not be handled
		ignoreNextActivateEvent = false;

	/**
	 * A cache to hold information derived, and used in getContents().
	 * @type {object<string,(string|jQuery.<HTMLElement>)>}
	 * @private
	 */
	var editableContentCache = {};

	// default supported and custom content handler settings
	// @TODO move to new config when implemented in Aloha
	Aloha.defaults.contentHandler = {};
	Aloha.defaults.contentHandler.initEditable = ['blockelement', 'sanitize'];
	Aloha.defaults.contentHandler.getContents = ['blockelement', 'sanitize', 'basic'];

	// The insertHtml contenthandler ( paste ) will, by default, use all
	// registered content handlers.
	//Aloha.defaults.contentHandler.insertHtml = void 0;

	if (typeof Aloha.settings.contentHandler === 'undefined') {
		Aloha.settings.contentHandler = {};
	}

	var defaultContentSerializer = function (editableElement) {
		return jQuery(editableElement).html();
	};

	var contentSerializer = defaultContentSerializer;

	/**
	 * Triggers smartContentChange handlers.
	 *
	 * @param {Aloha.Editable}
	 * @return {string} Content that has been processed by getContent handlers
	 *                  and smartContentChange handlers.
	 */
	function handleSmartContentChange(editable) {
		return ContentHandlerManager.handleContent(editable.getContents(), {
			contenthandler: Aloha.settings.contentHandler.smartContentChange
		}, editable);
	}

	/**
	 * List of observed key, mapped against their keycodes.
	 *
	 * @type {object<number, string>}
	 * @const
	 */
	var KEYCODES = {
		65: 'a'
	};

	/**
	 * Handlers for various key combos.
	 * Each handler ought to return false if they do not want the event to
	 * continue propagating.
	 */
	var keyBindings = {
		'ctrl+a': function () {
			var editable = CopyPaste.getEditableAt(CopyPaste.getRange());
			if (editable) {
				CopyPaste.selectAllOf(editable.obj[0]);
				return false;
			}
		}
	};

	/**
	 * Gets the name of the modifier key if is in effect for the given event.
	 *
	 * eg: <Ctrl>+c
	 *
	 * @param {jQuery.Event} $event
	 * @return {string|null} Modifier string or null if no modifier is in
	 *                       effect.
	 *                      
	 */
	function keyModifier($event) {
		return $event.altKey ? 'alt' :
					$event.ctrlKey ? 'ctrl' :
						$event.shiftKey ? 'shift' : null;
	}

	/**
	 * Handles keydown events that are fired on the page's document.
	 *
	 * @param {jQuery.Event) $event
	 * @return {boolean} Returns false to stop propagation; undefined otherwise.
	 */
	function onKeydown($event) {
		if (!Aloha.activeEditable) {
			return;
		}
		var key = KEYCODES[$event.which];
		if (key) {
			var modifier = keyModifier($event);
			var combo = (modifier ? modifier + '+' : '') + key;
			if (keyBindings[combo]) {
				return keyBindings[combo]($event);
			}
		}
	}

	/**
	 * Registers events on the given editable's corresponding DOM element.
	 *
	 * @param {Editable} editable
	 */
	function registerEvents(editable) {
		var $editable = editable.obj;

		$editable.mousedown(function (event) {
			if (!Aloha.eventHandled) {
				Aloha.eventHandled = true;
				return editable.activate(event);
			}
		});

		$editable.mouseup(function (event) {
			Aloha.eventHandled = false;
		});

		$editable.focus(function (event) {
			return editable.activate(event);
		});

		$editable.keydown(function (event) {
			var letEventPass = Markup.preProcessKeyStrokes(event);
			editable.keyCode = event.which;
			if (!letEventPass) {
				// the event will not proceed to key press, therefore trigger
				// smartContentChange
				editable.smartContentChange(event);
			}
			return letEventPass;
		});

		$editable.keypress(StateOverride.keyPressHandler);
		$editable.keypress(function (event) {
			// triggers a smartContentChange to get the right charcode
			// To test try http://www.w3.org/2002/09/tests/keys.html
			Aloha.activeEditable.smartContentChange(event);
		});

		$editable.keyup(function (event) {
			if (event.keyCode === 27) {
				Aloha.deactivateEditable();
				return false;
			}
		});

		$editable.contentEditableSelectionChange(function (event) {
			Selection.onChange($editable, event);
			return $editable;
		});
	}

	$(document).keydown(onKeydown);

	/**
	 * Editable object
	 * @namespace Aloha
	 * @class Editable
	 * @method
	 * @constructor
	 * @param {Object} obj jQuery object reference to the object
	 */
	Aloha.Editable = Class.extend({

		_constructor: function (obj) {
			// check wheter the object has an ID otherwise generate and set
			// globally unique ID
			if (!obj.attr('id')) {
				obj.attr('id', GENTICS.Utils.guid());
			}

			// store object reference
			this.obj = obj;
			this.originalObj = obj;
			this.ready = false;

			// delimiters, timer and idle for smartContentChange
			// smartContentChange triggers -- tab: '\u0009' - space: '\u0020' - enter: 'Enter'
			// backspace: U+0008 - delete: U+007F
			this.sccDelimiters = [':', ';', '.', '!', '?', ',',
								  unescape('%u0009'), unescape('%u0020'), unescape('%u0008'), unescape('%u007F'), 'Enter'];
			this.sccIdle = 5000;
			this.sccDelay = 500;
			this.sccTimerIdle = false;
			this.sccTimerDelay = false;

			// see keyset http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html
			this.keyCodeMap = {
				93: 'Apps', // The Application key
				18: 'Alt', // The Alt ( Menu ) key.
				20: 'CapsLock', // The Caps Lock ( Capital ) key.
				17: 'Control', // The Control ( Ctrl ) key.
				40: 'Down', // The Down Arrow key.
				35: 'End', // The End key.
				13: 'Enter', // The Enter key.
				112: 'F1', // The F1 key.
				113: 'F2', // The F2 key.
				114: 'F3', // The F3 key.
				115: 'F4', // The F4 key.
				116: 'F5', // The F5 key.
				117: 'F6', // The F6 key.
				118: 'F7', // The F7 key.
				119: 'F8', // The F8 key.
				120: 'F9', // The F9 key.
				121: 'F10', // The F10 key.
				122: 'F11', // The F11 key.
				123: 'F12', // The F12 key.

				// Anybody knows the keycode for F13-F24?
				36: 'Home', // The Home key.
				45: 'Insert', // The Insert ( Ins ) key.
				37: 'Left', // The Left Arrow key.
				224: 'Meta', // The Meta key.
				34: 'PageDown', // The Page Down ( Next ) key.
				33: 'PageUp', // The Page Up key.
				19: 'Pause', // The Pause key.
				44: 'PrintScreen', // The Print Screen ( PrintScrn, SnapShot ) key.
				39: 'Right', // The Right Arrow key.
				145: 'Scroll', // The scroll lock key
				16: 'Shift', // The Shift key.
				38: 'Up', // The Up Arrow key.
				91: 'Win', // The left Windows Logo key.
				92: 'Win' // The right Windows Logo key.
			};

			this.placeholderClass = 'aloha-placeholder';

			Aloha.registerEditable(this);
		},

		/**
		 * Initialize the editable
		 * @return void
		 * @hide
		 */
		init: function () {
			var me = this;

			// TODO make editables their own settings.
			this.settings = Aloha.settings;

			// smartContentChange settings
			// @TODO move to new config when implemented in Aloha
			if (Aloha.settings && Aloha.settings.smartContentChange) {
				if (Aloha.settings.smartContentChange.delimiters) {
					this.sccDelimiters = Aloha.settings.smartContentChange.delimiters;
				}

				if (Aloha.settings.smartContentChange.idle) {
					this.sccIdle = Aloha.settings.smartContentChange.idle;
				}

				if (Aloha.settings.smartContentChange.delay) {
					this.sccDelay = Aloha.settings.smartContentChange.delay;
				}
			}

			// check if Aloha can handle the obj as Editable
			if (!this.check(this.obj)) {
				//Aloha.log( 'warn', this, 'Aloha cannot handle {' + this.obj[0].nodeName + '}' );
				this.destroy();
				return;
			}

			// apply content handler to clean up content
			if (typeof Aloha.settings.contentHandler.getContents === 'undefined') {
				Aloha.settings.contentHandler.getContents = Aloha.defaults.contentHandler.getContents;
			}

			// apply content handler to clean up content
			if (typeof Aloha.settings.contentHandler.initEditable === 'undefined') {
				Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable;
			}

			var content = me.obj.html();
			content = ContentHandlerManager.handleContent(content, {
				contenthandler: Aloha.settings.contentHandler.initEditable,
				command: 'initEditable'
			}, me);
			me.obj.html(content);

			// Because editables can only properly be initialized when Aloha
			// plugins are loaded.
			Aloha.bind('aloha-plugins-loaded', function () {
				me.obj.addClass('aloha-editable').contentEditable(true);

				registerEvents(me);

				// mark the editable as unmodified
				me.setUnmodified();

				// we don't do the sanitizing on aloha ready, since some plugins add elements into the content and bind
				// events to it. If we sanitize by replacing the html, all events would get lost. TODO: think about a
				// better solution for the sanitizing, without destroying the events  apply content handler to clean up content
				//				var content = me.obj.html();
				//				if ( typeof Aloha.settings.contentHandler.initEditable === 'undefined' ) {
				//					Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable;
				//				}
				//				content = ContentHandlerManager.handleContent( content, {
				//					contenthandler: Aloha.settings.contentHandler.initEditable
				//				} );
				//				me.obj.html( content );

				me.snapshotContent = me.getContents();


				me.initPlaceholder();

				me.ready = true;

				// disable object resizing.
				// we do this in here and with a slight delay, because
				// starting with FF 15, this would cause a JS error
				// if done before the first DOM object is made contentEditable.
				window.setTimeout(function () {
					Aloha.disableObjectResizing();
				}, 20);

				// throw a new event when the editable has been created
				/**
				 * @event editableCreated fires after a new editable has been created, eg. via $( '#editme' ).aloha()
				 * The event is triggered in Aloha's global scope Aloha
				 * @param {Event} e the event object
				 * @param {Array} a an array which contains a reference to the currently created editable on its first position
				 */
				Aloha.trigger('aloha-editable-created', [me]);
				PubSub.pub('aloha.editable.created', {data: me});
			});
		},

		/**
		 * True, if this editable is active for editing
		 * @property
		 * @type boolean
		 */
		isActive: false,

		/**
		 * stores the original content to determine if it has been modified
		 * @hide
		 */
		originalContent: null,

		/**
		 * every time a selection is made in the current editable the selection has to
		 * be saved for further use
		 * @hide
		 */
		range: undefined,

		/**
		 * Check if object can be edited by Aloha Editor
		 * @return {boolean } editable true if Aloha Editor can handle else false
		 * @hide
		 */
		check: function () {
			/* TODO check those elements
			'map', 'meter', 'object', 'output', 'progress', 'samp',
			'time', 'area', 'datalist', 'figure', 'kbd', 'keygen',
			'mark', 'math', 'wbr', 'area',
			*/

			// Extract El
			var me = this,
				obj = this.obj,
				el = obj.get(0),
				nodeName = el.nodeName.toLowerCase(),

				// supported elements
				textElements = ['a', 'abbr', 'address', 'article', 'aside', 'b', 'bdo', 'blockquote', 'cite', 'code', 'command', 'del', 'details', 'dfn', 'div', 'dl', 'em', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'i', 'ins', 'menu', 'nav', 'p', 'pre', 'q', 'ruby', 'section', 'small', 'span', 'strong', 'sub', 'sup', 'var'],
				i,
			    div;

			for (i = 0; i < textElements.length; ++i) {
				if (nodeName === textElements[i]) {
					return true;
				}
			}

			// special handled elements
			switch (nodeName) {
			case 'label':
			case 'button':
				// TODO need some special handling.
				break;
			case 'textarea':
			case 'input':
				// Create a div alongside the textarea
				div = jQuery('<div id="' + this.getId() + '-aloha" class="aloha-' + nodeName + '" />').insertAfter(obj);

				// Resize the div to the textarea and
				// Populate the div with the value of the textarea
				// Then, hide the textarea
				div.height(obj.height()).width(obj.width()).html(obj.val());

				obj.hide();

				// Attach a onsubmit to the form to place the HTML of the
				// div back into the textarea
				obj.parents('form:first').submit(function () {
					obj.val(me.getContents());
				});

				// Swap textarea reference with the new div
				this.obj = div;

				// Supported
				return true;
			default:
				break;
			}

			// the following elements are not supported
			/*
			'canvas', 'audio', 'br', 'embed', 'fieldset', 'hgroup', 'hr',
			'iframe', 'img', 'input', 'map', 'script', 'select', 'style',
			'svg', 'table', 'ul', 'video', 'ol', 'form', 'noscript',
			 */
			return false;
		},

		/**
		 * Init Placeholder
		 *
		 * @return void
		 */
		initPlaceholder: function () {
			if (Aloha.settings.placeholder && this.isEmpty()) {
				this.addPlaceholder();
			}
		},

		/**
		 * Check if the conteneditable is empty.
		 *
		 * @return {Boolean}
		 */
		isEmpty: function () {
			var editableTrimedContent = jQuery.trim(this.getContents()),
				onlyBrTag = (editableTrimedContent === '<br>') ? true : false;
			return (editableTrimedContent.length === 0 || onlyBrTag);
		},

		/**
		 * Add placeholder in editable
		 *
		 * @return void
		 */
		addPlaceholder: function () {
			var div = jQuery('<div>'),
				span = jQuery('<span>'),
				el,
				obj = this.obj;
			if (GENTICS.Utils.Dom.allowsNesting(obj[0], div[0])) {
				el = div;
			} else {
				el = span;
			}
			if (jQuery("." + this.placeholderClass, obj).length !== 0) {
				return;
			}
			jQuery.each(Aloha.settings.placeholder, function (selector, selectorConfig) {
				if (obj.is(selector)) {
					el.html(selectorConfig);
				}
			});
			if (!el.is(':empty')) {
				el.addClass(this.placeholderClass).addClass('aloha-ephemera');
				jQuery(obj).append(el);
			}
			jQuery('br', obj).remove();
		},

		/**
		 * remove placeholder from contenteditable. If setCursor is true,
		 * will also set the cursor to the start of the selection. However,
		 * this will be ASYNCHRONOUS, so if you rely on the fact that
		 * the placeholder is removed after calling this method, setCursor
		 * should be false ( or not set )
		 *
		 * @return void
		 */
		removePlaceholder: function (obj, setCursor) {
			var placeholderClass = this.placeholderClass,
				range;
			if (jQuery("." + this.placeholderClass, obj).length === 0) {
				return;
			}
			// set the cursor // remove placeholder
			if (setCursor === true) {
				window.setTimeout(function () {
					range = new Selection.SelectionRange();
					range.startContainer = range.endContainer = obj.get(0);
					range.startOffset = range.endOffset = 0;
					jQuery('.' + placeholderClass, obj).remove();
					range.select();

				}, 100);
			} else {
				jQuery('.' + placeholderClass, obj).remove();
			}
		},

		/**
		 * destroy the editable
		 * @return void
		 */
		destroy: function () {
			// leave the element just to get sure
			if (this === Aloha.getActiveEditable()) {
				this.blur();
			}

			// special handled elements
			switch (this.originalObj.get(0).nodeName.toLowerCase()) {
			case 'label':
			case 'button':
				// TODO need some special handling.
				break;
			case 'textarea':
			case 'input':
				// restore content to original textarea
				this.originalObj.val(this.getContents());
				this.obj.remove();
				this.originalObj.show();
				break;
			default:
				break;
			}

			// now the editable is not ready any more
			this.ready = false;

			// remove the placeholder if needed.
			this.removePlaceholder(this.obj);

			// initialize the object and disable contentEditable
			// unbind all events
			// TODO should only unbind the specific handlers.
			this.obj.removeClass('aloha-editable').contentEditable(false).unbind('mousedown click dblclick focus keydown keypress keyup');

			/* TODO remove this event, it should implemented as bind and unbind
			// register the onSelectionChange Event with the Editable field
			this.obj.contentEditableSelectionChange( function( event ) {
				Aloha.Selection.onChange( me.obj, event );
				return me.obj;
			} );
			*/

			// throw a new event when the editable has been created
			/**
			 * @event editableCreated fires after a new editable has been destroyes, eg. via $( '#editme' ).mahalo()
			 * The event is triggered in Aloha's global scope Aloha
			 * @param {Event} e the event object
			 * @param {Array} a an array which contains a reference to the currently created editable on its first position
			 */
			Aloha.trigger('aloha-editable-destroyed', [this]);
			PubSub.pub('aloha.editable.destroyed', {data: this});

			// finally register the editable with Aloha
			Aloha.unregisterEditable(this);
		},

		/**
		 * marks the editables current state as unmodified. Use this method to inform the editable
		 * that it's contents have been saved
		 * @method
		 */
		setUnmodified: function () {
			this.originalContent = this.getContents();
		},

		/**
		 * check if the editable has been modified during the edit process#
		 * @method
		 * @return boolean true if the editable has been modified, false otherwise
		 */
		isModified: function () {
			return this.originalContent !== this.getContents();
		},

		/**
		 * String representation of the object
		 * @method
		 * @return Aloha.Editable
		 */
		toString: function () {
			return 'Aloha.Editable';
		},

		/**
		 * check whether the editable has been disabled
		 */
		isDisabled: function () {
			return !this.obj.contentEditable() || this.obj.contentEditable() === 'false';
		},

		/**
		 * disable this editable
		 * a disabled editable cannot be written on by keyboard
		 */
		disable: function () {
			return this.isDisabled() || this.obj.contentEditable(false);
		},

		/**
		 * enable this editable
		 * reenables a disabled editable to be writteable again
		 */
		enable: function () {
			return this.isDisabled() && this.obj.contentEditable(true);
		},


		/**
		 * activates an Editable for editing
		 * disables all other active items
		 * @method
		 */
		activate: function (e) {
			// get active Editable before setting the new one.
			var oldActive = Aloha.getActiveEditable();

			// We need to ommit this call when this flag is set to true.
			// This flag will only be set to true before the removePlaceholder method
			// is called since that method invokes a focus event which will again trigger
			// this method. We want to avoid double invokation of this method.
			if (ignoreNextActivateEvent) {
				ignoreNextActivateEvent = false;
				return;
			}

			// handle special case in which a nested editable is focused by a click
			// in this case the "focus" event would be triggered on the parent element
			// which actually shifts the focus away to it's parent. this if is here to
			// prevent this situation
			if (e && e.type === 'focus' && oldActive !== null && oldActive.obj.parent().get(0) === e.currentTarget) {
				return;
			}

			// leave immediately if this is already the active editable
			if (this.isActive || this.isDisabled()) {
				// we don't want parent editables to be triggered as well, so return false
				return;
			}

			this.obj.addClass('aloha-editable-active');

			Aloha.activateEditable(this);

			ignoreNextActivateEvent = true;
			this.removePlaceholder(this.obj, true);
			ignoreNextActivateEvent = false;

			this.isActive = true;

			/**
			 * @event editableActivated fires after the editable has been activated by clicking on it.
			 * This event is triggered in Aloha's global scope Aloha
			 * @param {Event} e the event object
			 * @param {Array} a an array which contains a reference to last active editable on its first position, as well
			 * as the currently active editable on it's second position
			 */
			// trigger a 'general' editableActivated event
			Aloha.trigger('aloha-editable-activated', {
				'oldActive': oldActive,
				'editable': this
			});
			PubSub.pub('aloha.editable.activated', {
				data: {
					old: oldActive,
					editable: this
				}
			});
		},

		/**
		 * handle the blur event
		 * this must not be attached to the blur event, which will trigger far too often
		 * eg. when a table within an editable is selected
		 * @hide
		 */
		blur: function () {
			this.obj.blur();
			this.isActive = false;
			this.initPlaceholder();
			this.obj.removeClass('aloha-editable-active');

			/**
			 * @event editableDeactivated fires after the editable has been activated by clicking on it.
			 * This event is triggered in Aloha's global scope Aloha
			 * @param {Event} e the event object
			 * @param {Array} a an array which contains a reference to this editable
			 */
			Aloha.trigger('aloha-editable-deactivated', {
				editable: this
			});
			PubSub.pub('aloha.editable.deactivated', {
				data: {
					editable: this
				}
			});

			/**
			 * @event smartContentChanged
			 */
			Aloha.activeEditable.smartContentChange({
				type: 'blur'
			}, null);
		},

		/**
		 * check if the string is empty
		 * used for zerowidth check
		 * @return true if empty or string is null, false otherwise
		 * @hide
		 */
		empty: function (str) {
			// br is needed for chrome
			return (null === str) || (jQuery.trim(str) === '' || str === '<br/>');
		},

		/**
		 * Get the contents of this editable as a HTML string or child node DOM
		 * objects.
		 *
		 * @param {boolean} asObject Whether or not to retreive the contents of
		 *                           this editable as child node objects or as
		 *                           HTML string.
		 * @return {string|jQuery.<HTMLElement>} Contents of the editable as
		 *                                       DOM objects or an HTML string.
		 */
		getContents: function (asObject) {
			var raw = this.obj.html();
			var cache = editableContentCache[this.getId()];

			if (!cache || raw !== cache.raw) {

				BlockJump.removeZeroWidthTextNodeFix();

				var $clone = this.obj.clone(false);
				this.removePlaceholder($clone);
				$clone = jQuery(Ephemera.prune($clone[0]));
				PluginManager.makeClean($clone);

				// TODO rewrite ContentHandlerManager to accept DOM trees instead of strings
				$clone = jQuery('<div>' + ContentHandlerManager.handleContent($clone.html(), {
					contenthandler: Aloha.settings.contentHandler.getContents,
					command: 'getContents'
				}, this) + '</div>');

				cache = editableContentCache[this.getId()] = {};
				cache.raw = raw;
				cache.element = $clone;
			}

			if (asObject) {
				return cache.element.clone().contents();
			}

			if (null == cache.serialized) {
				cache.serialized = contentSerializer(cache.element[0]);
			}
			return cache.serialized;
		},

		/**
		 * Set the contents of this editable as a HTML string
		 * @param content as html
		 * @param return as object or html string
		 * @return contents of the editable
		 */
		setContents: function (content, asObject) {
			var reactivate = null;

			if (Aloha.getActiveEditable() === this) {
				Aloha.deactivateEditable();
				reactivate = this;
			}

			this.obj.html(content);

			if (null !== reactivate) {
				reactivate.activate();
			}

			this.smartContentChange({
				type: 'set-contents'
			});

			return asObject ? this.obj.contents() : contentSerializer(this.obj[0]);
		},

		/**
		 * Get the id of this editable
		 * @method
		 * @return id of this editable
		 */
		getId: function () {
			return this.obj.attr('id');
		},

		/**
		 * Generates and signals a smartContentChange event.
		 *
		 * A smart content change occurs when a special editing action, or a
		 * combination of interactions are performed by the user during the
		 * course of editing within an editable.
		 * The smart content change event would therefore signal to any
		 * component that is listening to this event, that content has been
		 * inserted into the editable that may need to be prococessed in a
		 * special way
		 * This is used for smart actions within the content/while editing.
		 * @param {Event} event
		 * @hide
		 */
		smartContentChange: function (event) {
			var me = this,
				uniChar = null,
				re,
				match;

			// ignore meta keys like crtl+v or crtl+l and so on
			if (event && (event.metaKey || event.crtlKey || event.altKey)) {
				return false;
			}

			if (event && event.originalEvent) {
				// regex to strip unicode
				re = new RegExp("U\\+(\\w{4})");
				match = re.exec(event.originalEvent.keyIdentifier);

				// Use among browsers reliable which http://api.jquery.com/keypress
				uniChar = (this.keyCodeMap[this.keyCode] || String.fromCharCode(event.which) || 'unknown');
			}

			var snapshot = null;

			function getSnapshotContent() {
				if (null == snapshot) {
					snapshot = me.getSnapshotContent();
				}
				return snapshot;
			}

			// handle "Enter" -- it's not "U+1234" -- when returned via "event.originalEvent.keyIdentifier"
			// reference: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html
			if (jQuery.inArray(uniChar, this.sccDelimiters) >= 0) {
				clearTimeout(this.sccTimerIdle);
				clearTimeout(this.sccTimerDelay);

				this.sccTimerDelay = window.setTimeout(function () {
					Aloha.trigger('aloha-smart-content-changed', {
						'editable': me,
						'keyIdentifier': event.originalEvent.keyIdentifier,
						'keyCode': event.keyCode,
						'char': uniChar,
						'triggerType': 'keypress', // keypress, timer, blur, paste
						'getSnapshotContent': getSnapshotContent
					});
					handleSmartContentChange(me);

					console.debug('Aloha.Editable',
							'smartContentChanged: event type keypress triggered');
				}, this.sccDelay);

			} else if (event && event.type === 'paste') {
				Aloha.trigger('aloha-smart-content-changed', {
					'editable': me,
					'keyIdentifier': null,
					'keyCode': null,
					'char': null,
					'triggerType': 'paste',
					'getSnapshotContent': getSnapshotContent
				});
				handleSmartContentChange(me);

			} else if (event && event.type === 'blur') {
				Aloha.trigger('aloha-smart-content-changed', {
					'editable': me,
					'keyIdentifier': null,
					'keyCode': null,
					'char': null,
					'triggerType': 'blur',
					'getSnapshotContent': getSnapshotContent
				});
				handleSmartContentChange(me);

			} else if (event && event.type === 'block-change') {
				Aloha.trigger('aloha-smart-content-changed', {
					'editable': me,
					'keyIdentifier': null,
					'keyCode': null,
					'char': null,
					'triggerType': 'block-change',
					'getSnapshotContent': getSnapshotContent
				});
				handleSmartContentChange(me);

			} else if (uniChar !== null) {
				// in the rare case idle time is lower then delay time
				clearTimeout(this.sccTimerDelay);
				clearTimeout(this.sccTimerIdle);
				this.sccTimerIdle = window.setTimeout(function () {
					Aloha.trigger('aloha-smart-content-changed', {
						'editable': me,
						'keyIdentifier': null,
						'keyCode': null,
						'char': null,
						'triggerType': 'idle',
						'getSnapshotContent': getSnapshotContent
					});
					handleSmartContentChange(me);
				}, this.sccIdle);
			}
		},

		/**
		 * Get a snapshot of the active editable as a HTML string
		 * @hide
		 * @return snapshot of the editable
		 */
		getSnapshotContent: function () {
			var ret = this.snapshotContent;
			this.snapshotContent = this.getContents();
			return ret;
		}
	});

	/**
	 * Sets the content serializer function.
	 *
	 * The default content serializer will just call the jQuery.html()
	 * function on the editable element (which gets the innerHTML property).
	 *
	 * This method is a static class method and will affect the result
	 * of editable.getContents() for all editables that have been or
	 * will be constructed.
	 *
	 * @param {!Function} serializerFunction
	 *        A function that accepts a DOM element and returns the serialized
	 *        XHTML of the element contents (excluding the start and end tag of
	 *        the passed element).
	 * @api
	 */
	Aloha.Editable.setContentSerializer = function (serializerFunction) {
		contentSerializer = serializerFunction;
	};

	/**
	 * Gets the content serializer function.
	 *
	 * @see Aloha.Editable.setContentSerializer()
	 * @api
	 * @return {!Function}
	 *        The serializer function.
	 */
	Aloha.Editable.getContentSerializer = function () {
		return contentSerializer;
	};

	Aloha.Editable.registerEvents = registerEvents;

});

/* plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/plugin',[
	'aloha/core',
	'jquery',
	'util/class',
	'aloha/pluginmanager',
	'aloha/console'
], function (
	Aloha,
	jQuery,
	Class,
	PluginManager,
	console
) {
	

	/**
	 * Abstract Plugin Object
	 * @namespace Aloha
	 * @class Plugin
	 * @constructor
	 * @param {String} pluginPrefix unique plugin prefix
	 */
	var Plugin = Class.extend({

		name: null,

		/**
		 * contains the plugin's default settings object
		 * @cfg {Object} default settings for the plugin
		 */
		defaults: {},

		/**
		 * contains the plugin's settings object
		 * @cfg {Object} settings the plugins settings stored in an object
		 */
		settings: {},

		/**
		 * Names of other plugins which must be loaded in order for this plugin to
		 * function.
		 * @cfg {Array}
		 */
		dependencies: [],

		_constructor: function (name) {
			/**
			 * Settings of the plugin
			 */
			if (typeof name !== "string") {
				console.error('Cannot initialise unnamed plugin, skipping');
			} else {
				this.name = name;
			}
		},

		/**
		 * @return true if dependencies satisfied, false otherwise
		 */
		checkDependencies: function () {
			var plugin = this;
			var satisfied = true;
			jQuery.each(plugin.dependencies, function (i, dependency) {
				if (!Aloha.isPluginLoaded(dependency.toString())) {
					satisfied = false;
					console.error('plugin.' + plugin.name,
							'Required plugin "' + dependency + '" not found.');
					return false;
				}
			});
			return satisfied;
		},

		/**
		 * Init method of the plugin. Called from Aloha Core to initialize this plugin
		 * @return void
		 * @hide
		 */
		init: function () {},

		/**
		 * Get the configuration settings for an editable obj.
		 * Handles both conf arrays or conf objects
		 * <ul>
		 * <li>Array configuration parameters are:
		 * <pre>
		 * "list": {
		 *		config : [ 'b', 'h1' ],
		 *		editables : {
		 *			'#title'	: [ ],
		 *			'div'		: [ 'b', 'i' ],
		 *			'.article'	: [ 'h1' ]
		 *		}
		 *	}
		 * </pre>
		 *
		 * The hash keys of the editables are css selectors. For a
		 *
		 * <pre>
		 *  <div class="article">content</div>
		 * </pre>
		 *
		 *  the selectors 'div' and '.article' match and the returned configuration is
		 *
		 * <pre>
		 *  [ 'b', 'i', 'h1']
		 * </pre>
		 *
		 * The '#title' object would return an empty configuration.
		 *
		 * <pre>
		 *  [ ]
		 * </pre>
		 *
		 *  All other objects would get the 'config' configuration. If config is not set
		 * the plugin default configuration is returned.
		 *
		 * <pre>
		 *  [ 'b', 'h1']
		 * </pre></li>
		 * <li>Object configuration parameters are :
		 * <pre>
		 *	"image": {
		 *		config : { 'img': { 'max_width': '50px',
		 *		'max_height': '50px' }},
		 *		editables : {
		 *			'#title': {},
		 *			'div': {'img': {}},
		 *			'.article': {'img': { 'max_width': '150px',
		 *			'max_height': '150px' }}
		 *		}
		 *	}
		 * </pre>
		 *  The '#title' object would return an empty configuration.<br/>
		 *  The 'div' object would return the default configuration.<br/>
		 *  the '.article' would return :
		 *  <pre>
		 *		{'img': { 'max_width': '150px',
		 *		'max_height': '150px' }}
		 *  </pre>
		 * </li>
		 *
		 * @param {jQuery} obj jQuery object of an Editable Object
		 * @return {Array} config A Array with configuration entries
		 */
		getEditableConfig: function (obj) {
			var configObj = null,
				configSpecified = false,
				that = this;

			if (this.settings.editables) {
				// check if the editable's selector matches and if so add its configuration to object configuration
				jQuery.each(this.settings.editables, function (selector, selectorConfig) {
					var k;
					if (obj.is(selector)) {
						configSpecified = true;
						if (selectorConfig instanceof Array) {
							configObj = [];
							configObj = jQuery.merge(configObj, selectorConfig);
						} else if (typeof selectorConfig === "object") {
							configObj = {};
							configObj['aloha-editable-selector'] = selector;
							for (k in selectorConfig) {
								if (selectorConfig.hasOwnProperty(k)) {
									if (selectorConfig[k] instanceof Array) {
										//configObj[k] = [];
										//configObj[k] = jQuery.extend(true, configObj[k], that.config[k], selectorConfig[k]);
										configObj[k] = selectorConfig[k];
									} else if (typeof selectorConfig[k] === "object") {
										configObj[k] = {};
										configObj[k] = jQuery.extend(true, configObj[k], that.config[k], selectorConfig[k]);
									} else {
										configObj[k] = selectorConfig[k];
									}
								}
							}
						} else {
							configObj = selectorConfig;
						}
					}
				});
			}

			// fall back to default configuration
			if (!configSpecified) {
				if (typeof this.settings.config === 'undefined' || !this.settings.config) {
					configObj = this.config;
				} else {
					configObj = this.settings.config;
				}
			}

			return configObj;
		},

		/**
		 * Make the given jQuery object (representing an editable) clean for saving
		 * @param obj jQuery object to make clean
		 * @return void
		 */
		makeClean: function (obj) {},

		/**
		 * Make a system-wide unique id out of a plugin-wide unique id by prefixing it with the plugin prefix
		 * @param id plugin-wide unique id
		 * @return system-wide unique id
		 * @hide
		 * @deprecated
		 */
		getUID: function (id) {
			console.deprecated('plugin', 'getUID() is deprecated. Use plugin.name instead.');
			return this.name;
		},

		/**
		 * Return string representation of the plugin, which is the prefix
		 * @return name
		 * @hide
		 * @deprecated
		 */
		toString: function () {
			return this.name;
		},

		/**
		 * Log a plugin message to the logger
		 * @param level log level
		 * @param message log message
		 * @return void
		 * @hide
		 * @deprecated
		 */
		log: function (level, message) {
			console.deprecated('plugin', 'log() is deprecated. Use Aloha.console instead.');
			console.log(level, this, message);
		}
	});

	/**
	 * Static method used as factory to create plugins.
	 * 
	 * @param {String} pluginName name of the plugin
	 * @param {Object} definition definition of the plugin, should have at least an "init" and "destroy" method.
	 */
	Plugin.create = function (pluginName, definition) {

		var pluginInstance = new (Plugin.extend(definition))(pluginName);
		pluginInstance.settings = jQuery.extendObjects(true, pluginInstance.defaults, Aloha.settings[pluginName]);
		PluginManager.register(pluginInstance);

		return pluginInstance;
	};

	return Plugin;
});

/* jquery.aloha.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * IMPORTANT!
 * Don't add any more custom jquery extensions here.
 * Instead use the define(...) mechanism to define a module and to
 * import it where you need it.
 */
define('aloha/jquery.aloha',[
	'aloha/core',
	'aloha/selection',
	'jquery',
	'aloha/console'
], function (
	Aloha,
	Selection,
	jQuery,
	console
) {
	

	var XMLSerializer = window.XMLSerializer;

	/**
	 * jQuery between Extension
	 *
	 * insert either html code, a dom object OR a jQuery object inside of an existing text node.
	 * if the chained jQuery object is not a text node, nothing will happen.
	 *
	 * @param content HTML Code, DOM object or jQuery object to be inserted
	 * @param offset character offset from the start where the content should be inserted
	 */
	jQuery.fn.between = function (content, offset) {
		var offSize, fullText;

		if (this[0].nodeType !== 3) {
			// we are not in a text node, just insert the element at the corresponding position
			offSize = this.children().size();
			if (offset > offSize) {
				offset = offSize;
			}
			if (offset <= 0) {
				this.prepend(content);
			} else {
				this.children().eq(offset - 1).after(content);
			}
		} else {
			// we are in a text node so we have to split it at the correct position
			if (offset <= 0) {
				this.before(content);
			} else if (offset >= this[0].length) {
				this.after(content);
			} else {
				fullText = this[0].data;
				this[0].data = fullText.substring(0, offset);
				this.after(fullText.substring(offset, fullText.length));
				this.after(content);
			}
		}
	};

	/**
	 * Make the object contenteditable. Care about browser version (name of contenteditable attribute depends on it)
	 */
	jQuery.fn.contentEditable = function (b) {
		// ie does not understand contenteditable but contentEditable
		// contentEditable is not xhtml compatible.
		var $el = jQuery(this);
		var ce = 'contenteditable';

		// Check
		if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) == 7) {
			ce = 'contentEditable';
		}

		if (typeof b === 'undefined') {

			// For chrome use this specific attribute. The old ce will only
			// return 'inherit' for nested elements of a contenteditable.
			// The isContentEditable is a w3c standard compliant property which works in IE7,8,FF36+, Chrome 12+
			if (typeof $el[0] === 'undefined') {
				console.warn('The jquery object did not contain any valid elements.'); // die silent
				return undefined;
			}
			if (typeof $el[0].isContentEditable === 'undefined') {
				console.warn('Could not determine whether the is editable or not. I assume it is.');
				return true;
			}

			return $el[0].isContentEditable;
		}

		if (b === '') {
			$el.removeAttr(ce);
		} else {
			if (b && b !== 'false') {
				b = 'true';
			} else {
				b = 'false';
			}
			$el.attr(ce, b);
		}

		return $el;
	};

	/**
	 * jQuery Aloha Plugin.
	 *
	 * Makes the elements in a jQuery selection set Aloha editables.
	 *
	 * @return jQuery container of holding DOM elements that have been
	 *         aloha()fied.
	 * @api
	 */
	jQuery.fn.aloha = function () {
		var $elements = this;
		Aloha.bind('aloha-plugins-loaded', function () {
			$elements.each(function (_, elem) {
				if (!Aloha.isEditable(elem)) {
					new Aloha.Editable(jQuery(elem)).init();
				}
			});
		});
		return $elements;
	};

	/**
	 * jQuery destroy elements as editable
	 *
	 * destroy all mached elements editable capabilities
	 * @return	jQuery object for the matched elements
	 * @api
	 */
	jQuery.fn.mahalo = function () {
		return this.each(function () {
			if (Aloha.isEditable(this)) {
				Aloha.getEditableById(jQuery(this).attr('id')).destroy();
			}
		});
	};

	/**
	 * jQuery alohaText gets contents for an Aloha Editor editable
	 *
	 * getContents forall editable
	 * @return	jQuery object for the matched elements
	 * @api
	 */
	jQuery.fn.alohaText = function () {
		return this.each(function () {
			if (Aloha.isEditable(this)) {
				Aloha.getEditableById(jQuery(this).attr('id')).getContents();
			}
		});
	};

	/**
	 * jQuery Extension
	 * new Event which is triggered whenever a selection (length >= 0) is made in
	 * an Aloha Editable element
	 */
	jQuery.fn.contentEditableSelectionChange = function (callback) {
		var that = this;

		// update selection when keys are pressed
		this.keyup(function (event) {
			var rangeObject = Selection.getRangeObject();
			callback(event);
		});

		// update selection on doubleclick (especially important for the first automatic selection, when the Editable is not active yet, but is at the same time activated as the selection occurs
		this.dblclick(function (event) {
			callback(event);
		});

		// update selection when text is selected
		this.mousedown(function (event) {
			// remember that a selection was started
			that.selectionStarted = true;
		});

		jQuery(document).mouseup(function (event) {
			Selection.eventOriginalTarget = that;
			if (that.selectionStarted) {
				callback(event);
			}
			Selection.eventOriginalTarget = false;
			that.selectionStarted = false;
		});

		return this;
	};

	/**
	 * Fetch the outerHTML of an Element
	 * @version 1.0.0
	 * @date February 01, 2011
	 * @package jquery-sparkle {@link http://www.balupton/projects/jquery-sparkle}
	 * @author Benjamin Arthur Lupton {@link http://balupton.com}
	 * @copyright 2011 Benjamin Arthur Lupton {@link http://balupton.com}
	 * @license MIT License {@link http://creativecommons.org/licenses/MIT/}
	 * @return {String} outerHtml
	 */
	jQuery.fn.outerHtml = jQuery.fn.outerHtml || function () {
		var $el = jQuery(this),
			el = $el.get(0);
		if (typeof el.outerHTML != 'undefined') {
			return el.outerHTML;
		}
		try {
			// Gecko-based browsers, Safari, Opera.
			return (new XMLSerializer()).serializeToString(el);
		} catch (e) {
			try {
				// Internet Explorer.
				return el.xml;
			} catch (e2) {}
		}
	};

	jQuery.fn.zap = function () {
		return this.each(function () {
			jQuery(this.childNodes).insertBefore(this);
		}).remove();
	};

	jQuery.fn.textNodes = function (excludeBreaks, includeEmptyTextNodes) {
		var ret = [],
			doSomething = function (el) {
				var i, childLength;
				if ((el.nodeType === 3 && jQuery.trim(el.data) && !includeEmptyTextNodes) || (el.nodeType === 3 && includeEmptyTextNodes) || (el.nodeName == "BR" && !excludeBreaks)) {
					ret.push(el);
				} else {
					for (i = 0, childLength = el.childNodes.length; i < childLength; ++i) {
						doSomething(el.childNodes[i]);
					}
				}
			};

		doSomething(this[0]);

		return jQuery(ret);
	};

	/**
	 * extendObjects is like jQuery.extend, but it does not extend arrays
	 */
	jQuery.extendObjects = jQuery.fn.extendObjects = function (arg1, arg2) {
		var options, name, src, copy, copyIsArray, clone,
		    start = 1,
		    target = arg1 || {},
			length = arguments.length,
		    deep = false,
		    i;


		// Handle a deep copy situation
		if (typeof target === "boolean") {
			deep = target;
			target = arg2 || {};
			// skip the boolean and the target
			start = 2;
		}

		// Handle case when target is a string or something (possible in deep copy)
		if (typeof target !== "object" && !jQuery.isFunction(target)) {
			target = {};
		}

		// extend jQuery itself if only one argument is passed
		if (length === start) {
			target = this;
			--start;
		}

		for (i = start; i < length; i++) {
			// Only deal with non-null/undefined values
			if ((options = arguments[i]) != null) {
				// Extend the base object
				for (name in options) {
					if (options.hasOwnProperty(name)) {

						src = target[name];
						copy = options[name];

						// Prevent never-ending loop
						if (target === copy) {
							continue;
						}

						// Recurse if we're merging plain objects or arrays
						if (deep && copy && (jQuery.isPlainObject(copy) || true === (copyIsArray = jQuery.isArray(copy)))) {
							if (copyIsArray) {
								copyIsArray = false;
								clone = src && jQuery.isArray(src) ? src : [];

							} else {
								clone = src && jQuery.isPlainObject(src) ? src : {};
							}

							// Never move original objects, clone them
							if (jQuery.isArray(copy)) {
								// don't extend arrays
								target[name] = copy;
							} else {
								target[name] = jQuery.extendObjects(deep, clone, copy);
							}

							// Don't bring in undefined values
						} else if (copy !== undefined) {
							target[name] = copy;
						}
					}
				}
			}
		}

		// Return the modified object
		return target;
	};

	/*
	 * jQuery Hotkeys Plugin
	 * Copyright 2010, John Resig
	 * Dual licensed under the MIT or GPL Version 2 licenses.
	 *
	 * Based upon the plugin by Tzury Bar Yochay:
	 * http://github.com/tzuryby/hotkeys
	 *
	 * Original idea by:
	 * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
	 */

	jQuery.hotkeys = {
		version: "0.8",

		specialKeys: {
			8: "backspace",
			9: "tab",
			13: "return",
			16: "shift",
			17: "ctrl",
			18: "alt",
			19: "pause",
			20: "capslock",
			27: "esc",
			32: "space",
			33: "pageup",
			34: "pagedown",
			35: "end",
			36: "home",
			37: "left",
			38: "up",
			39: "right",
			40: "down",
			45: "insert",
			46: "del",
			96: "0",
			97: "1",
			98: "2",
			99: "3",
			100: "4",
			101: "5",
			102: "6",
			103: "7",
			104: "8",
			105: "9",
			106: "*",
			107: "+",
			109: "-",
			110: ".",
			111: "/",
			112: "f1",
			113: "f2",
			114: "f3",
			115: "f4",
			116: "f5",
			117: "f6",
			118: "f7",
			119: "f8",
			120: "f9",
			121: "f10",
			122: "f11",
			123: "f12",
			144: "numlock",
			145: "scroll",
			188: ",",
			190: ".",
			191: "/",
			224: "meta"
		},

		shiftNums: {
			"`": "~",
			"1": "!",
			"2": "@",
			"3": "#",
			"4": "$",
			"5": "%",
			"6": "^",
			"7": "&",
			"8": "*",
			"9": "(",
			"0": ")",
			"-": "_",
			"=": "+",
			";": ": ",
			"'": "\"",
			",": "<",
			".": ">",
			"/": "?",
			"\\": "|"
		}
	};

	function applyKeyHandler(handler, context, args, event) {
		// Don't fire in text-accepting inputs that we didn't directly bind to
		if (context !== event.target && (/textarea|input|select/i.test(event.target.nodeName) || event.target.type === "text")) {
			return;
		}
		return handler.apply(context, args);
	}

	function keyHandler(handleObj) {
		var origHandler, keys, handle, i;

		// Only care when a possible input has been specified
		if (typeof handleObj.data !== "string") {
			return;
		}

		origHandler = handleObj.handler;
		keys = handleObj.data.toLowerCase().split(" ");
		handle = {};

		for (i = 0; i < keys.length; i++) {
			handle[keys[i]] = true;
		}

		handleObj.handler = function (event) {
			// The original comment that was added with this condition says:
			// "Don't fire in contentEditable true elements"
			// But this is incorrect.
			// What this condition does is it skips hotkey events for
			// any target unless it is directly bound.
			// The condition event.target.contentEditable !== true will
			// always be true, because contentEditable is a string
			// attribute that is never strictly equal true.
			//if (this !== event.target && event.target.contentEditable !== true) {
			//return;
			//}
			// Below is what this condition really does. Ideally, I'd
			// like to remove this condition since it was not there in
			// the original implementation by John Resig and it could
			// interfere with other plugins, but when I removed it, I
			// was unable to input any space characters into an
			// editable.
			// TODO figure out a way to safely remove this
			if (this !== event.target) {
				return;
			}

			// Keypress represents characters, not special keys
			var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
				modif = "",
				character;

			// check combinations (alt|ctrl|shift+anything)
			if (event.altKey && special !== "alt") {
				modif += "alt+";
			}

			if (event.ctrlKey && special !== "ctrl") {
				modif += "ctrl+";
			}

			// TODO: Need to make sure this works consistently across platforms
			if (event.metaKey && !event.ctrlKey && special !== "meta") {
				modif += "meta+";
			}

			if (event.shiftKey && special !== "shift") {
				modif += "shift+";
			}

			if (special) {
				if (handle[modif + special]) {
					return applyKeyHandler(origHandler, this, arguments, event);
				}
			} else {
				character = String.fromCharCode(event.which).toLowerCase();

				if (handle[modif + character]) {
					return applyKeyHandler(origHandler, this, arguments, event);
				}

				if (handle[modif + jQuery.hotkeys.shiftNums[character]]) {
					return applyKeyHandler(origHandler, this, arguments, event);
				}

				// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
				if (modif === "shift+") {
					if (handle[jQuery.hotkeys.shiftNums[character]]) {
						return applyKeyHandler(origHandler, this, arguments, event);
					}
				}
			}
		};
	}

	jQuery.each(['keydown', 'keyup', 'keypress'], function () {
		jQuery.event.special[this] = {
			add: keyHandler
		};
	});

});

/* sidebar.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @todo: - Make the sidebars resizable using drag handles.
 *        - Make overlayPage setting settable from external config.
 */

define('aloha/sidebar',[
	'aloha/core',
	'jquery',
	'aloha/selection',
	'PubSub'
], function (
	Aloha,
	$,
	Selection,
	PubSub
) {
	

	var uid = +(new Date());

	// Extend jQuery easing animations.
	//debugger;
	if (!$.easing.easeOutExpo) {
		$.extend($.easing, {
			easeOutExpo: function (x, t, b, c, d) {
				return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
			},
			easeOutElastic: function (x, t, b, c, d) {
				var m = Math,
					s = 1.70158,
					p = 0,
					a = c;
				if (!t) {
					return b;
				}
				if ((t /= d) == 1) {
					return b + c;
				}
				if (!p) {
					p = d * 0.3;
				}
				if (a < m.abs(c)) {
					a = c;
					s = p / 4;
				} else {
					s = p / (2 * m.PI) * m.asin(c / a);
				}
				return a * m.pow(2, -10 * t) * m.sin((t * d - s) * (2 * m.PI) / p) + c + b;
			}
		});
	}

	var Panel = function Panel(opts) {
		this.id = null;
		this.folds = {};
		this.button = null;
		this.title = $('<div class="aloha-sidebar-panel-title">' + '<span class="aloha-sidebar-panel-title-arrow"></span>' + '<span class="aloha-sidebar-panel-title-text">Untitled</span>' + '</div>');
		this.content = $('<div class="aloha-sidebar-panel-content">' + '<div class="aloha-sidebar-panel-content-inner">' + '<div class="aloha-sidebar-panel-content-inner-text"></div>' + '</div>' + '</div>');
		this.element = null;
		this.effectiveElement = null;
		this.expanded = false;
		this.isActive = true;
		this.init(opts);
	};

	var Sidebar = function Sidebar(opts) {
		var sidebar = this;
		this.id = 'aloha-sidebar-' + (++uid);
		this.panels = {};
		this.container = $('<div class="aloha-ui aloha-sidebar-bar">' + '<div class="aloha-sidebar-handle">' + '<span class="aloha-sidebar-handle-icon"></span>' + '</div>' + '<div class="aloha-sidebar-inner">' + '<ul class="aloha-sidebar-panels"></ul>' + '</div>' + '</div>');
		this.width = 300;
		this.opened = false;
		this.isOpen = false;
		this.isCompletelyOpen = false;
		this.settings = {
			// We automatically set this to true when we are in IE, where
			// rotating elements using filters causes undesirable rendering
			// ugliness.  Our solution is to fallback to swapping icon images.
			// We set this as a sidebar property so that it can overridden by
			// whoever thinks they are smarter than we are.
			rotateIcons: !$.browser.msie,
			overlayPage: true
		};

		$(function () {
			if (!((typeof Aloha.settings.sidebar !== 'undefined') && Aloha.settings.sidebar.disabled)) {
				sidebar.init(opts);
			}
		});
	};

	/**
	 * The last calculated view port height.
	 * @type {number}
	 */
	var previousViewportHeight = null;
	var previousActivePanelIds = null;

	$.extend(Sidebar.prototype, {

		// We build as much of the sidebar as we can before appending it to DOM
		// to minimize reflow.
		init: function (opts) {
			var that = this;
			var panels;

			if (typeof opts === 'object') {
				panels = opts.panels;
				delete opts.panels;
			}

			$.extend(this, opts);

			if (typeof panels === 'object') {
				$.each(panels, function () {
					that.addPanel(this, true);
				});
			}

			var bar = this.container;

			if (this.position === 'right') {
				bar.addClass('aloha-sidebar-right');
			}

			bar.hide().appendTo($('body')).click(function () {
				that.barClicked.apply(that, arguments);
			}).find('.aloha-sidebar-panels').width(this.width);

			// IE7 needs us to explicitly set the container width, since it is
			// unable to determine it on its own.
			bar.width(this.width);
			this.width = bar.width();

			this.updateHeight();
			this.initToggler();

			this.container.css(this.position === 'right' ? 'marginRight' : 'marginLeft', -this.width);

			if (this.opened) {
				this.open(0);
			}

			this.toggleHandleIcon(this.isOpen);
			this.subscribeToEvents();

			$(window).resize(function () {
				that.updateHeight();
				that.correctHeight();
			});

			this.correctHeight();
		},

		show: function () {
			this.container.css('display', 'block');
			return this;
		},

		hide: function () {
			this.container.css('display', 'none');
			return this;
		},

		/**
		 * Determines the effective elements at the current selection.
		 * Iterates through all panels and checks whether the panel should be
		 * activated for any of the effective elements in the selection.
		 *
		 * @param {Aloha.RangeObject} range The current selection range.
		 */
		checkActivePanels: function (range) {
			var effective = [];

			if (typeof range !== 'undefined' && typeof range.markupEffectiveAtStart !== 'undefined') {
				var l = range.markupEffectiveAtStart.length;
				var i;
				for (i = 0; i < l; ++i) {
					effective.push($(range.markupEffectiveAtStart[i]));
				}
			}

			var that = this;

			$.each(this.panels, function () {
				that.showActivePanel(this, effective);
			});

			this.correctHeight();
		},

		subscribeToEvents: function () {
			var that = this;

			PubSub.sub('aloha.selection.context-change', function (message) {
				if (that.isOpen) {
					that.checkActivePanels(message.range);
				}
				that.lastRange = message.range;
			});

			Aloha.bind('aloha-editable-deactivated', function (event, params) {
				if (that.isOpen) {
					that.checkActivePanels();
				}
				that.lastRange = null;
			});

			this.container.mousedown(function (e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = true;
			});

			this.container.mouseup(function (e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = false;
			});
		},

		/**
		 * Dynamically set appropriate heights for panels.
		 * The height for each panel is determined by the amount of space that
		 * is available in the viewport and the number of panels that need to
		 * share that space.
		 */
		correctHeight: function () {
			if (!this.isOpen) {
				return;
			}
			if (!this.isCompletelyOpen) {
				this.correctHeightWhenCompletelyOpen = true;
				return;
			}

			var viewportHeight = $(window).height();
			var activePanelIds = [];
			var panels = [];
			var panelId;
			for (panelId in this.panels) {
				if (this.panels.hasOwnProperty(panelId)) {
					if (this.panels[panelId].isActive) {
						panels.push(this.panels[panelId]);
						activePanelIds.push(panelId);
					}
				}
			}

			if (0 === panels.length) {
				return;
			}

			activePanelIds = activePanelIds.sort().join(',');

			if (previousActivePanelIds === activePanelIds && previousViewportHeight === viewportHeight) {
				return;
			}

			previousViewportHeight = viewportHeight;
			previousActivePanelIds = activePanelIds;

			var height = this.container.find('.aloha-sidebar-inner').height();
			var remainingHeight = height - ((panels[0].title.outerHeight() + 10) * panels.length);
			var panel;
			var targetHeight;
			var panelInner;
			var panelText;
			var undone;
			var toadd = 0;
			var math = Math;
			var j;

			while (panels.length > 0 && remainingHeight > 0) {
				remainingHeight += toadd;
				toadd = 0;
				undone = [];

				for (j = panels.length - 1; j >= 0; --j) {
					panel = panels[j];
					panelInner = panel.content.find('.aloha-sidebar-panel-content-inner');

					targetHeight = math.min(
						panelInner.height('auto').height(),
						math.floor(remainingHeight / (j + 1))
					);

					panelInner.height(targetHeight);
					remainingHeight -= targetHeight;
					panelText = panelInner.find('.aloha-sidebar-panel-content-inner-text');

					if (panelText.height() > targetHeight) {
						undone.push(panel);
						toadd += targetHeight;
						panelInner.css({
							'overflow-x': 'hidden',
							'overflow-y': 'scroll'
						});
					} else {
						panelInner.css('overflow-y', 'hidden');
					}

					if (panel.expanded) {
						panel.expand();
					}
				}

				panels = undone;
			}
		},

		/**
		 * Checks whether this panel should be activated (ie: made visible) for
		 * any of the elements specified in a given list of elements.
		 *
		 * We have to add a null object to the list of elements to allow us to
		 * check whether the panel should be visible when we have no effective
		 * elements in the current selection
		 *
		 * @param {object} panel The Panel object we will test
		 * @param {Array.<jQuery.<HTMLElement>>} elements The effective
		 *                                                elements, any of
		 *                                                which may activate
		 *                                                the panel.
		 */
		showActivePanel: function (panel, elements) {
			elements.push(null);

			var li = panel.content.parent('li');
			var activeOn = panel.activeOn;
			var effective = $();
			var count = 0;
			var j = elements.length;
			var i;

			for (i = 0; i < j; ++i) {
				if (activeOn(elements[i])) {
					++count;
					if (elements[i]) {
						$.merge(effective, elements[i]);
					}
				}
			}

			if (count) {
				panel.activate(effective);
			} else {
				panel.deactivate();
			}
		},

		/**
		 * Sets up the functionality, event listeners, and animation of the
		 * sidebar handle
		 */
		initToggler: function () {
			var that = this;
			var bar = this.container;
			var icon = bar.find('.aloha-sidebar-handle-icon');
			var toggledClass = 'aloha-sidebar-toggled';
			var bounceTimer;
			var isRight = (this.position === 'right');

			if (this.opened) {
				this.rotateHandleArrow(isRight ? 0 : 180, 0);
			}

			// configure the position of the sidebar handle
			$(function () {
				if (typeof Aloha.settings.sidebar !== 'undefined' && Aloha.settings.sidebar.handle && Aloha.settings.sidebar.handle.top) {
					$(bar.find('.aloha-sidebar-handle'))[0].style.top = Aloha.settings.sidebar.handle.top;
				}
			});

			bar.find('.aloha-sidebar-handle').click(function () {
				if (bounceTimer) {
					clearInterval(bounceTimer);
				}

				icon.stop().css('marginLeft', 4);

				if (that.isOpen) {
					$(this).removeClass(toggledClass);
					that.close();
					that.isOpen = false;
				} else {
					$(this).addClass(toggledClass);
					that.open();
					that.isOpen = true;
				}
			}).hover(function () {
				var flag = that.isOpen ? -1 : 1;

				if (bounceTimer) {
					clearInterval(bounceTimer);
				}

				icon.stop();

				$(this).stop().animate(
					isRight ? {
						marginLeft: '-=' + (flag * 5)
					} : {
						marginRight: '-=' + (flag * 5)
					},
					200
				);

				bounceTimer = setInterval(function () {
					flag *= -1;
					icon.animate(
						isRight ? {
							left: '-=' + (flag * 4)
						} : {
							right: '-=' + (flag * 4)
						},
						300
					);
				}, 300);
			}, function () {
				if (bounceTimer) {
					clearInterval(bounceTimer);
				}

				icon.stop().css(isRight ? 'left' : 'right', 5);

				$(this).stop().animate(
					isRight ? {
						marginLeft: 0
					} : {
						marginRight: 0
					},
					600,
					'easeOutElastic'
				);
			});
		},

		/**
		 * Rounds the top corners of the first visible panel, and the bottom
		 * corners of the last visible panel elements in the panels ul list.
		 * @deprecated
		 * @fixme: css3
		 */
		roundCorners: function () {

			var bar = this.container;
			var lis = bar.find('.aloha-sidebar-panels>li:not(.aloha-sidebar-deactivated)');
			var topClass = 'aloha-sidebar-panel-top';
			var bottomClass = 'aloha-sidebar-panel-bottom';

			bar.find('.aloha-sidebar-panel-top, .aloha-sidebar-panel-bottom').removeClass(topClass).removeClass(bottomClass);

			lis.first().find('.aloha-sidebar-panel-title').addClass(topClass);
			lis.last().find('.aloha-sidebar-panel-content').addClass(bottomClass);
		},

		/**
		 * Updates the height of the inner div of the sidebar. This is done
		 * whenever the viewport is resized.
		 */
		updateHeight: function () {
			var h = $(window).height();
			this.container.height(h).find('.aloha-sidebar-inner').height(h);
		},

		/**
		 * Delegate all sidebar onclick events to the container.
		 * Then use handleBarclick method until we bubble up to the first
		 * significant element that we can interact with.
		 */
		barClicked: function (ev) {
			this.handleBarclick($(ev.target));
		},

		/**
		 * We handle all click events on the sidebar from here--dispatching
		 * calls to which ever methods that should be invoked for the each
		 * interaction.
		 */
		handleBarclick: function (el) {
			if (el.hasClass('aloha-sidebar-panel-title')) {
				this.togglePanel(el);
			} else if (!el.hasClass('aloha-sidebar-panel-content') && !el.hasClass('aloha-sidebar-handle') && !el.hasClass('aloha-sidebar-bar')) {
				this.handleBarclick(el.parent());
			}
		},

		getPanelById: function (id) {
			return this.panels[id];
		},

		getPanelByElement: function (el) {
			var li = (el[0].tagName === 'LI') ? el : el.parent('li');
			return this.getPanelById(li[0].id);
		},

		togglePanel: function (el) {
			this.getPanelByElement(el).toggle();
		},

		/**
		 * Animation to rotate the sidebar arrow
		 *
		 * @param {number} angle The angle two which the arrow should rotate
		 *						 (0 or 180).
		 * @param {number|String} duration (Optional) How long the animation
		 *                                 should play for.
		 */
		rotateHandleIcon: function (angle, duration) {
			var arr = this.container.find('.aloha-sidebar-handle-icon');
			arr.animate({
				angle: angle
			}, {
				duration: (typeof duration === 'number' || typeof duration === 'string') ? duration : 500,
				easing: 'easeOutExpo',
				step: function (val, fx) {
					arr.css({
						'-o-transform': 'rotate(' + val + 'deg)',
						'-webkit-transform': 'rotate(' + val + 'deg)',
						'-moz-transform': 'rotate(' + val + 'deg)',
						'-ms-transform': 'rotate(' + val + 'deg)'
						// We cannot use Microsoft Internet Explorer filters
						// because Microsoft Internet Explore 8 does not support
						// Microsoft Internet Explorer filters correctly. It
						// breaks the layout
						// filter             : 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (angle / 90) + ')'
					});
				}
			});
		},

		/**
		 * Sets the handle icon to the "i am opened, click me to close the
		 * sidebar" state, or vice versa. The direction of the arrow depends
		 * on whether the sidebar is on the left or right, and whether it is
		 * in an opened state or not.
		 *
		 * @param {boolean} isOpen Whether or not the sidebar is in the opened
		 *                         state.
		 */
		toggleHandleIcon: function (isOpen) {
			var isPointingLeft = (this.position === 'right') ^ isOpen;

			if (this.settings.rotateIcons) {
				this.rotateHandleIcon(isPointingLeft ? 180 : 0, 0);
			} else {
				var icon = this.container.find('.aloha-sidebar-handle-icon');

				if (isPointingLeft) {
					icon.addClass('aloha-sidebar-handle-icon-left');
				} else {
					icon.removeClass('aloha-sidebar-handle-icon-left');
				}
			}
		},

		/**
		 * Slides the sidebar into view
		 */
		open: function (duration, callback) {
			if (this.isOpen) {
				return this;
			}

			var isRight = (this.position === 'right');
			var anim = isRight ? {
				marginRight: 0
			} : {
				marginLeft: 0
			};
			var sidebar = this;

			this.toggleHandleIcon(true);
			this.container.animate(anim, (typeof duration === 'number' || typeof duration === 'string') ? duration : 500, 'easeOutExpo');

			if (!this.settings.overlayPage) {
				$('body').animate(
					isRight ? {
						marginRight: '+=' + this.width
					} : {
						marginLeft: '+=' + this.width
					},
					500,
					'easeOutExpo',
					function () {
						sidebar.isCompletelyOpen = true;
						if (sidebar.correctHeightWhenCompletelyOpen) {
							sidebar.correctHeight();
						}
					}
				);
			}

			this.isOpen = true;
			this.correctHeight();
			if (this.lastRange) {
				this.checkActivePanels(this.lastRange);
			}
			$('body').trigger('aloha-sidebar-opened', this);

			return this;
		},

		/**
		 * Slides that sidebar out of view.
		 */
		close: function (duration, callback) {
			if (!this.isOpen) {
				return this;
			}

			var isRight = (this.position === 'right');
			var anim = isRight ? {
				marginRight: -this.width
			} : {
				marginLeft: -this.width
			};

			this.toggleHandleIcon(false);
			this.container.animate(anim, (typeof duration === 'number' || typeof duration === 'string') ? duration : 500, 'easeOutExpo');

			if (!this.settings.overlayPage) {
				$('body').animate(
					isRight ? {
						marginRight: '-=' + this.width
					} : {
						marginLeft: '-=' + this.width
					},
					500,
					'easeOutExpo'
				);
			}

			this.isOpen = false;
			this.isCompletelyOpen = false;

			return this;
		},

		/**
		 * Activates the given panel and passes to it the given element as the
		 * the effective that we want it to think activated it.
		 *
		 * @param {object|String} panel Panel instance or the id of a panel
		 *								object.
		 * @param {jQuery} element Element to pass to the panel as effective
		 *	                       element (the element that activated it).
		 */
		activatePanel: function (panel, element) {
			if (typeof panel === 'string') {
				panel = this.getPanelById(panel);
			}

			if (panel) {
				panel.activate(element);
			}

			this.roundCorners();

			return this;
		},

		/**
		 * Invokes the expand method for the given panel so that it expands its
		 * height to display its contents
		 *
		 * @param {object|String} panel Panel instance or the id of a panel
		 *                              object.
		 * @param {funtion} callback
		 */
		expandPanel: function (panel, callback) {
			if (typeof panel === 'string') {
				panel = this.getPanelById(panel);
			}

			if (panel) {
				panel.expand(callback);
			}

			return this;
		},

		/**
		 * Collapses the panel contents by invoking the given panel's collapse
		 * method.
		 *
		 * @param {object|String} panel Panel instance or the id of a panel
		 *								object.
		 * @param {funtion} callback
		 */
		collapsePanel: function (panel, callback) {
			if (typeof panel === 'string') {
				panel = this.getPanelById(panel);
			}

			if (panel) {
				panel.collapse(callback);
			}

			return this;
		},

		/**
		 * Adds a panel to this sidebar instance.
		 * We try and build as much of the panel DOM as we can before inserting
		 * it into the DOM in order to reduce reflow.
		 *
		 * @param {object} panel - either a panel instance or an associative
		 *			   array containing settings for the construction
		 *			   of a new panel.
		 * @param {boolean} deferRounding - (Optional) If true, the rounding-off
		 *				    of the top most and bottom most panels
		 *				    will not be automatically done. Set
		 *				    this to true when adding a lot of panels
		 *				    at once.
		 * @return {object} The newly created panel.
		 */
		addPanel: function (panel, deferRounding) {
			if (!(panel instanceof Panel)) {
				if (!panel.width) {
					panel.width = this.width;
				}
				panel.sidebar = this;
				panel = new Panel(panel);
			}

			this.panels[panel.id] = panel;
			this.container.find('.aloha-sidebar-panels').append(panel.element);
			this.checkActivePanels(Selection.getRangeObject());
			return panel;
		}

	});

	// ------------------------------------------------------------------------
	// Panel prototype
	// ------------------------------------------------------------------------
	$.extend(Panel.prototype, {

		init: function (opts) {
			this.setTitle(opts.title).setContent(opts.content);

			delete opts.title;
			delete opts.content;

			$.extend(this, opts);

			if (!this.id) {
				this.id = 'aloha-sidebar-' + (++uid);
			}

			var li = this.element = $('<li id="' + this.id + '">').append(this.title, this.content);

			if (this.expanded) {
				this.content.height('auto');
			}

			this.toggleTitleIcon(this.expanded);
			this.coerceActiveOn();

			// Disable text selection on title element.
			this.title.attr('unselectable', 'on').css('-moz-user-select', 'none').each(function () {
				this.onselectstart = function () {
					return false;
				};
			});

			if (typeof this.onInit === 'function') {
				this.onInit.apply(this);
			}
		},

		/**
		 * @param {boolean} isExpanded Whether or not the panel is in an
		 *                             expanded state.
		 */
		toggleTitleIcon: function (isExpanded) {
			if (this.sidebar.settings.rotateIcons) {
				this.rotateTitleIcon(isExpanded ? 90 : 0);
			} else {
				var icon = this.title.find('.aloha-sidebar-panel-title-arrow');

				if (isExpanded) {
					icon.addClass('aloha-sidebar-panel-title-arrow-down');
				} else {
					icon.removeClass('aloha-sidebar-panel-title-arrow-down');
				}
			}
		},

		/**
		 * Normalizes the activeOn property into a predicate function.
		 */
		coerceActiveOn: function () {
			if (typeof this.activeOn !== 'function') {
				var activeOn = this.activeOn;

				this.activeOn = (function () {
					var typeofActiveOn = typeof activeOn,
						fn;

					if (typeofActiveOn === 'boolean') {
						fn = function () {
							return activeOn;
						};
					} else if (typeofActiveOn === 'undefined') {
						fn = function () {
							return true;
						};
					} else if (typeofActiveOn === 'string') {
						fn = function (el) {
							return el ? el.is(activeOn) : false;
						};
					} else {
						fn = function () {
							return false;
						};
					}

					return fn;
				}());
			}
		},

		/**
		 * Activates (displays) this panel.
		 */
		activate: function (effective) {
			this.isActive = true;
			this.content.parent('li').show().removeClass('aloha-sidebar-deactivated');
			this.effectiveElement = effective;
			if (typeof this.onActivate === 'function') {
				this.onActivate.call(this, effective);
			}
		},

		/**
		 * Hides this panel.
		 */
		deactivate: function () {
			if (!this.isActive) {
				return;
			}
			this.isActive = false;
			this.content.parent('li').hide().addClass('aloha-sidebar-deactivated');
			this.effectiveElement = null;
		},

		toggle: function () {
			if (this.expanded) {
				this.collapse();
			} else {
				this.expand();
			}
		},

		/**
		 * Displays the panel's contents.
		 */
		expand: function (callback) {
			var that = this;
			var el = this.content;
			var old_h = el.height();
			var new_h = el.height('auto').height();
			el.height(old_h).stop().animate(
				{height: new_h},
				500,
				'easeOutExpo',
				function () {
					if (typeof callback === 'function') {
						callback.call(that);
					}
				}
			);
			this.element.removeClass('collapsed');
			this.toggleTitleIcon(true);
			this.expanded = true;
			return this;
		},

		/**
		 * Hides the panel's contents--leaving only it's header.
		 */
		collapse: function (duration, callback) {
			var that = this;
			this.element.addClass('collapsed');
			this.content.stop().animate(
				{ height: 3 },
				250,
				'easeOutExpo',
				function () {
					if (typeof callback === 'function') {
						callback.call(that);
					}
				}
			);
			this.toggleTitleIcon(false);
			this.expanded = false;
			return this;
		},

		/**
		 * May also be called by the Sidebar to update title of panel
		 *
		 * @param {string} html Markup string, DOM object, or jQuery object.
		 */
		setTitle: function (html) {
			this.title.find('.aloha-sidebar-panel-title-text').html(html);
			return this;
		},

		/**
		 * May also be called by the Sidebar to update content of panel
		 *
		 * @param {string|jQuery.<HTMLElement>|HTMLElement} html Markup string,
		 *                                                       DOM object, or
		 *                                                       jQuery object.
		 */
		setContent: function (html) {
			// We do this so that empty panels don't appear collapsed
			if (!html || html === '') {
				html = '&nbsp;';
			}

			this.content.find('.aloha-sidebar-panel-content-inner-text').html(html);
			return this;
		},

		rotateTitleIcon: function (angle, duration) {
			var arr = this.title.find('.aloha-sidebar-panel-title-arrow');
			arr.animate({
				angle: angle
			}, {
				duration: (typeof duration === 'number') ? duration : 500,
				easing: 'easeOutExpo',
				step: function (val, fx) {
					arr.css({
						'-o-transform': 'rotate(' + val + 'deg)',
						'-webkit-transform': 'rotate(' + val + 'deg)',
						'-moz-transform': 'rotate(' + val + 'deg)',
						'-ms-transform': 'rotate(' + val + 'deg)'
						// filter              : 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (angle / 90) + ')'
					});
				}
			});
		},

		/**
		 * Walks up the ancestors chain for the given effective element, and
		 * renders subpanels using the specified renderer function.
		 *
		 * @param {jQuery.<HTMLElement>} effective The effective element, whose
		 *                                         lineage we want to render.
		 * @param {function} renderer (Optional) function that will render each
		 *                                       element in the parental
		 *                                       lineage of the effective
		 *                                       element.
		 */
		renderEffectiveParents: function (effective, renderer) {
			var el = effective.first();
			var content = [];
			var path = [];
			var activeOn = this.activeOn;
			var l;
			var pathRev;

			while (el.length > 0 && !el.is('.aloha-editable')) {
				if (activeOn(el)) {
					path.push('<span>' + el[0].tagName.toLowerCase() + '</span>');
					l = path.length;
					pathRev = [];
					while (l--) {
						pathRev.push(path[l]);
					}
					content.push(
						'<div class="aloha-sidebar-panel-parent">'
							+ '<div class="aloha-sidebar-panel-parent-path">'
							+ pathRev.join('')
							+ '</div>'
							+ '<div class="aloha-sidebar-panel-parent-content'
							+ 'aloha-sidebar-opened">'
							+ ((typeof renderer === 'function') ? renderer(el) : '----') + '</div>' + '</div>'
					);
				}
				el = el.parent();
			}

			this.setContent(content.join(''));

			$('.aloha-sidebar-panel-parent-path').click(function () {
				var $content = $(this).parent().find('.aloha-sidebar-panel-parent-content');
				if ($content.hasClass('aloha-sidebar-opened')) {
					$content.hide().removeClass('aloha-sidebar-opened');
				} else {
					$content.show().addClass('aloha-sidebar-opened');
				}
			});

			this.content.height('auto').find('.aloha-sidebar-panel-content-inner').height('auto');
		}

	});

	var left = new Sidebar({
		position: 'left',
		width: 250 // TODO define in config
	});

	var right = new Sidebar({
		position: 'right',
		width: 250 // TODO define in config
	});

	Aloha.Sidebar = {
		left: left,
		right: right
	};

	return Aloha.Sidebar;
});

/* position.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
// Start Closure
// Ensure GENTICS Namespace
window.GENTICS = window.GENTICS || {};
window.GENTICS.Utils = window.GENTICS.Utils || {};
define('util/position',['jquery'], function (jQuery) {
	

	var	$ = jQuery,
		GENTICS = window.GENTICS,
		Class = window.Class,
		console = window.console;

	/**
	 * position utility, which will provide scroll and mouse positions
	 * please note that the positions provided by this class are not
	 * realtime - instead they are calculated with a 0.5 second delay
	 */
	GENTICS.Utils.Position = {};

	/**
	 * jquery reference to the window object
	 */
	GENTICS.Utils.Position.w = jQuery(window);

	/**
	 * contains the current scroll top and left position, and indicates if the user is currently scrolling
	 * @api
	 */
	GENTICS.Utils.Position.Scroll = {
		top: 0,
		left: 0,
		isScrolling: false
	};

	/**
	 * contains the scroll corrections to apply on special cases (ribbon for example)
	 * @api
	 */
	GENTICS.Utils.Position.ScrollCorrection = {
		top: 100,
		left: 50
	};

	/**
	 * contains the current mouse position (x,y) as well as an indicator if the mouse is moving
	 * @api
	 */
	GENTICS.Utils.Position.Mouse = {
		x: 0,
		y: 0,
		oldX: 0,
		oldY: 0,
		isMoving: false,
		triggeredMouseStop: true
	};

	/**
	 * contains all mousestop callbacks
	 */
	GENTICS.Utils.Position.mouseStopCallbacks = [];

	/**
	 * contains all mousemove callbacks
	 */
	GENTICS.Utils.Position.mouseMoveCallbacks = [];

	/**
	 * updates scroll position and the scrolling status
	 */
	GENTICS.Utils.Position.update = function () {
		// update scroll position
		var st = this.w.scrollTop(),
			sl = this.w.scrollLeft(),
			i;

		if (this.Scroll.isScrolling) {
			if (this.Scroll.top == st && this.Scroll.left == sl) {
				// stopped scrolling
				this.Scroll.isScrolling = false;
			}
		} else {
			if (this.Scroll.top != st || this.Scroll.left != sl) {
				// started scrolling
				this.Scroll.isScrolling = true;
			}
		}

		// update scroll positions
		this.Scroll.top = st;
		this.Scroll.left = sl;

		// check wether the user has stopped moving the mouse
		if (this.Mouse.x == this.Mouse.oldX && this.Mouse.y == this.Mouse.oldY) {
			this.Mouse.isMoving = false;
			// now check if we've triggered the mousestop event
			if (!this.Mouse.triggeredMouseStop) {
				this.Mouse.triggeredMouseStop = true;
				// iterate callbacks
				for (i = 0; i < this.mouseStopCallbacks.length; i++) {
					this.mouseStopCallbacks[i].call();
				}
			}
		} else {
			this.Mouse.isMoving = true;
			this.Mouse.triggeredMouseStop = false;
			// iterate callbacks
			for (i = 0; i < this.mouseMoveCallbacks.length; i++) {
				this.mouseMoveCallbacks[i].call();
			}
		}

		// update mouse positions
		this.Mouse.oldX = this.Mouse.x;
		this.Mouse.oldY = this.Mouse.y;
	};

	/**
	 * adds a callback method which is invoked when the mouse has stopped moving
	 * @param	callback	the callback method to be invoked
	 * @return	index of the callback
	 */
	GENTICS.Utils.Position.addMouseStopCallback = function (callback) {
		this.mouseStopCallbacks.push(callback);
		return (this.mouseStopCallbacks.length - 1);
	};

	/**
	 * adds a callback method which is invoked when the mouse is moving
	 * @param	callback	the callback method to be invoked
	 * @return	index of the callback
	 */
	GENTICS.Utils.Position.addMouseMoveCallback = function (callback) {
		this.mouseMoveCallbacks.push(callback);
		return (this.mouseMoveCallbacks.length - 1);
	};


	// Mousemove Hooks
	jQuery(function () {
		window.setInterval(function () {
			GENTICS.Utils.Position.update();
		}, 500);
	});

	jQuery('html').mousemove(function (e) {
		GENTICS.Utils.Position.Mouse.x = e.pageX;
		GENTICS.Utils.Position.Mouse.y = e.pageY;
	});

});

/* repositoryobjects.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/repositoryobjects',[
	'aloha/core',
	'util/class'
], function (
	Aloha,
	Class
) {
	

	var	GENTICS = window.GENTICS;

	Aloha.RepositoryObject = function () {};

	/**
	 * @namespace Aloha.Repository
	 * @class Document
	 * @constructor
	 *
	 * Abstract Document suitable for most Objects.<br /><br />
	 *
	 * Example:
	 *
	<pre><code>
	 var item = new Aloha.Repository.Document({
		id: 1,
		repositoryId: 'myrepository',
		name: 'Aloha Editor - The HTML5 Editor',
		type: 'website',
		url:'http://aloha-editor.com',
	 });
	</code></pre>
	 *
	 * @param {Object} properties An object with the data.
	 * <div class="mdetail-params"><ul>
	 * <li><code>id</code> : String <div class="sub-desc">Unique identifier</div></li>
	 * <li><code>repositoryId</code> : String <div class="sub-desc">Unique repository identifier</div></li>
	 * <li><code>name</code> : String <div class="sub-desc">Name of the object. This name is used to display</div></li>
	 * <li><code>type</code> : String <div class="sub-desc">The specific object type</div></li>
	 * <li><code>partentId</code> : String (optional) <div class="sub-desc"></div></li>
	 * <li><code>mimetype</code> : String (optional) <div class="sub-desc">MIME type of the Content Stream</div></li>
	 * <li><code>filename</code> : String (optional) <div class="sub-desc">File name of the Content Stream</div></li>
	 * <li><code>length</code> : String (optional) <div class="sub-desc">Length of the content stream (in bytes)</div></li>
	 * <li><code>url</code> : String (optional) <div class="sub-desc">URL of the content stream</div></li>
	 * <li><code>renditions</code> : Array (optional) <div class="sub-desc">Array of different renditions of this object</div></li>
	 * <li><code>localName</code> : String (optional) <div class="sub-desc">Name of the object. This name is used internally</div></li>
	 * <li><code>createdBy</code> : String (optional) <div class="sub-desc">User who created the object</div></li>
	 * <li><code>creationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was created</div></li>
	 * <li><code>lastModifiedBy</code> : String (optional) <div class="sub-desc">User who last modified the object</div></li>
	 * <li><code>lastModificationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was last modified</div></li>
	 * </ul></div>
	 *
	 */
	Aloha.RepositoryDocument = Class.extend({
		_constructor: function (properties) {

			var p = properties;

			this.type = 'document';

			// Basic error checking for MUST attributes
			if (!p.id || !p.name || !p.repositoryId) {
				//				Aloha.Log.error(this, "No valid Aloha Object. Missing MUST property");
				return;
			}

			GENTICS.Utils.applyProperties(this, properties);

			this.baseType = 'document';
		}
		//		/**
		//		 * Not implemented method to generate this JS API doc correctly.
		//		 */
		//		,empty = function() }

	});



	/**
	 * @namespace Aloha.Repository
	 * @class Folder
	 * @constructor
	 * Abstract Folder suitable for most strucural Objects.<br /><br />
	 *
	 * Example:
	 *
	<pre><code>
	 var item = new Aloha.Repository.Folder({
		id: 2,
		repositoryId: 'myrepository',
		name: 'images',
		type: 'directory',
		parentId:'/www'
	 });
	</code></pre>
	 * @param {Object} properties An object with the data.
	 * <div class="mdetail-params"><ul>
	 * <li><code>id</code> : String <div class="sub-desc">Unique identifier</div></li>
	 * <li><code>repositoryId</code> : String <div class="sub-desc">Unique repository identifier</div></li>
	 * <li><code>name</code> : String <div class="sub-desc">Name of the object. This name is used to display</div></li>
	 * <li><code>type</code> : String <div class="sub-desc">The specific object type</div></li>
	 * <li><code>partentId</code> : String (optional) <div class="sub-desc"></div></li>
	 * <li><code>localName</code> : String (optional) <div class="sub-desc">Name of the object. This name is used internally</div></li>
	 * <li><code>createdBy</code> : String (optional) <div class="sub-desc">User who created the object</div></li>
	 * <li><code>creationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was created</div></li>
	 * <li><code>lastModifiedBy</code> : String (optional) <div class="sub-desc">User who last modified the object</div></li>
	 * <li><code>lastModificationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was last modified</div></li>
	 * </ul></div>
	 *
	 */
	Aloha.RepositoryFolder = Class.extend({

		_constructor: function (properties) {

			var p = properties;

			this.type = 'folder';

			// Basic error checking for MUST attributes
			if (!p.id || !p.name || !p.repositoryId) {
				//		Aloha.Log.error(this, "No valid Aloha Object. Missing MUST property");
				return;
			}

			GENTICS.Utils.applyProperties(this, properties);

			this.baseType = 'folder';

		}
		//	/**
		//	* Not implemented method to generate this JS API doc correctly.
		//	*/
		//	,empty = function() {};

	});
});

/* repositorymanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/repositorymanager',[
	'jquery',
	'util/class',
	'aloha/core',
	'aloha/console',
	'aloha/repositoryobjects' // Provides Aloha.RepositoryFolder
], function (
	$,
	Class,
	Aloha,
	Console,
	__unused__
) {
	

	/**
	 * Given an input set, returns a new set which is a range of the input set
	 * that maps to the given predicate.
	 *
	 * Prefers native Array.prototype.filter() where available (after JavaScript
	 * 1.6).
	 *
	 * @param {function:boolean} predicate
	 * @return {Array} Sub set of domain
	 */
	var filter = (function (predicate) {
		if (predicate) {
			return function (domain, predicate) {
				return domain.filter(predicate);
			};
		}

		return function (domain, predicate) {
			var codomain = [],
				i,
				len = domain.length;
			for (i = 0; i < len; i++) {
				if (predicate(domain[i])) {
					codomain.push(domain[i]);
				}
			}
			return codomain;
		};
	}(Array.prototype.hasOwnProperty('filter')));

	/**
	 * Bundles results, and meta information in preparation for the JSON Reader.
	 *
	 * Used with query().
	 *
	 * @param {Array.<Document|Folder>} items Results, collected from all
	 *                                        repositories.
	 * @param {object<string, number>} meta Optional object containing metainfo.
	 * @return {object} Result object.
	 */
	function bundle(items, meta) {
		var result = {
			items: items,
			results: items.length
		};
		if (meta) {
			result.numItems = meta.numItems;
			result.hasMoreItems = meta.hasMoreItems;
			result.timeout = meta.timeout;
		}
		return result;
	}

	/**
	 * Passes all the results we have collected to the client through the
	 * callback it specified.
	 *
	 * TODO: Implement sorting based on repository specification sort
	 *       items by weight.
	 * items.sort(function (a, b) {
	 *	return (b.weight || 0) - (a.weight || 0);
	 * });
	 *
	 * @param {function} callback Callback specified by client when invoking
	 *                            the query method.
	 * @param {Array.<Document|Folder>|object<string, number>} results
	 */
	function report(callback, results) {
		callback(results);
	}

	/**
	 * Predicates; used to filter lists of repositories based on whether they
	 * implement a method or not.
	 *
	 * @type {object<string, function(Repository):boolean}
	 */
	var repositoryFilters = {
		query: function (repository) {
			return typeof repository.query === 'function';
		},
		getChildren: function (repository) {
			return typeof repository.getChildren === 'function';
		},
		getSelectedFolder: function (repository) {
			return typeof repository.getSelectedFolder === 'function';
		}
	};

	/**
	 * Repository Manager.
	 *
	 * @namespace Aloha
	 * @class RepositoryManager
	 * @singleton
	 */
	var RepositoryManager = Class.extend({

		repositories: [],

		settings: (Aloha.settings && Aloha.settings.repositories) || {},

		initialized: false,

		/**
		 * Initializes all registered repositories.
		 *
		 *                            ???
		 *                             |
		 *                             v
		 *
		 * Warning: testing has shown that repositories are maybe not loaded yet
		 * (found that case in IE7), so don't rely on that in this init
		 * function.
		 *
		 *                             ^
		 *                             |
		 *                            !!!
		 */
		init: function () {
			var manager = this;
			if (typeof manager.settings.timeout === 'undefined') {
				manager.settings.timeout = 5000;
			}
			var i;
			for (i = 0; i < manager.repositories.length; i++) {
				manager.initRepository(manager.repositories[i]);
			}
			manager.initialized = true;
		},

		/**
		 * Registers a Repository.
		 *
		 * If the repositorie is registered after the Repository Manager is
		 * initialized it will be automatically initialized.
		 *
		 * @param {Repository} repository Repository to register.
		 */
		register: function (repository) {
			var manager = this;
			if (!manager.getRepository(repository.repositoryId)) {
				manager.repositories.push(repository);
				if (manager.initialized) {
					manager.initRepository(repository);
				}
			} else {
				Console.warn(manager, 'A repository with name "'
						+ repository.repositoryId
						+ '" already registerd. Ignoring this.');
			}
		},

		/**
		 * Initializes a repository.
		 *
		 * @param {Repository} repository Repository to initialize.
		 */
		initRepository: function (repository) {
			var manager = this;
			if (!repository.settings) {
				repository.settings = {};
			}
			if (manager.settings[repository.repositoryId]) {
				$.extend(repository.settings,
				         manager.settings[repository.repositoryId]);
			}
			repository.init();
		},

		/**
		 * Returns the repository identified by repositoryId.
		 *
		 * @param {String} id Id of repository to retrieve.
		 * @return {Repository|null} Repository or null if none with the given
		 *                           id is found.
		 */
		getRepository: function (id) {
			var manager = this;
			var i;
			for (i = 0; i < manager.repositories.length; i++) {
				if (manager.repositories[i].repositoryId === id) {
					return manager.repositories[i];
				}
			}
			return null;
		},

		/**
		 * Searches all repositories for repositoryObjects matching query and
		 * repositoryObjectType.
		 *
		 * <pre><code>
		 *  // Example:
		 *  var params = {
		 *      queryString: 'hello',
		 *      objectTypeFilter: ['website'],
		 *      filter: null,
		 *      inFolderId: null,
		 *      orderBy: null,
		 *      maxItems: null,
		 *      skipCount: null,
		 *      renditionFilter: null,
		 *      repositoryId: null
		 *  };
		 *  Aloha.RepositoryManager.query(params, function (items) {
		 *      Console.log(items);
		 *  });
		 * </code></pre>
		 *
		 * @param {object<string, mixed>} params
		 *
		 *       queryString: String             The query string for full text
		 *                                       search.
		 *  objectTypeFilter: Array   (optional) Object types to be retrieved.
		 *            filter: Array   (optional) Attributes that will be
		 *                                       included.
		 *        inFolderId: boolean (optional) Whether or not a candidate
		 *                                       object is a child-object of the
		 *                                       folder object identified by the
		 *                                       given inFolderId (objectId).
		 *          inTreeId: boolean (optional) This indicates whether or
		 *                                       not a candidate object is a
		 *                                       descendant-object of the folder
		 *                                       object identified by the given
		 *                                       inTreeId (objectId).
		 *           orderBy: Array   (optional) example: [{
		 *                                           lastModificationDate: 'DESC',
		 *                                           name: 'ASC'
		 *                                       }]
		 *          maxItems: number  (optional) Number of items to include in
		 *                                       result set.
		 *         skipCount: number  (optional) This is tricky in a merged
		 *                                       multi repository scenario.
		 *   renditionFilter: Array   (optional) Instead of termlist, an
		 *                                       array of kind or mimetype is
		 *                                       expected.  If null or an empty
		 *                                       set, then all renditions are
		 *                                       returned. See
		 *                                       http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310
		 *                                       for renditionFilter.
		 *
		 * @param {function(Document|Folder)} callback Function to be invoked
		 *                                             after the repository
		 *                                             manager has finished
		 *                                             querying all
		 *                                             repositories.
		 */
		query: function (params, callback) {
			var manager = this;

			var i;

			// The merged results, collected from repository responses.
			var results = [];

			// The merged metainfo, collected from repository responses.
			var allmetainfo = {
				numItems: 0,
				hasMoreItems: false
			};

			// A counting semaphore (working in reverse, ie: 0 means free).
			var numOpenQueries;

			// Unless the calling client specifies otherwise, the manager will
			// wait a maximum of 5 seconds for all repositories to be queried
			// and respond. 5 seconds is deemed to be the reasonable time to
			// wait when querying the repository manager in the context of
			// something like autocomplete.
			var timeout = (params.timeout && parseInt(params.timeout, 10))
			           || manager.settings.timeout;

			// When this timer times-out, whatever has been collected in
			// `results' will be returned to the calling client and all further
			// processing aborted.
			var timer = window.setTimeout(function () {
				// Store in metainfo that a timeout occurred.
				allmetainfo = allmetainfo || {};
				allmetainfo.timeout = true;

				if (numOpenQueries > 0) {
					Console.warn(manager, numOpenQueries
							+ ' repositories did not return before the '
							+ 'configured timeout of ' + timeout + 'ms.');
					numOpenQueries = 0;
				}
				clearTimeout(timer);
				report(callback, bundle(results, allmetainfo));
			}, timeout);

			/**
			 * Invoked by each repository when it wants to present its results
			 * to the manager.
			 *
			 * Collects the results from each repository, and decrements the
			 * numOpenQueries semaphore to indicate that there is one less
			 * repository for which the manager is waiting for a reponse.
			 *
			 * If a repository invokes this callback after all openCallbacks
			 * have been closed (ie: numOpenQueries == 0), then the repository
			 * was too late ("missed the ship"), and will be ignored.
			 *
			 * If numOpenQueries decrements to 0 during this call, it means that
			 * the the manager is ready to report the results back to the client
			 * through the report() method.
			 *
			 * @param {Array.<Document|Folder>} items Results returned by the
			 *                                        repository.
			 * @param {object<string, number>} metainfo Optional Metainfo
			 *                                          returned by some
			 *                                          repositories.
			 */
			var process = function (items, metainfo) {
				var repository = this;

				if (0 === numOpenQueries) {
					return;
				}

				if (items && items.length) {

					// Because some negligent repository implementations do not
					// set repositoryId properly.
					if (!items[0].repositoryId) {
						var id = repository.repositoryId;
						var i;
						for (i = 0; i < items.length; i++) {
							items[i].repositoryId = id;
						}
					}

					$.merge(results, items);
				}

				if (metainfo && allmetainfo) {
					allmetainfo.numItems =
						($.isNumeric(metainfo.numItems) &&
						 $.isNumeric(allmetainfo.numItems))
							? allmetainfo.numItems + metainfo.numItems
							: undefined;

					allmetainfo.hasMoreItems =
						(typeof metainfo.hasMoreItems === 'boolean' &&
						 typeof allmetainfo.hasMoreItems === 'boolean')
							? allmetainfo.hasMoreItems || metainfo.hasMoreItems
							: undefined;

					if (metainfo.timeout) {
						allmetainfo.timeout = true;
					}
				} else {

					// Because if even one repository does not return metainfo,
					// so we have no aggregated metainfo at all.
					allmetainfo = undefined;
				}

				Console.debug(manager, 'The repository '
						+ repository.repositoryId + 'returned with '
						+ items.length + ' results.');

				// TODO: how to return the metainfo here?
				if (0 === --numOpenQueries) {
					clearTimeout(timer);
					report(callback, bundle(results, allmetainfo));
				}
			};

			var repositories = params.repositoryId
			                 ? [manager.getRepository(params.repositoryId)]
			                 : manager.repositories;

			var queue = filter(repositories, repositoryFilters.query);

			// If none of the repositories implemented the query method, then
			// don't wait for the timeout, simply report to the client.
			if (0 === queue.length) {
				clearTimeout(timer);
				report(callback, bundle(results, allmetainfo));
				return;
			}

			var makeProcess = function (repository) {
				return function () {
					process.apply(repository, arguments);
				};
			};

			numOpenQueries = queue.length;

			for (i = 0; i < queue.length; i++) {
				queue[i].query(params, makeProcess(queue[i]));
			}
		},

		/**
		 * Retrieves children items.
		 *
		 * @param {object<string,mixed>} params Object with properties.
		 *
		 *  objectTypeFilter: Array   (optional) Object types to be retrieved.
		 *            filter: Array   (optional) Attributes to be retrieved.
		 *        inFolderId: boolean (optional) This indicates whether or not
		 *                                       a candidate object is a
		 *                                       child-object of the folder
		 *                                       object identified by the given
		 *                                       inFolderId (objectId).
		 *           orderBy: Array   (optional) example: [{
		 *                                           lastModificationDate: 'DESC',
		 *                                           name: 'ASC'
		 *                                       }]
		 *          maxItems: number  (optional) number Items to return as a result.
		 *         skipCount: number  (optional) This is tricky in a merged
		 *                                       multi repository scenario.
		 *   renditionFilter: Array   (optional) Instead of termlist an Array
		 *                                       of kind or mimetype is
		 *                                       expected. If null or
		 *                                       Array.length == 0 all
		 *                                       renditions are returned. See
		 *                                       http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310
		 *                                       for renditionFilter.
		 *
		 * @param {function(Document|Folder)} callback Function to be invoked
		 *                                             after the repository
		 *                                             manager has finished
		 *                                             querying all
		 *                                             repositories.
		 */
		getChildren: function (params, callback) {
			var manager = this;

			var i;

			// The marged results, collected from repository responses.
			var results = [];

			// A counting semaphore (working in reverse, ie: 0 means free).
			var numOpenQueries = 0;

			var timeout = (params.timeout && parseInt(params.timeout, 10))
			           || manager.settings.timeout;

			var timer = window.setTimeout(function () {
				if (numOpenQueries > 0) {
					Console.warn(manager, numOpenQueries
							+ ' repositories did not respond before the '
							+ 'configured timeout of ' + timeout + 'ms.');
					numOpenQueries = 0;
				}
				clearTimeout(timer);
				report(callback, results);
			}, timeout);

			var process = function (items) {
				if (0 === numOpenQueries) {
					return;
				}
				if (items) {
					$.merge(results, items);
				}
				if (0 === --numOpenQueries) {
					clearTimeout(timer);
					report(callback, results);
				}
			};

			var repositories = params.repositoryId
			                 ? [manager.getRepository(params.repositoryId)]
			                 : manager.repositories;

			if (params.repositoryFilter && params.repositoryFilter.length) {
				repositories = filter(repositories, function (repository) {
					return -1 < $.inArray(repository.repositoryId,
						params.repositoryFilter);
				});
			}

			// If the inFolderId is the default id of 'aloha', then return all
			// registered repositories as the result set.
			if ('aloha' === params.inFolderId) {
				var hasRepoFilter = params.repositoryFilter
				                 && 0 < params.repositoryFilter.length;

				for (i = 0; i < repositories.length; i++) {
					results.push(new Aloha.RepositoryFolder({
						id: repositories[i].repositoryId,
						name: repositories[i].repositoryName,
						repositoryId: repositories[i].repositoryId,
						type: 'repository',
						hasMoreItems: true
					}));
				}

				clearTimeout(timer);
				report(callback, results);
				return;
			}

			var queue = filter(repositories, repositoryFilters.getChildren);

			if (0 === queue.length) {
				clearTimeout(timer);
				report(callback, results);
				return;
			}

			numOpenQueries = queue.length;

			for (i = 0; i < queue.length; i++) {
				queue[i].getChildren(params, process);
			}
		},

		/**
		 * @fixme: Not tested, but the code for this function does not seem to
		 *        compute repository.makeClean will be undefined
		 *
		 * @todo: Rewrite this function header comment so that is clearer
		 *
		 * Pass an object, which represents an marked repository to corresponding
		 * repository, so that it can make the content clean (prepare for saving)
		 *
		 * @param {jQuery} obj - representing an editable
		 * @return void
		 */
		makeClean: function (obj) {
			// iterate through all registered repositories
			var that = this,
				repository = {},
				i = 0,
				j = that.repositories.length;

			// find all repository tags
			obj.find('[data-gentics-aloha-repository=' + this.prefix + ']').each(function () {
				while (i < j) {
					repository.makeClean(obj);
					i += 1;
				}
				Console.debug(that, 'Passing contents of HTML Element with id { ' + this.attr('id') + ' } for cleaning to repository { ' + repository.repositoryId + ' }');
				repository.makeClean(this);
			});
		},

		/**
		 * Marks an object as repository of this type and with this item.id.
		 * Objects can be any DOM objects as A, SPAN, ABBR, etc. or
		 * special objects such as aloha-aloha_block elements.
		 *
		 * Marks the target obj with two private attributes:
		 * (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
		 *	- data-gentics-aloha-repository: stores the repositoryId
		 *	- data-gentics-aloha-object-id: stores the object.id
		 *
		 * @param {HTMLElement} obj DOM object to mark.
		 * @param {Aloha.Repository.Object} item Item which is applied to obj,
		 *                                       if set to null, the
		 *                                       "data-GENTICS-..." attributes
		 *                                       are removed.
		 */
		markObject: function (obj, item) {
			if (!obj) {
				return;
			}

			var manager = this;

			if (item) {
				var repository = manager.getRepository(item.repositoryId);
				if (repository) {
					$(obj).attr({
						'data-gentics-aloha-repository': item.repositoryId,
						'data-gentics-aloha-object-id': item.id
					});
					repository.markObject(obj, item);
				} else {
					Console.error(manager, 'Trying to apply a repository "'
							+ item.name
							+ '" to an object, but item has no repositoryId.');
				}
			} else {
				$(obj).removeAttr('data-gentics-aloha-repository')
				      .removeAttr('data-gentics-aloha-object-id');
			}
		},

		/**
		 * Get the object for which the given DOM object is marked from the
		 * repository.
		 *
		 * Will initialize the item cache (per repository) if not already done.
		 *
		 * @param {HTMLElement} element DOM object which probably is marked.
		 * @param {function} callback
		 */
		getObject: function (element, callback) {
			var manager = this;
			var $element = $(element);
			var itemId = $element.attr('data-gentics-aloha-object-id');
			var repositoryId = $element.attr('data-gentics-aloha-repository');
			var repository = manager.getRepository(repositoryId);

			if (repository && itemId) {
				if (!manager.itemCache) {
					manager.itemCache = [];
				}

				var cache = manager.itemCache[repositoryId];
				if (!cache) {
					cache = manager.itemCache[repositoryId] = [];
				}

				if (cache[itemId]) {
					callback([cache[itemId]]);
				} else {
					repository.getObjectById(itemId, function (items) {
						cache[itemId] = items[0];
						callback(items);
					});
				}
			}
		},

		/**
		 * Mark a folder as opened.
		 *
		 * Called by a repository client (eg: repository browser) when a folder
		 * is opened.
		 *
		 * @param {object|Folder} folder Object with property repositoryId.
		 */
		folderOpened: function (folder) {
			var repository = this.getRepository(folder.repositoryId);
			if (typeof repository.folderOpened === 'function') {
				repository.folderOpened(folder);
			}
		},

		/**
		 * Mark a folder as closed.
		 *
		 * Called by a repository client (eg: repository browser) when a folder
		 * is closed.
		 *
		 * @param {object|Folder} folder Object with property repositoryId.
		 */
		folderClosed: function (folder) {
			var repository = this.getRepository(folder.repositoryId);
			if (typeof repository.folderClosed === 'function') {
				repository.folderClosed(folder);
			}
		},

		/**
		 * Mark a folder as selected.
		 *
		 * Called by a repository client (eg: repository browser) when a folder
		 * is selected.
		 *
		 * @param {object|Folder} folder Object with property repositoryId.
		 */
		folderSelected: function (folder) {
			var repository = this.getRepository(folder.repositoryId);
			if (typeof repository.folderSelected === 'function') {
				repository.folderSelected(folder);
			}
		},

		/**
		 * Retrieve the selected folder.
		 *
		 * @return {Folder} Selected folder or null if it cannot be found.
		 */
		getSelectedFolder: function () {
			var repositories = filter(this.repositories,
					repositoryFilters.getSelectedFolder);
			var i;
			var selected;
			for (i = 0; i < repositories.length; i++) {
				selected = repositories[i].getSelectedFolder();
				if (selected) {
					return selected;
				}
			}
			return null;
		},

		/**
		 * Human readable representation of repository manager.
		 *
		 * @return {string}
		 */
		toString: function () {
			return 'repositorymanager';
		}

	});

	Aloha.RepositoryManager = new RepositoryManager();

	return Aloha.RepositoryManager;
});

/* repository.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('aloha/repository',[
	'aloha/core',
	'util/class',
	'aloha/repositorymanager'
], function (
	Aloha,
	Class,
	RepositoryManager
) {
	

	//	var
	//		$ = jQuery,
	//		GENTICS = window.GENTICS,
	//		Aloha = window.Aloha,
	//		Class = window.Class;

	/**
	 * Abstract Repository Class. Implement that class for your own repository.
	 * @namespace Aloha.Repository
	 * @class Repository
	 * @constructor
	 * @param {String} repositoryId unique repository identifier
	 * @param {String} repositoryName (optional) is the displyed name for this Repository instance
	 */
	var AbstractRepository = Class.extend({
		_constructor: function (repositoryId, repositoryName) {
			/**
			 * @property repositoryId is the unique Id for this Repository instance
			 */
			this.repositoryId = repositoryId;

			/**
			 * contains the repository's settings object
			 * @property settings {Object} the repository's settings stored in an object
			 */
			this.settings = {};

			/**
			 * @property repositoryName is the name for this Repository instance
			 */
			this.repositoryName = repositoryName || repositoryId;

			RepositoryManager.register(this);
		},

		/**
		 * Init method of the repository. Called from Aloha Core to initialize this repository
		 * @return void
		 * @hide
		 */
		init: function () {},

		/**
	 * Searches a repository for object items matching queryString if none found returns null.
	 * The returned object items must be an array of Aloha.Repository.Object
	 *
	<pre><code>
	// simple delicious implementation
	Aloha.Repositories.myRepository.query = function (params, callback) {

		// make local var of this to use in ajax function
		var that = this;

		// handle each word as tag
		var tags = p.queryString.split(' ');

		// if we have a query and no tag matching return
		if ( p.queryString && tags.length == 0 ) {
			callback.call( that, []);
			return;
		}

		// no handling of objectTypeFilter, filter, inFolderId, etc...
		// in real implementation you should handle all parameters

		jQuery.ajax({ type: "GET",
			dataType: "jsonp",
			url: 'http://feeds.delicious.com/v2/json/' + tags.join('+'),
			success: function(data) {
				var items = [];
				// convert data to Aloha objects
				for (var i = 0; i < data.length; i++) {
					if (typeof data[i] != 'function' ) {
						items.push(new Aloha.Repository.Document ({
							id: data[i].u,
							name: data[i].d,
							repositoryId: that.repositoryId,
							type: 'website',
							url: data[i].u
						}));
					}
				}
				callback.call( that, items);
			}
		});
	};
	</code></pre>
	 *
	 * @param {object} params object with properties
	 * <div class="mdetail-params"><ul>
	 * <li><code> queryString</code> :  String <div class="sub-desc">The query string for full text search</div></li>
	 * <li><code> objectTypeFilter</code> : array  (optional) <div class="sub-desc">Object types that will be returned.</div></li>
	 * <li><code> filter</code> : array (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
	 * <li><code> inFolderId</code> : boolean  (optional) <div class="sub-desc">This is indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
	 * <li><code> inTreeId</code> : boolean  (optional) <div class="sub-desc">This indicates whether or not a candidate object is a descendant-object of the folder object identified by the given inTreeId (objectId).</div></li>
	 * <li><code> orderBy</code> : array  (optional) <div class="sub-desc">ex. [{lastModificationDate:’DESC’, name:’ASC’}]</div></li>
	 * <li><code> maxItems</code> : Integer  (optional) <div class="sub-desc">number items to return as result</div></li>
	 * <li><code> skipCount</code> : Integer  (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
	 * <li><code> renditionFilter</code> : array  (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
	 * </ul></div>
	 * @param {function} callback this method must be called with all result items</div></li>
	 */
		query: null,
		/*
	query: function( params, callback ) {
		if (typeof callback === 'function') {
			callback([]);
		}
	},
	*/

		/**
		 * Returns all children of a given motherId.
		 *
		 * @param {object} params object with properties
		 * <div class="mdetail-params"><ul>
		 * <li><code> objectTypeFilter</code> : array  (optional) <div class="sub-desc">Object types that will be returned.</div></li>
		 * <li><code> filter</code> : array  (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
		 * <li><code> inFolderId</code> : boolean  (optional) <div class="sub-desc">This indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
		 * <li><code> orderBy</code> : array  (optional) <div class="sub-desc">ex. [{lastModificationDate:’DESC’, name:’ASC’}]</div></li>
		 * <li><code> maxItems</code> : Integer  (optional) <div class="sub-desc">number items to return as result</div></li>
		 * <li><code> skipCount</code> : Integer  (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
		 * <li><code> renditionFilter</code> : array  (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
		 * </ul></div>
		 * @param {function} callback this method must be called with all result items
		 */
		getChildren: null,
		/*
	getChildren: function( params, callback ) {
		if (typeof callback === 'function') {
			callback([]);
		}
	},
	*/

		/**
	 * Make the given jQuery object (representing an object marked as object of this type)
	 * clean. All attributes needed for handling should be removed.
	 *
	<pre><code>
	Aloha.Repositories.myRepository.makeClean = function (obj) {
		obj.removeAttr('data-myRepository-name');
	};
	</code></pre>
	 * @param {jQuery} obj jQuery object to make clean
	 * @return void
	 */
		makeClean: function (obj) {},

		/**
	 * This method will be called when a user chooses an item from a repository and wants
	 * to insert this item in his content.
	 * Mark or modify an object as needed by that repository for handling, processing or identification.
	 * Objects can be any DOM object as A, SPAN, ABBR, etc. or
	 * special objects such as aloha-aloha_block elements.
	 * (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
	 *
	<pre><code>
	Aloha.Repositories.myRepository.markObject = function (obj, resourceItem) {
		obj.attr('data-myRepository-name').text(resourceItem.name);
	};
	</code></pre>
	 *
	 *
	 * @param obj jQuery target object to which the repositoryItem will be applied
	 * @param repositoryItem The selected item. A class constructed from Document or Folder.
	 * @return void
	 */
		markObject: function (obj, repositoryItem) {},

		/**
		 * Set a template for rendering objects of this repository
		 * @param {String} template
		 * @return void
		 * @method
		 */
		setTemplate: function (template) {
			if (template) {
				this.template = template;
			} else {
				this.template = null;
			}
		},

		/**
		 * Checks whether the repository has a template
		 * @return {boolean} true when the repository has a template, false if not
		 * @method
		 */
		hasTemplate: function () {
			return this.template ? true : false;
		},

		/**
		 * Get the parsed template
		 * @return {Object} parsed template
		 * @method
		 */
		getTemplate: function () {
			return this.template;
		},

		/**
		 * Get the repositoryItem with given id
		 * @param itemId {String} id of the repository item to fetch
		 * @param callback {function} callback function
		 * @return {Aloha.Repository.Object} item with given id
		 */
		getObjectById: function (itemId, callback) {
			return true;
		}
	});

	// expose the AbstractRepository
	Aloha.AbstractRepository = AbstractRepository;

	return AbstractRepository;
});

/* aloha.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
(function (global) {
	

	/**
	 * Initialization facilities.
	 */
	var Initialization = {

		/**
		 * A list of all stages that are passed into the Initialization.start()
		 * function.  Unless failure happens, every single one of these phases
		 * will be passed.
		 *
		 * @type {Array.<object>}
		 */
		phases: [],

		/**
		 * Completed phases.
		 *
		 * This array grows as the initialization process progresses through
		 * the initialization phases.  Each phases which is completed is pushed
		 * to the bottom of the list.
		 *
		 * @type {Array.<object>}
		 */
		completed: [],

		/**
		 * Starts the initialization phases.
		 *
		 * @param {object.<object>} phases Initialization phases.
		 * @param {function} callback Callback function to be invoked when
		 *                            initialization is completed.
		 */
		start: function (phases, callback) {
			Initialization.phases = Initialization.phases.concat(phases);
			Initialization.proceed(0, phases, callback);
		},

		/**
		 * Proceeds to next initialization phase.
		 *
		 * @param {number} index The current initialization phase, as an index
		 *                       into `phases'.
		 * @param {Array.<object>} phases
		 * @param {function=} callback Callback function to invoke at the end
		 *                             of the initialization phases.
		 */
		proceed: function (index, phases, callback) {
			if (index < phases.length) {
				var phase = phases[index];
				var next = function () {
					Initialization.proceed(++index, phases, callback);
				};
				var event = function () {
					Initialization.completed.push(phase);
					if (phase.event) {
						Aloha.trigger(phase.event);
					}
				};
				if (phase.fn) {
					phase.fn(event, next);
				} else {
					event();
					next();
				}
			} else if (callback) {
				callback();
			}
		},

		/**
		 * Retreives an phase object whose `event' property string matches the
		 * given event name.
		 *
		 * @param {string} event The event name.
		 * @return {object} A phase object or null.
		 */
		getPhaseByEvent: function (event) {
			var i;
			for (i = 0; i < Initialization.phases.length; i++) {
				if (event === Initialization.phases[i].event) {
					return Initialization.phases[i];
				}
			}
			return null;
		},

		/**
		 * Given and the name of an event, returns a corresponding readiness
		 * state concerning what should be done with that event at the current
		 * stage in the initialization phase.
		 *
		 * @param {string} event Name of event.
		 * @return {string} One of either "immediate", "deferred", or "noraml".
		 */
		getReadiness: function (event) {
			var i;
			for (i = 0; i < Initialization.completed.length; i++) {
				if (event === Initialization.completed[i].event) {
					return 'immediate';
				}
			}
			return Initialization.getPhaseByEvent(event) ? 'deferred'
			                                             : 'normal';
		}
	};

	/**
	 * Gets the configuration for loading Aloha.
	 *
	 * If Aloha.settings.baseUrl is not specified, it will be taken from
	 * the first script element that has a data-aloha-plugins attribute,
	 * or, if there is no such script element, the first script element
	 * of which the src attribute matches /\/aloha.js$/.
	 *
	 * If Aloha.settings.plugins.load is not specified, it will be taken
	 * from the data-aloha-plugins attribute from the first script
	 * element carrying this attribute.
	 *
	 * @return
	 *       A map with two properties:
	 *       baseUrl - the path to aloha.js (this file).
	 *       plugins - an array of plugins to load.
	 */
	function getLoadConfig() {
		var scripts,
		    script,
		    plugins = Aloha.settings.plugins && Aloha.settings.plugins.load,
		    baseUrl = Aloha.settings.baseUrl,
		    pluginsAttr,
		    regexAlohaJs = /\/aloha.js(\?\S*)?$/,
            regexStripFilename = /\/[^\/]*\.js$/,
		    i;

		if (!plugins || !baseUrl) {
			scripts = document.getElementsByTagName('script');
			for (i = 0; i < scripts.length; i++) {
				script = scripts[i];
				pluginsAttr = script.getAttribute('data-aloha-plugins');
				if (null != pluginsAttr) {
					if (!plugins) {
						plugins = pluginsAttr;
					}
					if (!baseUrl) {
						baseUrl = script.src.replace(regexStripFilename, '');
					}
					break;
				}
				if (!baseUrl && regexAlohaJs.test(script.src)) {
					baseUrl = script.src.replace(regexAlohaJs, '');
				}
			}
		}

		if (typeof plugins === 'string' && plugins !== '') {
			plugins = plugins.replace(/\s+/g, '').split(',');
		}

		return {
			baseUrl: baseUrl,
			plugins: plugins || []
		};
	}

	function isDeferInit() {
		var scripts = document.getElementsByTagName('script');
		for (var i = 0; i < scripts.length; i++) {
			var attr = scripts[i].getAttribute('data-aloha-defer-init');
			if ("true" === attr) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Extends the given map with plugin specific requirejs path configuration.
	 *
	 * plugin-name: bundle-path/plugin-name/lib
	 * plugin-name/nls: bundle-path/plugin-name/nls
	 * plugin-name/css: bundle-path/plugin-name/css
	 * plugin-name/vendor: bundle-path/plugin-name/vendor
	 * plugin-name/res: bundle-path/plugin-name/res
	 */
	function mergePluginPaths(paths, bundlePath, pluginName) {
		var resourceFolders = ['nls', 'css', 'vendor', 'res'],
		    resourceFolder,
		    i;
		paths[pluginName] = bundlePath + '/' + pluginName + '/lib';
		for (i = 0; i < resourceFolders.length; i++) {
			var resourceFolder = resourceFolders[i];
			paths[pluginName + '/' + resourceFolder]
				= bundlePath + '/' + pluginName + '/' + resourceFolder;
		}
	}

	/**
	 * Gets the configuration for loading the given plugins.
	 *
	 * The bundle-path for each given plugin is determined in the following manner:
	 * Aloha.settings.basePath + (Aloha.settings.bundles[bundleName] || "../plugins/bundle-name")
	 *
	 * @param plugins
	 *        An array of plugins to get the configuration for in the
	 *        form "bundle-name/plugin-name"
	 * @return
	 *        A map with the following properties:
	 *        paths - requirejs path configuration for each plugin (mergePluginPaths())
	 *        entryPoints - an array of requirejs entry points ("link/link-plugin")
	 *        baseUrlByName - ("link" => "bundle-path/link")
	 *        names - an array of plugin names (the same as the given
	 *                array with the bundle-name stripped)
	 */
	function getPluginLoadConfig(plugins) {
		var paths = {},
		    entryPoints = [],
		    names = [],
		    baseUrlByName = {},
		    map = {},
		    parts,
		    bundleName,
		    pluginName,
		    basePath = Aloha.settings.basePath || '',
		    bundlePath,
		    bundles = Aloha.settings.bundles || {},
		    i;
		for (i = 0; i < plugins.length; i++) {
			parts = plugins[i].split('/');
			bundleName = parts[0];
			pluginName = parts[1];
			if (bundles[bundleName]) {
				bundlePath = basePath + bundles[bundleName];
			} else {
				bundlePath = basePath + '../plugins/' + bundleName;
			}
			mergePluginPaths(paths, bundlePath, pluginName);
			baseUrlByName[pluginName] = bundlePath + '/' + pluginName;
			entryPoints.push(pluginName + '/' + pluginName + '-plugin');
			map[pluginName] = {'jquery': 'aloha/jquery'};
		}
		return {
			paths: paths,
			entryPoints: entryPoints,
			baseUrlByName: baseUrlByName,
			names: names,
			map: map
		};
	}

	/**
	 * Merges properites of all given arguments into a new one.
	 * Duplicate properties will be "seived" out.
	 * Works in a similar way to jQuery.extend.
	 * Necessary because we must not assume that jquery was already
	 * loaded.
	 */
	function mergeObjects () {
		var clone = {};
		var objects = Array.prototype.slice.call(arguments);
		var name;
		var i;
		var obj;
		for (i = 0; i < objects.length; i++) {
			obj = objects[i];
			for (name in obj) {
				if (obj.hasOwnProperty(name)) {
					clone[name] = objects[i][name];
				}
			}
		}
		return clone;
	}

	function createDefine(name, module) {
		define(name, function () {
			return module;
		});
	}

	function load() {
		Aloha.features = {};
		Aloha.defaults = {};
		Aloha.settings = Aloha.settings || {};
		Aloha.initialize = Initialization.start;

		var loadConfig = getLoadConfig();
		var pluginConfig = getPluginLoadConfig(loadConfig.plugins);

		Aloha.settings.baseUrl = loadConfig.baseUrl;
		Aloha.settings.loadedPlugins = pluginConfig.names;
		Aloha.settings._pluginBaseUrlByName = pluginConfig.baseUrlByName;

		var coreMap = {
			'aloha':             {'jquery': 'aloha/jquery'},
			'aloha/jquery':      {'jquery': 'jquery'}, // avoid a circular dependency
			'jqueryui':          {'jquery': 'aloha/jquery'},
			'vendor':            {'jquery': 'aloha/jquery'},
			'util':              {'jquery': 'aloha/jquery'},
			'RepositoryBrowser': {'jquery': 'aloha/jquery'},
			'jstree':            {'jquery': 'aloha/jquery'},
			'jqgrid':            {'jquery': 'aloha/jquery'},
			'jqgrid-locale-en':  {'jquery': 'aloha/jquery'},
			'jqgrid-locale-de':  {'jquery': 'aloha/jquery'},
			'jquery-layout':     {'jquery': 'aloha/jquery'}
		};

		/**
		 * Map the 'jquery' module to the 'aloha/jquery' module. This
		 * enforces Aloha modules to always use aloha/jquery instead of
		 * jquery. One could also just write
		 * define(['aloha/jquery']... to require Aloha's jquery, but
		 * this is problematic in vendor files that don't know anything
		 * about Aloha. Each key in the map is either the module name,
		 * or the firs part of the module name. For example, the mapping
		 * under the key 'aloha' will take effect for all modules with
		 * names like aloha/xxx.  When a new 'paths' entry is added
		 * (browserPaths or other), an entry should also be added the
		 * moduleMap to rename the jquery dependency.
		 * See also define('aloha/jquery', ... below.
		 */
		var moduleMap = mergeObjects(coreMap, pluginConfig.map)

		var defaultConfig = {
			context: 'aloha',
			config: {
				i18n: {
					locale: Aloha.settings.locale || 'en'
				}
			},
			baseUrl: Aloha.settings.baseUrl,
			map: moduleMap
		};
		
		var DependencyManagement = global.__DEPS__ || (global.__DEPS__ = {});
		
		DependencyManagement.lang = defaultConfig.config.i18n.locale;

		var defaultPaths = {
			jquery: 'vendor/jquery-1.7.2',
			jqueryui: 'vendor/jquery-ui-1.9.0.custom-aloha'
		};

		var browserPaths = {
			PubSub: 'vendor/pubsub/js/pubsub-unminified',
			'Class': 'vendor/class',
			RepositoryBrowser: 'vendor/repository-browser/js/repository-browser-unminified',
			jstree: 'vendor/jquery.jstree',              // Mutates jquery
			jqgrid: 'vendor/jquery.jqgrid',              // Mutates jquery
			'jquery-layout': 'vendor/jquery.layout-1.3.0-rc30.7',     // Mutates jquery
			'jqgrid-locale-en': 'vendor/grid.locale.en', // Mutates jqgrid
			'jqgrid-locale-de': 'vendor/grid.locale.de', // Mutates jqgrid
			'repository-browser-i18n-de': 'vendor/repository-browser/js/repository-browser-unminified',
			'repository-browser-i18n-en': 'vendor/repository-browser/js/repository-browser-unminified'
		};

		var requireConfig = mergeObjects(
			defaultConfig,
			Aloha.settings.requireConfig
		);

		requireConfig.paths = mergeObjects(
			defaultPaths,
			browserPaths,
			pluginConfig.paths,
			requireConfig.paths
		);

		// Create define() wrappers that will provide the initialized objects
		// that the user passes into Aloha via require() calls.
		var predefinedModules = Aloha.settings.predefinedModules || {};

		if (Aloha.settings.jQuery) {
			predefinedModules.jquery = Aloha.settings.jQuery;
		}

		var moduleName;
		for (moduleName in predefinedModules) if (predefinedModules.hasOwnProperty(moduleName)) {
			createDefine(moduleName, predefinedModules[moduleName]);
			delete requireConfig.paths[moduleName];
		}

		// Configure require and expose the Aloha.require.
		var alohaRequire = require.config(requireConfig);

		Aloha.require = function (callback) {
			// Pass the Aloha object to the given callback.
			if (1 === arguments.length && typeof callback === 'function') {
				return alohaRequire(['aloha'], callback);
			}
			return alohaRequire.apply(this, arguments);
		};

		/**
		 *
		 * @param {string} event Name of event
		 * @param {function} fn Event handler
		 */
		Aloha.bind = function (event, fn) {
			Aloha.require(['aloha/jquery'], function ($) {
				// Because we will only need to load jQuery once
				Aloha.bind = function (event, fn) {
					switch(Initialization.getReadiness(event)) {
					case 'deferred':
						var phase = Initialization.getPhaseByEvent(event);
						if (!phase.deferred) {
							phase.deferred = $.Deferred();
						}
						phase.deferred.done(fn);
						break;
					case 'immediate':
						fn();
						break;
					case 'normal':
						$(Aloha, 'body').bind(event, fn);
						break;
					default:
						throw 'Unknown readiness';
					}
					return this;
				};
				Aloha.bind(event, fn);
			});
			return this;
		};

		Aloha.trigger = function (type, data) {
			Aloha.require(['aloha/jquery'], function ($) {
				Aloha.trigger = function (type, data) {
					var phase = Initialization.getPhaseByEvent(type);
					if (phase) {
						if (phase.deferred) {
							$(phase.deferred.resolve);
							phase.deferred = null;
						}
					}
					$(Aloha, 'body').trigger(type, data);
					return this;
				};
				Aloha.trigger(type, data);
			});
			return this;
		};

		Aloha.unbind = function (typeOrEvent) {
			Aloha.require(['aloha/jquery'], function ($) {
				Aloha.unbind = function (typeOrEvent) {
					$(Aloha, 'body').unbind(typeOrEvent);
				};
				Aloha.unbind(typeOrEvent);
			});
		};

		Aloha.ready = function (fn) {
			this.bind('aloha-ready', fn);
			return this;
		};

		/**
		 * This makes sure that all Aloha modules will receive the same jQuery.
		 *
		 * This is a workaround for when a user includes his own
		 * jQuery _after_ aloha.js has been loaded.
		 *
		 * If multiple 'jquery's are included in the page, each version
		 * will make its own call to define(), and depending on when an
		 * Aloha module is loaded, it may receive a different
		 * 'jquery'. However, 'aloha/jquery' will not be redefined and
		 * will therefore point always to only one particular version.
		 *
		 * !!Important!! to be certain that 'aloha/jquery' points to
		 * the jQuery intended for Aloha, it can't be loaded
		 * dynamically, because if a user loads his own jQuery after
		 * aloha.js, then there is no way to tell whether it is the
		 * user's jQuery or Aloha's jQuery that has finished
		 * loading. Instead, jQuery must be loaded before aloha.js and
		 * passed in to us.
		 */
		var jQueryThatWasPassedToUs = Aloha.settings.jQuery;
		define('aloha/jquery', ['jquery'], function ($) {
			// We prefer Aloha.settings.jQuery, since a dynamically loaded
			// jQuery may have been redefined by a user's jQuery.
			return jQueryThatWasPassedToUs || $;
		});

		// Initialize this early so that the user doesn't have to use
		// Aloha.ready().
		Aloha.jQuery = jQueryThatWasPassedToUs;

		define('aloha', [
			'aloha/jquery',
			'util/json2',
			'aloha/rangy-core',
			'util/class',
			'util/lang',
			'util/range',
			'util/dom',
			'aloha/core',
			'aloha/editable',
			'aloha/console',
			'aloha/markup',
			'aloha/plugin',
			'aloha/selection',
			'aloha/command',
			'aloha/jquery.aloha',
			'aloha/sidebar',
			'util/position',
			'aloha/repositorymanager',
			'aloha/repository',
			'aloha/repositoryobjects',
			'aloha/contenthandlermanager'
		], function($) {
			Aloha.features.jquery = true;

			// Set it again in case jQuery was loaded asynchronously.
			Aloha.jQuery = $;

			// Some core files provide default settings in Aloha.defaults.
			Aloha.settings = $.extendObjects(true, {}, Aloha.defaults,
					Aloha.settings);

			return Aloha;
		});

		// TODO aloha should not make the require call itself.  Instead, user
		// code should require and initialize aloha.
		require(requireConfig, ['aloha', 'aloha/jquery'], function (Aloha, $) {
			require(requireConfig, pluginConfig.entryPoints, function () {
				$(function () {
					// Rangy must be initialized only after the body is
					// available since it accesses the body element during
					// initialization.
					window.rangy.init();

					// The same for Aloha, but probably only because it depends
					// on rangy.
					Aloha.init();
				});
			});
		});
	} // end load()

	global.Aloha = global.Aloha || {};
	if (global.Aloha.deferInit || isDeferInit()) {
		global.Aloha.deferInit = load;
	} else {
		// Unless init is deferred above, aloha mus be loaded
		// immediately in the development version, but later in the
		// compiled version. The reason loading must be delayed in the
		// compiled version is that the "include" directive in the r.js
		// build profile, which lists the plugins that will be compiled
		// into aloha.js, will include the plugins *after* this
		// file. Since the require() call that loads the plugins is in
		// this file, it will not see any of the plugin's defines that
		// come after this file. The call to Aloha._load is only made in
		// compiled mode in closure-end.frag. The call to load() below
		// is only made in development mode because the excludeStart and
		// excludeEnd r.js pragmas will exclude everything inbetween in
		// the compiled version.
		// TODO ideally the bootstrap file should not make the require
		//      call at all. Instead, user code should require and
		//      initialize aloha.
		Aloha._load = load;
			}
}(window));

/**
 * The context module provides functions to get at the context element
 * for widgets.
 *
 * Many widgets are created dynamically and append themselves to the
 * body so that they can be shown to the user. The context element is
 * just a div appended to the body, that provides a common parent for
 * these widget elements.
 * 
 * Appending widget elements to the context element provides two benefits:
 * 1 - it would be cleaner for all aloha-specific elements to be
 *     appended to one common parent.
 * 2 - all css rules should have a .aloha context class, and the common
 *     parent provides this class.
 */
define('ui/context',[
	'aloha',
	'jquery',
	'util/class'
], function(
	Aloha,
	$,
	Class
) {
	

	var id = 'aloha-ui-context',
	    selector = '#' + id,
	    element;

	// There is just a single context element in the page
	element = $(selector);
	if (!element.length) {
		element = $('<div>', {'class': 'aloha', 'id': id});
		// In the built aloha.js, init will happend before the body has
		// finished loading, so we have to defer appending the element.
		$(function(){ element.appendTo('body'); });
	}

	var Context =  Class.extend({
		surfaces: [],
		containers: []
	});

	// static fields

	$.extend(Context, {
		selector: selector,
		element: element
	});

	return Context;
});

define('ui/scopes',[
	'jquery',
	'PubSub',
	'util/maps'
], function (
	$,
	PubSub,
	Maps
) {
	

	var scopes = {
		    'Aloha.empty': [],
		    'Aloha.global': ['Aloha.empty'],
		    'Aloha.continuoustext': ['Aloha.global']
	    },
	    activeScopes = [],
	    addedScopes = {},
	    scopeSetDuringSelectionChanged = false;

	function pushScopeAncestors(ancestorScopes, scope) {
		if (!scopes.hasOwnProperty(scope)) {
			return;
		}
		var parentScopes = scopes[scope];
		for (var i = 0; i < parentScopes.length; i++) {
			var parentScope = parentScopes[i];
			ancestorScopes.push(parentScope);
			pushScopeAncestors(ancestorScopes, parentScope);
		}
	}

	Aloha.bind('aloha-selection-changed-before', function () {
		scopeSetDuringSelectionChanged = false;
	});

	Aloha.bind('aloha-selection-changed-after', function (event, range, originalEvent) {
		// I don't know why we check for originalEvent != 'undefined', here is
		// the original comment:
		// "Only set the specific scope if an event was provided, which means
		// that somehow an editable was selected"
		if (typeof originalEvent !== 'undefined' && ! scopeSetDuringSelectionChanged) {
			Scopes.setScope('Aloha.continuoustext');
		}
	});

	/**
	 * @deprecated
	 *     Scopes don't provide any additional functionality since
	 *     the visibility of containers and components can be
	 *     controlled individually.
	 */
	var Scopes = {

		/**
		 * Increments the scope counter for the given scope and requestor.
		 *
		 * A counter is maintained per scope and requestor. The counter
		 * can be incremented/decremented with enterScope/leaveScope.
		 *
		 * The first increment of the counter (the increment to 1) will
		 * make the scope active (isActiveScope() returns true) and
		 * publish the aloha.ui.scope.change event. Further increments
		 * will do nothing except increment the counter, which will
		 * require more leaveScope calls to be made to leave the scope.
		 *
		 * The last decrement of the counter (the decrement to 0) will
		 * make the scope inactive (isActiveScope() returns false) and
		 * publish the aloha.ui.scope.change event. Further decrements
		 * will do nothing.
		 *
		 * @param scope
		 *        The scope to enter.
		 * @param requestor
		 *        The subsystem or plugin that requests to leave the
		 *        scope. Can be used to isolate the scope counter to a
		 *        particular subsystem, usually a plugin. If not given,
		 *        a global counter will be used instead.
		 * @deprecated
		 *     Scopes don't provide any additional functionality since
		 *     the visibility of containers and components can be
		 *     controlled individually.
		 */
		enterScope: function(scope, requestor) {
			requestor = requestor || '_globalCounter';
			var counters = addedScopes[scope];
			if (!counters) {
				counters = addedScopes[scope] =  {};
			}
			var counter = counters[requestor] || 0;
			counters[requestor] = counter + 1;
			if (!counter) {
				PubSub.pub('aloha.ui.scope.change');
			}
		},

		/**
		 * Decrements the scope counter for the given scope and requestor.
		 *
		 * @param force
		 *        True when the scope should be left even if the counter
		 *        is non-zero after decrementing it.
		 * @see enterScope()
		 * @deprecated
		 *     Scopes don't provide any additional functionality since
		 *     the visibility of containers and components can be
		 *     controlled individually.
		 */
		leaveScope: function(scope, requestor, force) {
			requestor = requestor || '_globalCounter';
			var counters = addedScopes[scope];
			if (!counters) {
				return;
			}
			var counter = counters[requestor];
			if (!counter) {
				return;
			}
			counter -= 1;
			if (counter && !force) {
				counters[requestor] = counter;
			} else {
				delete counters[requestor];
				if (Maps.isEmpty(counters)) {
					delete addedScopes[scope];
				}
				PubSub.pub('aloha.ui.scope.change');
			}
		},

		/**
		 * @deprecated
		 *     Scopes don't provide any additional functionality since
		 *     the visibility of containers and components can be
		 *     controlled individually.
		 */
		isActiveScope: function(scope){
			if (addedScopes[scope]) {
				return true;
			}
			var isActive = (-1 !== $.inArray(scope, activeScopes));
			if (isActive) {
				return true;
			}
			return false;
		},

		/**
		 * @deprecated
		 *     See setScope()
		 */
		getPrimaryScope: function() {
			return activeScopes[0];
		},

		/**
		 * @deprecated
		 *     Problem with setScope is that scopes defined by multiple plugins are exclusive to one another.
		 *     Example: table plugin and link plugin - you want to be able to set both table and link scopes.
		 *     Use enterScope and leaveScope instead.
		 */
		setScope: function(scope) {
			scopeSetDuringSelectionChanged = true;
			if (activeScopes[0] != scope) {
				activeScopes = [scope];
				pushScopeAncestors(activeScopes, scope);
				PubSub.pub('aloha.ui.scope.change');
			}
		},

		/**
		 * @deprecated
		 *     This method was used to define an ancestry for scopes.
		 *     It is unknonwn what problem scope ancestry solved, and
		 *     the method is therefore deprecated.
		 */
		createScope: function(scope, parentScopes){
			if ( ! parentScopes ) {
				parentScopes = ['Aloha.empty'];
			} else if (typeof parentScopes === 'string') {
				parentScopes = [parentScopes];
			}
			scopes[scope] = parentScopes;
		}
	};
	return Scopes;
});

/**
 * Defines a `Container` Class.
 *
 * Containers are activated based on the `showOn` setting for the container.
 * The values are normalized to functions which accept an element and return a
 * boolean; true means the container should be shown.
 *
 * For efficiency, we group all containers that have the same normalized
 * `showOn()' function together, so we can evaluate it once, regardless of how
 * many containers are using the same logic. In order for this to work, the
 * exact same function must be returned from `Container.normalizeShowOn()' when
 * the logic is the same.
 *
 * The list of containers is then stored on the context instance as
 * `context.containers', which is a hash of `showOn()' ids to an array of
 * containers. The `showOn()' ids are unique identifiers that are stored as
 * properties of the `showOn()' function (see `getShowOnId()'). This gives us
 * constant lookup times when grouping containers.
 */

define('ui/container',[
	'jquery',
	'util/class',
	'ui/scopes'
], function(
	$,
	Class,
	Scopes
) {
	

	var uid = 0;

	/**
	 * Gets the id of a normalized showOn option.  If the given function has
	 * not had its showOnId set it will receive one, the first time this
	 * function it is passed to this function.
	 *
	 * @param {function} showOn The function whose id we wish to get.
	 * @return {number} The id of the given function.
	 */
	function getShowOnId(showOn) {
		// Store a unique id on the showOn function.
		// See full explanation at top of file.
		if (!showOn.showOnId) {
			showOn.showOnId = ++uid;
		}
		return showOn.showOnId;
	}

	/**
	 * Show or hide a set of containers.
	 *
	 * @param {Array.<Container>} containers The set of containers to operate
	 *                                       on.
	 * @param {boolean} show Whether to show or hide the given containers.
	 */
	function toggleContainers(containers, show) {
		var action = show ? 'show' : 'hide',
		    i;
		for (i = 0; i < containers.length; i++) {
			containers[i][action]();
		}
	}

	var scopeFns = {};

	var returnTrue = function() {
		return true;
	};

	/**
	 * Normalizes a showOn option into a function.
	 *
	 * @param {(string|boolean|function)} showOn
	 * @return function
	 */
	function normalizeShowOn(container, showOn) {
		switch ($.type(showOn)) {
		case 'function':
			return showOn;
		case 'object':
			if (showOn.scope) {
				if (scopeFns[showOn.scope]) {
					return scopeFns[showOn.scope];
				}
				return scopeFns[showOn.scope] = function() {
					return Scopes.isActiveScope(showOn.scope);
				};
			} else {
				throw "Invalid showOn configuration";
			}
		default:
			return returnTrue;
		}
	}

	/**
	 * Container class.
	 *
	 * @class
	 * @base
	 */
	var Container = Class.extend({

		/**
		 * The containing (wrapper) element for this container.
		 *
		 * @type {jQuery<HTMLElement>}
		 */
		element: null,

		/**
		 * Initialize a new container with the specified properties.
		 *
		 * @param {object=} settings Optional properties, and override methods.
		 * @constructor
		 */
		_constructor: function(context, settings) {
			var showOn = normalizeShowOn(this, settings.showOn),
			    key = getShowOnId(showOn),
			    group = context.containers[key];
			this.context = context;
			if (!group) {
				group = context.containers[key] = {
					shouldShow: showOn,
					containers: []
				};
			}
			group.containers.push(this);
		},

		/**
		 * Must be implemented by extending classes.
		 *
		 * @ingroup api
		 * @{
		 */

		/**
		 * A container is also a component; this is part of the component API.
		 */
		show: function() {},
		/**
		 * A container is also a component; this is part of the component API.
		 */
		hide: function() {},
		/**
		 * A container is also a component; this is part of the component API.
		 */
		focus: function() {},
		/**
		 * A container is also a component; this is part of the component API.
		 */
		foreground: function() {},

		/**
		 * The container was previously hidden, and now has become visible. This
		 * allows a container to let its children react to this.
		 */
		childVisible: function(childComponent, visible) {},
		/**
		 * The container was given focus; this method must give focus to all
		 * children of the container.
		 * Optional. (E.g. tab.js doesn't implement this.)
		 */
		childFocus: function(childComponent) {},
		/**
		 * The container was foregrounded; this method must foreground all children
		 * of the container.
		 */
		childForeground: function(childComponent) {}

		/**
		 * @} End of "ingroup api".
		 */

	});

	// static fields

	$.extend( Container, {
		/**
		 * Given an array of elements, show appropriate containers.
		 *
		 * @param {object} context
		 * @param {string} eventType Type of the event triggered (optional)
		 * @static
		 */
		showContainersForContext: function(context, eventType) {
			var group,
			    groupKey,
			    containerGroups;
			if (!context.containers) {
				// No containers were constructed for the given context, so
				// there is nothing for us to do.
				return;
			}
			containerGroups = context.containers;
			for (groupKey in containerGroups) {
				if (containerGroups.hasOwnProperty(groupKey)) {
					group = containerGroups[groupKey];
					toggleContainers(group.containers, group.shouldShow(eventType));
				}
			}
		}
	});

	return Container;
});

define('ui/surface',[
	'aloha/core',
	'jquery',
	'util/class',
	'ui/container'
], function(
	Aloha,
	$,
	Class,
	Container
) {
	

	/**
	 * The Surface class and manager.
	 *
	 * @class
	 * @base
	 */
	var Surface = Class.extend({
		_constructor: function(context) {
			context.surfaces.push(this);
		},

		/**
		 * Check for whether or not this surface is active--that is, whether is
		 * is visible and the user can interact with it.
		 *
		 * @eturn {boolean} True if this surface is visible.
		 */
		isActive: function() {
			return true;
		}
	});

	// Static fields for the Surface class.

	$.extend(Surface, {

		/**
		 * The range of the current selection.
		 * 
		 * Interacting with a surface removes focus from the editable, so the
		 * surface is responsible for keeping track of the range that should be
		 * modified by the components.
		 * 
		 * @static
		 * @type {Aloha.Selection}
		 */
		range: null,

		/**
		 * Shows all surfaces for a given context.
		 *
		 * @param {!Object} context.
		 */
		show: function(context) {
			$.each(context.surfaces, function(i, surface) {
				surface.show();
			});
		},

		/**
		 * Hides all surfaces for a given context.
		 *
		 * @param {!Object} context
		 */
		hide: function(context) {
			$.each(context.surfaces, function (i, surface) {
				surface.hide();
			});
		},

		/**
		 * Track editable and range when interacting with a surface.
		 *
		 * @param {jQuery<HTMLElement>} element A component or surface for
		 *                                      which we wish to keep track of
		 *                                      the current selection range
		 *                                      when the user interacts with
		 *                                      it.
		 */
		trackRange: function(element) {
			element.bind('mousedown', function(e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = true;
				Surface.suppressHide = true;

				if (Aloha.activeEditable) {
					var selection = Aloha.getSelection();
					Surface.range = (0 < selection.getRangeCount())
					              ? selection.getRangeAt(0)
								  : null;
				}
			});
			
			element.bind('mouseup', function(e) {
				e.originalEvent.stopSelectionUpdate = true;
				Aloha.eventHandled = false;
				Surface.suppressHide = false;
			});
		}
	});

	return Surface;
});

define('ui/component',[
	'aloha/core',
	'jquery',
	'util/class'
], function (
	Aloha,
	$,
	Class
) {
	

	var idCounter = 0;

	/**
	 * Component class and manager.
	 *
	 * This implementation constitues the base of all UI components (buttons,
	 * and labels).  The `Component' constructor object, with its static
	 * properties and functions, manages all components instances.
	 *
	 * @class
	 * @base
	 */
	var Component = Class.extend({

		id: 0,

		/**
		 * Flag to indicate that this is an instance of a component and  not the class object.
		 */
		isInstance: true,

		/**
		 * The Container instance or null if this component was not
		 * adopted by a counter by calling Component.adopt().
		 */
		container: null,

		/**
		 * Will be set in Component.define()
		 */
		type: null,

		/**
		 * @type {boolean} Whether or not this component is visible.
		 */
		visible: true,

		/**
		 * The type property is set in Component.define(), so components should only ever be instantiated through define.
		 * @constructor
		 */
		_constructor: function () {
			this.id = idCounter++;
			this.init();
		},

		adoptParent: function (container) {
			this.container = container;
		},

		/**
		 * Initializes this component.  To be implemented in subclasses.
		 */
		init: function () {},

		isVisible: function () {
			return this.visible;
		},

		/**
		 * Shows this component.
		 */
		show: function (show_opt) {
			if (false === show_opt) {
				this.hide();
				return;
			}
			// Only call container.childVisible if we switch from hidden to visible
			if (!this.visible) {
				this.visible = true;
				this.element.show();
				if (this.container) {
					this.container.childVisible(this, true);
				}
			}
		},

		/**
		 * Hides this component.
		 */
		hide: function () {
			// Only call container.childVisible if we switch from visible to hidden
			if (this.visible) {
				this.visible = false;
				this.element.hide();
				if (this.container) {
					this.container.childVisible(this, false);
				}
			}
		},

		focus: function () {
			this.element.focus();
			if (this.container) {
				this.container.childFocus(this);
			}
		},

		foreground: function () {
			if (this.container) {
				this.container.childForeground(this);
			}
		},

		enable: function (enable_opt) {},
		disable: function () {}
	});

	return Component;
});

define( 'jqueryui', [ 'jquery' ], function( jQuery ) {
/*! jQuery UI - v1.9.0 - 2012-10-18
* http://jqueryui.com
* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js
* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */

(function( $, undefined ) {

var uuid = 0,
	runiqueId = /^ui-id-\d+$/;

// prevent duplicate loading
// this is only a problem because we proxy existing functions
// and we don't want to double proxy them
$.ui = $.ui || {};
if ( $.ui.version ) {
	return;
}

$.extend( $.ui, {
	version: "1.9.0",

	keyCode: {
		BACKSPACE: 8,
		COMMA: 188,
		DELETE: 46,
		DOWN: 40,
		END: 35,
		ENTER: 13,
		ESCAPE: 27,
		HOME: 36,
		LEFT: 37,
		NUMPAD_ADD: 107,
		NUMPAD_DECIMAL: 110,
		NUMPAD_DIVIDE: 111,
		NUMPAD_ENTER: 108,
		NUMPAD_MULTIPLY: 106,
		NUMPAD_SUBTRACT: 109,
		PAGE_DOWN: 34,
		PAGE_UP: 33,
		PERIOD: 190,
		RIGHT: 39,
		SPACE: 32,
		TAB: 9,
		UP: 38
	}
});

// plugins
$.fn.extend({
	_focus: $.fn.focus,
	focus: function( delay, fn ) {
		return typeof delay === "number" ?
			this.each(function() {
				var elem = this;
				setTimeout(function() {
					$( elem ).focus();
					if ( fn ) {
						fn.call( elem );
					}
				}, delay );
			}) :
			this._focus.apply( this, arguments );
	},

	scrollParent: function() {
		var scrollParent;
		if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
			scrollParent = this.parents().filter(function() {
				return (/(relative|absolute|fixed)/).test($.css(this,'position')) && (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
			}).eq(0);
		} else {
			scrollParent = this.parents().filter(function() {
				return (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
			}).eq(0);
		}

		return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
	},

	zIndex: function( zIndex ) {
		if ( zIndex !== undefined ) {
			return this.css( "zIndex", zIndex );
		}

		if ( this.length ) {
			var elem = $( this[ 0 ] ), position, value;
			while ( elem.length && elem[ 0 ] !== document ) {
				// Ignore z-index if position is set to a value where z-index is ignored by the browser
				// This makes behavior of this function consistent across browsers
				// WebKit always returns auto if the element is positioned
				position = elem.css( "position" );
				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
					// IE returns 0 when zIndex is not specified
					// other browsers return a string
					// we ignore the case of nested elements with an explicit value of 0
					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
					value = parseInt( elem.css( "zIndex" ), 10 );
					if ( !isNaN( value ) && value !== 0 ) {
						return value;
					}
				}
				elem = elem.parent();
			}
		}

		return 0;
	},

	uniqueId: function() {
		return this.each(function() {
			if ( !this.id ) {
				this.id = "ui-id-" + (++uuid);
			}
		});
	},

	removeUniqueId: function() {
		return this.each(function() {
			if ( runiqueId.test( this.id ) ) {
				$( this ).removeAttr( "id" );
			}
		});
	}
});

// support: jQuery <1.8
if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
	$.each( [ "Width", "Height" ], function( i, name ) {
		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
			type = name.toLowerCase(),
			orig = {
				innerWidth: $.fn.innerWidth,
				innerHeight: $.fn.innerHeight,
				outerWidth: $.fn.outerWidth,
				outerHeight: $.fn.outerHeight
			};

		function reduce( elem, size, border, margin ) {
			$.each( side, function() {
				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
				if ( border ) {
					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
				}
				if ( margin ) {
					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
				}
			});
			return size;
		}

		$.fn[ "inner" + name ] = function( size ) {
			if ( size === undefined ) {
				return orig[ "inner" + name ].call( this );
			}

			return this.each(function() {
				$( this ).css( type, reduce( this, size ) + "px" );
			});
		};

		$.fn[ "outer" + name] = function( size, margin ) {
			if ( typeof size !== "number" ) {
				return orig[ "outer" + name ].call( this, size );
			}

			return this.each(function() {
				$( this).css( type, reduce( this, size, true, margin ) + "px" );
			});
		};
	});
}

// selectors
function focusable( element, isTabIndexNotNaN ) {
	var map, mapName, img,
		nodeName = element.nodeName.toLowerCase();
	if ( "area" === nodeName ) {
		map = element.parentNode;
		mapName = map.name;
		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
			return false;
		}
		img = $( "img[usemap=#" + mapName + "]" )[0];
		return !!img && visible( img );
	}
	return ( /input|select|textarea|button|object/.test( nodeName ) ?
		!element.disabled :
		"a" === nodeName ?
			element.href || isTabIndexNotNaN :
			isTabIndexNotNaN) &&
		// the element and all of its ancestors must be visible
		visible( element );
}

function visible( element ) {
	return !$( element ).parents().andSelf().filter(function() {
		return $.css( this, "visibility" ) === "hidden" ||
			$.expr.filters.hidden( this );
	}).length;
}

$.extend( $.expr[ ":" ], {
	data: $.expr.createPseudo ?
		$.expr.createPseudo(function( dataName ) {
			return function( elem ) {
				return !!$.data( elem, dataName );
			};
		}) :
		// support: jQuery <1.8
		function( elem, i, match ) {
			return !!$.data( elem, match[ 3 ] );
		},

	focusable: function( element ) {
		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
	},

	tabbable: function( element ) {
		var tabIndex = $.attr( element, "tabindex" ),
			isTabIndexNaN = isNaN( tabIndex );
		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
	}
});

// support
$(function() {
	var body = document.body,
		div = body.appendChild( div = document.createElement( "div" ) );

	// access offsetHeight before setting the style to prevent a layout bug
	// in IE 9 which causes the element to continue to take up space even
	// after it is removed from the DOM (#8026)
	div.offsetHeight;

	$.extend( div.style, {
		minHeight: "100px",
		height: "auto",
		padding: 0,
		borderWidth: 0
	});

	$.support.minHeight = div.offsetHeight === 100;
	$.support.selectstart = "onselectstart" in div;

	// set display to none to avoid a layout bug in IE
	// http://dev.jquery.com/ticket/4014
	body.removeChild( div ).style.display = "none";
});





// deprecated

$.fn.extend({
	disableSelection: function() {
		return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
			".ui-disableSelection", function( event ) {
				event.preventDefault();
			});
	},

	enableSelection: function() {
		return this.unbind( ".ui-disableSelection" );
	}
});

$.extend( $.ui, {
	// $.ui.plugin is deprecated.  Use the proxy pattern instead.
	plugin: {
		add: function( module, option, set ) {
			var i,
				proto = $.ui[ module ].prototype;
			for ( i in set ) {
				proto.plugins[ i ] = proto.plugins[ i ] || [];
				proto.plugins[ i ].push( [ option, set[ i ] ] );
			}
		},
		call: function( instance, name, args ) {
			var i,
				set = instance.plugins[ name ];
			if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
				return;
			}

			for ( i = 0; i < set.length; i++ ) {
				if ( instance.options[ set[ i ][ 0 ] ] ) {
					set[ i ][ 1 ].apply( instance.element, args );
				}
			}
		}
	},

	contains: $.contains,

	// only used by resizable
	hasScroll: function( el, a ) {

		//If overflow is hidden, the element might have extra content, but the user wants to hide it
		if ( $( el ).css( "overflow" ) === "hidden") {
			return false;
		}

		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
			has = false;

		if ( el[ scroll ] > 0 ) {
			return true;
		}

		// TODO: determine which cases actually cause this to happen
		// if the element doesn't have the scroll set, see if it's possible to
		// set the scroll
		el[ scroll ] = 1;
		has = ( el[ scroll ] > 0 );
		el[ scroll ] = 0;
		return has;
	},

	// these are odd functions, fix the API or move into individual plugins
	isOverAxis: function( x, reference, size ) {
		//Determines when x coordinate is over "b" element axis
		return ( x > reference ) && ( x < ( reference + size ) );
	},
	isOver: function( y, x, top, left, height, width ) {
		//Determines when x, y coordinates is over "b" element
		return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
	}
});

})( jQuery );
(function( $, undefined ) {

var uuid = 0,
	slice = Array.prototype.slice,
	_cleanData = $.cleanData;
$.cleanData = function( elems ) {
	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
		try {
			$( elem ).triggerHandler( "remove" );
		// http://bugs.jquery.com/ticket/8235
		} catch( e ) {}
	}
	_cleanData( elems );
};

$.widget = function( name, base, prototype ) {
	var fullName, existingConstructor, constructor, basePrototype,
		namespace = name.split( "." )[ 0 ];

	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
		return !!$.data( elem, fullName );
	};

	$[ namespace ] = $[ namespace ] || {};
	existingConstructor = $[ namespace ][ name ];
	constructor = $[ namespace ][ name ] = function( options, element ) {
		// allow instantiation without "new" keyword
		if ( !this._createWidget ) {
			return new constructor( options, element );
		}

		// allow instantiation without initializing for simple inheritance
		// must use "new" keyword (the code above always passes args)
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	};
	// extend with the existing constructor to carry over any static properties
	$.extend( constructor, existingConstructor, {
		version: prototype.version,
		// copy the object used to create the prototype in case we need to
		// redefine the widget later
		_proto: $.extend( {}, prototype ),
		// track widgets that inherit from this widget in case this widget is
		// redefined after a widget inherits from it
		_childConstructors: []
	});

	basePrototype = new base();
	// we need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
	basePrototype.options = $.widget.extend( {}, basePrototype.options );
	$.each( prototype, function( prop, value ) {
		if ( $.isFunction( value ) ) {
			prototype[ prop ] = (function() {
				var _super = function() {
						return base.prototype[ prop ].apply( this, arguments );
					},
					_superApply = function( args ) {
						return base.prototype[ prop ].apply( this, args );
					};
				return function() {
					var __super = this._super,
						__superApply = this._superApply,
						returnValue;

					this._super = _super;
					this._superApply = _superApply;

					returnValue = value.apply( this, arguments );

					this._super = __super;
					this._superApply = __superApply;

					return returnValue;
				};
			})();
		}
	});
	constructor.prototype = $.widget.extend( basePrototype, {
		// TODO: remove support for widgetEventPrefix
		// always use the name + a colon as the prefix, e.g., draggable:start
		// don't prefix for widgets that aren't DOM-based
		widgetEventPrefix: name
	}, prototype, {
		constructor: constructor,
		namespace: namespace,
		widgetName: name,
		// TODO remove widgetBaseClass, see #8155
		widgetBaseClass: fullName,
		widgetFullName: fullName
	});

	// If this widget is being redefined then we need to find all widgets that
	// are inheriting from it and redefine all of them so that they inherit from
	// the new version of this widget. We're essentially trying to replace one
	// level in the prototype chain.
	if ( existingConstructor ) {
		$.each( existingConstructor._childConstructors, function( i, child ) {
			var childPrototype = child.prototype;

			// redefine the child widget using the same prototype that was
			// originally used, but inherit from the new version of the base
			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
		});
		// remove the list of existing child constructors from the old constructor
		// so the old child constructors can be garbage collected
		delete existingConstructor._childConstructors;
	} else {
		base._childConstructors.push( constructor );
	}

	$.widget.bridge( name, constructor );
};

$.widget.extend = function( target ) {
	var input = slice.call( arguments, 1 ),
		inputIndex = 0,
		inputLength = input.length,
		key,
		value;
	for ( ; inputIndex < inputLength; inputIndex++ ) {
		for ( key in input[ inputIndex ] ) {
			value = input[ inputIndex ][ key ];
			if (input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
				target[ key ] = $.isPlainObject( value ) ? $.widget.extend( {}, target[ key ], value ) : value;
			}
		}
	}
	return target;
};

$.widget.bridge = function( name, object ) {
	var fullName = object.prototype.widgetFullName;
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string",
			args = slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.widget.extend.apply( null, [ options ].concat(args) ) :
			options;

		if ( isMethodCall ) {
			this.each(function() {
				var methodValue,
					instance = $.data( this, fullName );
				if ( !instance ) {
					return $.error( "cannot call methods on " + name + " prior to initialization; " +
						"attempted to call method '" + options + "'" );
				}
				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
				}
				methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue && methodValue.jquery ?
						returnValue.pushStack( methodValue.get() ) :
						methodValue;
					return false;
				}
			});
		} else {
			this.each(function() {
				var instance = $.data( this, fullName );
				if ( instance ) {
					instance.option( options || {} )._init();
				} else {
					new object( options, this );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( options, element ) {};
$.Widget._childConstructors = [];

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	defaultElement: "<div>",
	options: {
		disabled: false,

		// callbacks
		create: null
	},
	_createWidget: function( options, element ) {
		element = $( element || this.defaultElement || this )[ 0 ];
		this.element = $( element );
		this.uuid = uuid++;
		this.eventNamespace = "." + this.widgetName + this.uuid;
		this.options = $.widget.extend( {},
			this.options,
			this._getCreateOptions(),
			options );

		this.bindings = $();
		this.hoverable = $();
		this.focusable = $();

		if ( element !== this ) {
			// 1.9 BC for #7810
			// TODO remove dual storage
			$.data( element, this.widgetName, this );
			$.data( element, this.widgetFullName, this );
			this._on({ remove: "destroy" });
			this.document = $( element.style ?
				// element within the document
				element.ownerDocument :
				// element is window or document
				element.document || element );
			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
		}

		this._create();
		this._trigger( "create", null, this._getCreateEventData() );
		this._init();
	},
	_getCreateOptions: $.noop,
	_getCreateEventData: $.noop,
	_create: $.noop,
	_init: $.noop,

	destroy: function() {
		this._destroy();
		// we can probably remove the unbind calls in 2.0
		// all event bindings should go through this._on()
		this.element
			.unbind( this.eventNamespace )
			// 1.9 BC for #7810
			// TODO remove dual storage
			.removeData( this.widgetName )
			.removeData( this.widgetFullName )
			// support: jquery <1.6.3
			// http://bugs.jquery.com/ticket/9413
			.removeData( $.camelCase( this.widgetFullName ) );
		this.widget()
			.unbind( this.eventNamespace )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetFullName + "-disabled " +
				"ui-state-disabled" );

		// clean up events and states
		this.bindings.unbind( this.eventNamespace );
		this.hoverable.removeClass( "ui-state-hover" );
		this.focusable.removeClass( "ui-state-focus" );
	},
	_destroy: $.noop,

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key,
			parts,
			curOption,
			i;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.widget.extend( {}, this.options );
		}

		if ( typeof key === "string" ) {
			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
			options = {};
			parts = key.split( "." );
			key = parts.shift();
			if ( parts.length ) {
				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
				for ( i = 0; i < parts.length - 1; i++ ) {
					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
					curOption = curOption[ parts[ i ] ];
				}
				key = parts.pop();
				if ( value === undefined ) {
					return curOption[ key ] === undefined ? null : curOption[ key ];
				}
				curOption[ key ] = value;
			} else {
				if ( value === undefined ) {
					return this.options[ key ] === undefined ? null : this.options[ key ];
				}
				options[ key ] = value;
			}
		}

		this._setOptions( options );

		return this;
	},
	_setOptions: function( options ) {
		var key;

		for ( key in options ) {
			this._setOption( key, options[ key ] );
		}

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
				.attr( "aria-disabled", value );
			this.hoverable.removeClass( "ui-state-hover" );
			this.focusable.removeClass( "ui-state-focus" );
		}

		return this;
	},

	enable: function() {
		return this._setOption( "disabled", false );
	},
	disable: function() {
		return this._setOption( "disabled", true );
	},

	_on: function( element, handlers ) {
		// no element argument, shuffle and use this.element
		if ( !handlers ) {
			handlers = element;
			element = this.element;
		} else {
			// accept selectors, DOM elements
			element = $( element );
			this.bindings = this.bindings.add( element );
		}

		var instance = this;
		$.each( handlers, function( event, handler ) {
			function handlerProxy() {
				// allow widgets to customize the disabled handling
				// - disabled as an array instead of boolean
				// - disabled class as method for disabling individual parts
				if ( instance.options.disabled === true ||
						$( this ).hasClass( "ui-state-disabled" ) ) {
					return;
				}
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			}

			// copy the guid so direct unbinding works
			if ( typeof handler !== "string" ) {
				handlerProxy.guid = handler.guid =
					handler.guid || handlerProxy.guid || $.guid++;
			}

			var match = event.match( /^(\w+)\s*(.*)$/ ),
				eventName = match[1] + instance.eventNamespace,
				selector = match[2];
			if ( selector ) {
				instance.widget().delegate( selector, eventName, handlerProxy );
			} else {
				element.bind( eventName, handlerProxy );
			}
		});
	},

	_off: function( element, eventName ) {
		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
		element.unbind( eventName ).undelegate( eventName );
	},

	_delay: function( handler, delay ) {
		function handlerProxy() {
			return ( typeof handler === "string" ? instance[ handler ] : handler )
				.apply( instance, arguments );
		}
		var instance = this;
		return setTimeout( handlerProxy, delay || 0 );
	},

	_hoverable: function( element ) {
		this.hoverable = this.hoverable.add( element );
		this._on( element, {
			mouseenter: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-hover" );
			},
			mouseleave: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-hover" );
			}
		});
	},

	_focusable: function( element ) {
		this.focusable = this.focusable.add( element );
		this._on( element, {
			focusin: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-focus" );
			},
			focusout: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-focus" );
			}
		});
	},

	_trigger: function( type, event, data ) {
		var prop, orig,
			callback = this.options[ type ];

		data = data || {};
		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		// the original event may come from any element
		// so we need to reset the target on the new event
		event.target = this.element[ 0 ];

		// copy original event properties over to the new event
		orig = event.originalEvent;
		if ( orig ) {
			for ( prop in orig ) {
				if ( !( prop in event ) ) {
					event[ prop ] = orig[ prop ];
				}
			}
		}

		this.element.trigger( event, data );
		return !( $.isFunction( callback ) &&
			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
			event.isDefaultPrevented() );
	}
};

$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
		if ( typeof options === "string" ) {
			options = { effect: options };
		}
		var hasOptions,
			effectName = !options ?
				method :
				options === true || typeof options === "number" ?
					defaultEffect :
					options.effect || defaultEffect;
		options = options || {};
		if ( typeof options === "number" ) {
			options = { duration: options };
		}
		hasOptions = !$.isEmptyObject( options );
		options.complete = callback;
		if ( options.delay ) {
			element.delay( options.delay );
		}
		if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
			element[ method ]( options );
		} else if ( effectName !== method && element[ effectName ] ) {
			element[ effectName ]( options.duration, options.easing, callback );
		} else {
			element.queue(function( next ) {
				$( this )[ method ]();
				if ( callback ) {
					callback.call( element[ 0 ] );
				}
				next();
			});
		}
	};
});

// DEPRECATED
if ( $.uiBackCompat !== false ) {
	$.Widget.prototype._getCreateOptions = function() {
		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
	};
}

})( jQuery );
(function( $, undefined ) {

var mouseHandled = false;
$( document ).mouseup( function( e ) {
	mouseHandled = false;
});

$.widget("ui.mouse", {
	version: "1.9.0",
	options: {
		cancel: 'input,textarea,button,select,option',
		distance: 1,
		delay: 0
	},
	_mouseInit: function() {
		var that = this;

		this.element
			.bind('mousedown.'+this.widgetName, function(event) {
				return that._mouseDown(event);
			})
			.bind('click.'+this.widgetName, function(event) {
				if (true === $.data(event.target, that.widgetName + '.preventClickEvent')) {
					$.removeData(event.target, that.widgetName + '.preventClickEvent');
					event.stopImmediatePropagation();
					return false;
				}
			});

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	_mouseDestroy: function() {
		this.element.unbind('.'+this.widgetName);
		if ( this._mouseMoveDelegate ) {
			$(document)
				.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
				.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
		}
	},

	_mouseDown: function(event) {
		// don't let more than one widget handle mouseStart
		if( mouseHandled ) { return; }

		// we may have missed mouseup (out of window)
		(this._mouseStarted && this._mouseUp(event));

		this._mouseDownEvent = event;

		var that = this,
			btnIsLeft = (event.which === 1),
			// event.target.nodeName works around a bug in IE 8 with
			// disabled inputs (#7620)
			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
			return true;
		}

		this.mouseDelayMet = !this.options.delay;
		if (!this.mouseDelayMet) {
			this._mouseDelayTimer = setTimeout(function() {
				that.mouseDelayMet = true;
			}, this.options.delay);
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted = (this._mouseStart(event) !== false);
			if (!this._mouseStarted) {
				event.preventDefault();
				return true;
			}
		}

		// Click event may never have fired (Gecko & Opera)
		if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
			$.removeData(event.target, this.widgetName + '.preventClickEvent');
		}

		// these delegates are required to keep context
		this._mouseMoveDelegate = function(event) {
			return that._mouseMove(event);
		};
		this._mouseUpDelegate = function(event) {
			return that._mouseUp(event);
		};
		$(document)
			.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		event.preventDefault();
		
		mouseHandled = true;
		return true;
	},

	_mouseMove: function(event) {
		// IE mouseup check - mouseup happened when mouse was out of window
		if ($.browser.msie && !(document.documentMode >= 9) && !event.button) {
			return this._mouseUp(event);
		}

		if (this._mouseStarted) {
			this._mouseDrag(event);
			return event.preventDefault();
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted =
				(this._mouseStart(this._mouseDownEvent, event) !== false);
			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
		}

		return !this._mouseStarted;
	},

	_mouseUp: function(event) {
		$(document)
			.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		if (this._mouseStarted) {
			this._mouseStarted = false;

			if (event.target === this._mouseDownEvent.target) {
				$.data(event.target, this.widgetName + '.preventClickEvent', true);
			}

			this._mouseStop(event);
		}

		return false;
	},

	_mouseDistanceMet: function(event) {
		return (Math.max(
				Math.abs(this._mouseDownEvent.pageX - event.pageX),
				Math.abs(this._mouseDownEvent.pageY - event.pageY)
			) >= this.options.distance
		);
	},

	_mouseDelayMet: function(event) {
		return this.mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	_mouseStart: function(event) {},
	_mouseDrag: function(event) {},
	_mouseStop: function(event) {},
	_mouseCapture: function(event) { return true; }
});

})(jQuery);
(function( $, undefined ) {

$.ui = $.ui || {};

var cachedScrollbarWidth,
	max = Math.max,
	abs = Math.abs,
	round = Math.round,
	rhorizontal = /left|center|right/,
	rvertical = /top|center|bottom/,
	roffset = /[\+\-]\d+%?/,
	rposition = /^\w+/,
	rpercent = /%$/,
	_position = $.fn.position;

function getOffsets( offsets, width, height ) {
	return [
		parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
		parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
	];
}
function parseCss( element, property ) {
	return parseInt( $.css( element, property ), 10 ) || 0;
}

$.position = {
	scrollbarWidth: function() {
		if ( cachedScrollbarWidth !== undefined ) {
			return cachedScrollbarWidth;
		}
		var w1, w2,
			div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
			innerDiv = div.children()[0];

		$( "body" ).append( div );
		w1 = innerDiv.offsetWidth;
		div.css( "overflow", "scroll" );

		w2 = innerDiv.offsetWidth;

		if ( w1 === w2 ) {
			w2 = div[0].clientWidth;
		}

		div.remove();

		return (cachedScrollbarWidth = w1 - w2);
	},
	getScrollInfo: function( within ) {
		var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
			overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
			hasOverflowX = overflowX === "scroll" ||
				( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
			hasOverflowY = overflowY === "scroll" ||
				( overflowY === "auto" && within.height < within.element[0].scrollHeight );
		return {
			width: hasOverflowX ? $.position.scrollbarWidth() : 0,
			height: hasOverflowY ? $.position.scrollbarWidth() : 0
		};
	},
	getWithinInfo: function( element ) {
		var withinElement = $( element || window ),
			isWindow = $.isWindow( withinElement[0] );
		return {
			element: withinElement,
			isWindow: isWindow,
			offset: withinElement.offset() || { left: 0, top: 0 },
			scrollLeft: withinElement.scrollLeft(),
			scrollTop: withinElement.scrollTop(),
			width: isWindow ? withinElement.width() : withinElement.outerWidth(),
			height: isWindow ? withinElement.height() : withinElement.outerHeight()
		};
	}
};

$.fn.position = function( options ) {
	if ( !options || !options.of ) {
		return _position.apply( this, arguments );
	}

	// make a copy, we don't want to modify arguments
	options = $.extend( {}, options );

	var atOffset, targetWidth, targetHeight, targetOffset, basePosition,
		target = $( options.of ),
		within = $.position.getWithinInfo( options.within ),
		scrollInfo = $.position.getScrollInfo( within ),
		targetElem = target[0],
		collision = ( options.collision || "flip" ).split( " " ),
		offsets = {};

	if ( targetElem.nodeType === 9 ) {
		targetWidth = target.width();
		targetHeight = target.height();
		targetOffset = { top: 0, left: 0 };
	} else if ( $.isWindow( targetElem ) ) {
		targetWidth = target.width();
		targetHeight = target.height();
		targetOffset = { top: target.scrollTop(), left: target.scrollLeft() };
	} else if ( targetElem.preventDefault ) {
		// force left top to allow flipping
		options.at = "left top";
		targetWidth = targetHeight = 0;
		targetOffset = { top: targetElem.pageY, left: targetElem.pageX };
	} else {
		targetWidth = target.outerWidth();
		targetHeight = target.outerHeight();
		targetOffset = target.offset();
	}
	// clone to reuse original targetOffset later
	basePosition = $.extend( {}, targetOffset );

	// force my and at to have valid horizontal and vertical positions
	// if a value is missing or invalid, it will be converted to center
	$.each( [ "my", "at" ], function() {
		var pos = ( options[ this ] || "" ).split( " " ),
			horizontalOffset,
			verticalOffset;

		if ( pos.length === 1) {
			pos = rhorizontal.test( pos[ 0 ] ) ?
				pos.concat( [ "center" ] ) :
				rvertical.test( pos[ 0 ] ) ?
					[ "center" ].concat( pos ) :
					[ "center", "center" ];
		}
		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";

		// calculate offsets
		horizontalOffset = roffset.exec( pos[ 0 ] );
		verticalOffset = roffset.exec( pos[ 1 ] );
		offsets[ this ] = [
			horizontalOffset ? horizontalOffset[ 0 ] : 0,
			verticalOffset ? verticalOffset[ 0 ] : 0
		];

		// reduce to just the positions without the offsets
		options[ this ] = [
			rposition.exec( pos[ 0 ] )[ 0 ],
			rposition.exec( pos[ 1 ] )[ 0 ]
		];
	});

	// normalize collision option
	if ( collision.length === 1 ) {
		collision[ 1 ] = collision[ 0 ];
	}

	if ( options.at[ 0 ] === "right" ) {
		basePosition.left += targetWidth;
	} else if ( options.at[ 0 ] === "center" ) {
		basePosition.left += targetWidth / 2;
	}

	if ( options.at[ 1 ] === "bottom" ) {
		basePosition.top += targetHeight;
	} else if ( options.at[ 1 ] === "center" ) {
		basePosition.top += targetHeight / 2;
	}

	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
	basePosition.left += atOffset[ 0 ];
	basePosition.top += atOffset[ 1 ];

	return this.each(function() {
		var collisionPosition, using,
			elem = $( this ),
			elemWidth = elem.outerWidth(),
			elemHeight = elem.outerHeight(),
			marginLeft = parseCss( this, "marginLeft" ),
			marginTop = parseCss( this, "marginTop" ),
			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
			position = $.extend( {}, basePosition ),
			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );

		if ( options.my[ 0 ] === "right" ) {
			position.left -= elemWidth;
		} else if ( options.my[ 0 ] === "center" ) {
			position.left -= elemWidth / 2;
		}

		if ( options.my[ 1 ] === "bottom" ) {
			position.top -= elemHeight;
		} else if ( options.my[ 1 ] === "center" ) {
			position.top -= elemHeight / 2;
		}

		position.left += myOffset[ 0 ];
		position.top += myOffset[ 1 ];

		// if the browser doesn't support fractions, then round for consistent results
		if ( !$.support.offsetFractions ) {
			position.left = round( position.left );
			position.top = round( position.top );
		}

		collisionPosition = {
			marginLeft: marginLeft,
			marginTop: marginTop
		};

		$.each( [ "left", "top" ], function( i, dir ) {
			if ( $.ui.position[ collision[ i ] ] ) {
				$.ui.position[ collision[ i ] ][ dir ]( position, {
					targetWidth: targetWidth,
					targetHeight: targetHeight,
					elemWidth: elemWidth,
					elemHeight: elemHeight,
					collisionPosition: collisionPosition,
					collisionWidth: collisionWidth,
					collisionHeight: collisionHeight,
					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
					my: options.my,
					at: options.at,
					within: within,
					elem : elem
				});
			}
		});

		if ( $.fn.bgiframe ) {
			elem.bgiframe();
		}

		if ( options.using ) {
			// adds feedback as second argument to using callback, if present
			using = function( props ) {
				var left = targetOffset.left - position.left,
					right = left + targetWidth - elemWidth,
					top = targetOffset.top - position.top,
					bottom = top + targetHeight - elemHeight,
					feedback = {
						target: {
							element: target,
							left: targetOffset.left,
							top: targetOffset.top,
							width: targetWidth,
							height: targetHeight
						},
						element: {
							element: elem,
							left: position.left,
							top: position.top,
							width: elemWidth,
							height: elemHeight
						},
						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
					};
				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
					feedback.horizontal = "center";
				}
				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
					feedback.vertical = "middle";
				}
				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
					feedback.important = "horizontal";
				} else {
					feedback.important = "vertical";
				}
				options.using.call( this, props, feedback );
			};
		}

		elem.offset( $.extend( position, { using: using } ) );
	});
};

$.ui.position = {
	fit: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
				outerWidth = within.width,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = withinOffset - collisionPosLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
				newOverRight;

			// element is wider than within
			if ( data.collisionWidth > outerWidth ) {
				// element is initially over the left side of within
				if ( overLeft > 0 && overRight <= 0 ) {
					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
					position.left += overLeft - newOverRight;
				// element is initially over right side of within
				} else if ( overRight > 0 && overLeft <= 0 ) {
					position.left = withinOffset;
				// element is initially over both left and right sides of within
				} else {
					if ( overLeft > overRight ) {
						position.left = withinOffset + outerWidth - data.collisionWidth;
					} else {
						position.left = withinOffset;
					}
				}
			// too far left -> align with left edge
			} else if ( overLeft > 0 ) {
				position.left += overLeft;
			// too far right -> align with right edge
			} else if ( overRight > 0 ) {
				position.left -= overRight;
			// adjust based on position and margin
			} else {
				position.left = max( position.left - collisionPosLeft, position.left );
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
				outerHeight = data.within.height,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = withinOffset - collisionPosTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
				newOverBottom;

			// element is taller than within
			if ( data.collisionHeight > outerHeight ) {
				// element is initially over the top of within
				if ( overTop > 0 && overBottom <= 0 ) {
					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
					position.top += overTop - newOverBottom;
				// element is initially over bottom of within
				} else if ( overBottom > 0 && overTop <= 0 ) {
					position.top = withinOffset;
				// element is initially over both top and bottom of within
				} else {
					if ( overTop > overBottom ) {
						position.top = withinOffset + outerHeight - data.collisionHeight;
					} else {
						position.top = withinOffset;
					}
				}
			// too far up -> align with top
			} else if ( overTop > 0 ) {
				position.top += overTop;
			// too far down -> align with bottom edge
			} else if ( overBottom > 0 ) {
				position.top -= overBottom;
			// adjust based on position and margin
			} else {
				position.top = max( position.top - collisionPosTop, position.top );
			}
		}
	},
	flip: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.left + within.scrollLeft,
				outerWidth = within.width,
				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = collisionPosLeft - offsetLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
				myOffset = data.my[ 0 ] === "left" ?
					-data.elemWidth :
					data.my[ 0 ] === "right" ?
						data.elemWidth :
						0,
				atOffset = data.at[ 0 ] === "left" ?
					data.targetWidth :
					data.at[ 0 ] === "right" ?
						-data.targetWidth :
						0,
				offset = -2 * data.offset[ 0 ],
				newOverRight,
				newOverLeft;

			if ( overLeft < 0 ) {
				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
					position.left += myOffset + atOffset + offset;
				}
			}
			else if ( overRight > 0 ) {
				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
					position.left += myOffset + atOffset + offset;
				}
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.top + within.scrollTop,
				outerHeight = within.height,
				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = collisionPosTop - offsetTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
				top = data.my[ 1 ] === "top",
				myOffset = top ?
					-data.elemHeight :
					data.my[ 1 ] === "bottom" ?
						data.elemHeight :
						0,
				atOffset = data.at[ 1 ] === "top" ?
					data.targetHeight :
					data.at[ 1 ] === "bottom" ?
						-data.targetHeight :
						0,
				offset = -2 * data.offset[ 1 ],
				newOverTop,
				newOverBottom;
			if ( overTop < 0 ) {
				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
				if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
					position.top += myOffset + atOffset + offset;
				}
			}
			else if ( overBottom > 0 ) {
				newOverTop = position.top -  data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
				if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
					position.top += myOffset + atOffset + offset;
				}
			}
		}
	},
	flipfit: {
		left: function() {
			$.ui.position.flip.left.apply( this, arguments );
			$.ui.position.fit.left.apply( this, arguments );
		},
		top: function() {
			$.ui.position.flip.top.apply( this, arguments );
			$.ui.position.fit.top.apply( this, arguments );
		}
	}
};

// fraction support test
(function () {
	var testElement, testElementParent, testElementStyle, offsetLeft, i,
		body = document.getElementsByTagName( "body" )[ 0 ],
		div = document.createElement( "div" );

	//Create a "fake body" for testing based on method used in jQuery.support
	testElement = document.createElement( body ? "div" : "body" );
	testElementStyle = {
		visibility: "hidden",
		width: 0,
		height: 0,
		border: 0,
		margin: 0,
		background: "none"
	};
	if ( body ) {
		$.extend( testElementStyle, {
			position: "absolute",
			left: "-1000px",
			top: "-1000px"
		});
	}
	for ( i in testElementStyle ) {
		testElement.style[ i ] = testElementStyle[ i ];
	}
	testElement.appendChild( div );
	testElementParent = body || document.documentElement;
	testElementParent.insertBefore( testElement, testElementParent.firstChild );

	div.style.cssText = "position: absolute; left: 10.7432222px;";

	offsetLeft = $( div ).offset().left;
	$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;

	testElement.innerHTML = "";
	testElementParent.removeChild( testElement );
})();

// DEPRECATED
if ( $.uiBackCompat !== false ) {
	// offset option
	(function( $ ) {
		var _position = $.fn.position;
		$.fn.position = function( options ) {
			if ( !options || !options.offset ) {
				return _position.call( this, options );
			}
			var offset = options.offset.split( " " ),
				at = options.at.split( " " );
			if ( offset.length === 1 ) {
				offset[ 1 ] = offset[ 0 ];
			}
			if ( /^\d/.test( offset[ 0 ] ) ) {
				offset[ 0 ] = "+" + offset[ 0 ];
			}
			if ( /^\d/.test( offset[ 1 ] ) ) {
				offset[ 1 ] = "+" + offset[ 1 ];
			}
			if ( at.length === 1 ) {
				if ( /left|center|right/.test( at[ 0 ] ) ) {
					at[ 1 ] = "center";
				} else {
					at[ 1 ] = at[ 0 ];
					at[ 0 ] = "center";
				}
			}
			return _position.call( this, $.extend( options, {
				at: at[ 0 ] + offset[ 0 ] + " " + at[ 1 ] + offset[ 1 ],
				offset: undefined
			} ) );
		};
	}( jQuery ) );
}

}( jQuery ) );
(function( $, undefined ) {

$.widget("ui.draggable", $.ui.mouse, {
	version: "1.9.0",
	widgetEventPrefix: "drag",
	options: {
		addClasses: true,
		appendTo: "parent",
		axis: false,
		connectToSortable: false,
		containment: false,
		cursor: "auto",
		cursorAt: false,
		grid: false,
		handle: false,
		helper: "original",
		iframeFix: false,
		opacity: false,
		refreshPositions: false,
		revert: false,
		revertDuration: 500,
		scope: "default",
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		snap: false,
		snapMode: "both",
		snapTolerance: 20,
		stack: false,
		zIndex: false
	},
	_create: function() {

		if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
			this.element[0].style.position = 'relative';

		(this.options.addClasses && this.element.addClass("ui-draggable"));
		(this.options.disabled && this.element.addClass("ui-draggable-disabled"));

		this._mouseInit();

	},

	_destroy: function() {
		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
		this._mouseDestroy();
	},

	_mouseCapture: function(event) {

		var o = this.options;

		// among others, prevent a drag on a resizable-handle
		if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
			return false;

		//Quit if we're not on a valid handle
		this.handle = this._getHandle(event);
		if (!this.handle)
			return false;
		
		$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
			$('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
			.css({
				width: this.offsetWidth+"px", height: this.offsetHeight+"px",
				position: "absolute", opacity: "0.001", zIndex: 1000
			})
			.css($(this).offset())
			.appendTo("body");
		});

		return true;

	},

	_mouseStart: function(event) {

		var o = this.options;

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		this.helper.addClass("ui-draggable-dragging");

		//Cache the helper size
		this._cacheHelperProportions();

		//If ddmanager is used for droppables, set the global draggable
		if($.ui.ddmanager)
			$.ui.ddmanager.current = this;

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Store the helper's css position
		this.cssPosition = this.helper.css("position");
		this.scrollParent = this.helper.scrollParent();

		//The element's absolute position on the page minus margins
		this.offset = this.positionAbs = this.element.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		//Generate the original position
		this.originalPosition = this.position = this._generatePosition(event);
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Set a containment if given in the options
		if(o.containment)
			this._setContainment();

		//Trigger event + callbacks
		if(this._trigger("start", event) === false) {
			this._clear();
			return false;
		}

		//Recache the helper size
		this._cacheHelperProportions();

		//Prepare the droppable offsets
		if ($.ui.ddmanager && !o.dropBehaviour)
			$.ui.ddmanager.prepareOffsets(this, event);

		
		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
		
		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
		if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
		
		return true;
	},

	_mouseDrag: function(event, noPropagation) {

		//Compute the helpers position
		this.position = this._generatePosition(event);
		this.positionAbs = this._convertPositionTo("absolute");

		//Call plugins and callbacks and use the resulting position if something is returned
		if (!noPropagation) {
			var ui = this._uiHash();
			if(this._trigger('drag', event, ui) === false) {
				this._mouseUp({});
				return false;
			}
			this.position = ui.position;
		}

		if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
		if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
		if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);

		return false;
	},

	_mouseStop: function(event) {

		//If we are using droppables, inform the manager about the drop
		var dropped = false;
		if ($.ui.ddmanager && !this.options.dropBehaviour)
			dropped = $.ui.ddmanager.drop(this, event);

		//if a drop comes from outside (a sortable)
		if(this.dropped) {
			dropped = this.dropped;
			this.dropped = false;
		}
		
		//if the original element is no longer in the DOM don't bother to continue (see #8269)
		var element = this.element[0], elementInDom = false;
		while ( element && (element = element.parentNode) ) {
			if (element == document ) {
				elementInDom = true;
			}
		}
		if ( !elementInDom && this.options.helper === "original" )
			return false;

		if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
			var that = this;
			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
				if(that._trigger("stop", event) !== false) {
					that._clear();
				}
			});
		} else {
			if(this._trigger("stop", event) !== false) {
				this._clear();
			}
		}

		return false;
	},
	
	_mouseUp: function(event) {
		//Remove frame helpers
		$("div.ui-draggable-iframeFix").each(function() { 
			this.parentNode.removeChild(this); 
		});
		
		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
		if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
		
		return $.ui.mouse.prototype._mouseUp.call(this, event);
	},
	
	cancel: function() {
		
		if(this.helper.is(".ui-draggable-dragging")) {
			this._mouseUp({});
		} else {
			this._clear();
		}
		
		return this;
		
	},

	_getHandle: function(event) {

		var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
		$(this.options.handle, this.element)
			.find("*")
			.andSelf()
			.each(function() {
				if(this == event.target) handle = true;
			});

		return handle;

	},

	_createHelper: function(event) {

		var o = this.options;
		var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);

		if(!helper.parents('body').length)
			helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));

		if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
			helper.css("position", "absolute");

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj == 'string') {
			obj = obj.split(' ');
		}
		if ($.isArray(obj)) {
			obj = {left: +obj[0], top: +obj[1] || 0};
		}
		if ('left' in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ('right' in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ('top' in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ('bottom' in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_getParentOffset: function() {

		//Get the offsetParent and cache its position
		this.offsetParent = this.helper.offsetParent();
		var po = this.offsetParent.offset();

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
		|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
			po = { top: 0, left: 0 };

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};

	},

	_getRelativeOffset: function() {

		if(this.cssPosition == "relative") {
			var p = this.element.position();
			return {
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
			};
		} else {
			return { top: 0, left: 0 };
		}

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.element.css("marginLeft"),10) || 0),
			top: (parseInt(this.element.css("marginTop"),10) || 0),
			right: (parseInt(this.element.css("marginRight"),10) || 0),
			bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var o = this.options;
		if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
		if(o.containment == 'document' || o.containment == 'window') this.containment = [
			o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
			o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
			(o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
			(o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
		];

		if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
			var c = $(o.containment);
			var ce = c[0]; if(!ce) return;
			var co = c.offset();
			var over = ($(ce).css("overflow") != 'hidden');

			this.containment = [
				(parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
				(parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
				(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
				(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top  - this.margins.bottom
			];
			this.relative_container = c;

		} else if(o.containment.constructor == Array) {
			this.containment = o.containment;
		}

	},

	_convertPositionTo: function(d, pos) {

		if(!pos) pos = this.position;
		var mod = d == "absolute" ? 1 : -1;
		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		return {
			top: (
				pos.top																	// The absolute mouse position
				+ this.offset.relative.top * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.top * mod											// The offsetParent's offset without borders (offset + border)
				- ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
			),
			left: (
				pos.left																// The absolute mouse position
				+ this.offset.relative.left * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.left * mod											// The offsetParent's offset without borders (offset + border)
				- ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
			)
		};

	},

	_generatePosition: function(event) {

		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
		var pageX = event.pageX;
		var pageY = event.pageY;

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		if(this.originalPosition) { //If we are not dragging yet, we won't check for options
			var containment;
			if(this.containment) {
			if (this.relative_container){
				var co = this.relative_container.offset();
				containment = [ this.containment[0] + co.left,
					this.containment[1] + co.top,
					this.containment[2] + co.left,
					this.containment[3] + co.top ];
			}
			else {
				containment = this.containment;
			}

				if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
				if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
				if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
				if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
			}

			if(o.grid) {
				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
				var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
				pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
				pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

		}

		return {
			top: (
				pageY																// The absolute mouse position
				- this.offset.click.top													// Click offset (relative to the element)
				- this.offset.relative.top												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.top												// The offsetParent's offset without borders (offset + border)
				+ ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
			),
			left: (
				pageX																// The absolute mouse position
				- this.offset.click.left												// Click offset (relative to the element)
				- this.offset.relative.left												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.left												// The offsetParent's offset without borders (offset + border)
				+ ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
			)
		};

	},

	_clear: function() {
		this.helper.removeClass("ui-draggable-dragging");
		if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
		//if($.ui.ddmanager) $.ui.ddmanager.current = null;
		this.helper = null;
		this.cancelHelperRemoval = false;
	},

	// From now on bulk stuff - mainly helpers

	_trigger: function(type, event, ui) {
		ui = ui || this._uiHash();
		$.ui.plugin.call(this, type, [event, ui]);
		if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
		return $.Widget.prototype._trigger.call(this, type, event, ui);
	},

	plugins: {},

	_uiHash: function(event) {
		return {
			helper: this.helper,
			position: this.position,
			originalPosition: this.originalPosition,
			offset: this.positionAbs
		};
	}

});

$.ui.plugin.add("draggable", "connectToSortable", {
	start: function(event, ui) {

		var inst = $(this).data("draggable"), o = inst.options,
			uiSortable = $.extend({}, ui, { item: inst.element });
		inst.sortables = [];
		$(o.connectToSortable).each(function() {
			var sortable = $.data(this, 'sortable');
			if (sortable && !sortable.options.disabled) {
				inst.sortables.push({
					instance: sortable,
					shouldRevert: sortable.options.revert
				});
				sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
				sortable._trigger("activate", event, uiSortable);
			}
		});

	},
	stop: function(event, ui) {

		//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
		var inst = $(this).data("draggable"),
			uiSortable = $.extend({}, ui, { item: inst.element });

		$.each(inst.sortables, function() {
			if(this.instance.isOver) {

				this.instance.isOver = 0;

				inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
				this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)

				//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
				if(this.shouldRevert) this.instance.options.revert = true;

				//Trigger the stop of the sortable
				this.instance._mouseStop(event);

				this.instance.options.helper = this.instance.options._helper;

				//If the helper has been the original item, restore properties in the sortable
				if(inst.options.helper == 'original')
					this.instance.currentItem.css({ top: 'auto', left: 'auto' });

			} else {
				this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
				this.instance._trigger("deactivate", event, uiSortable);
			}

		});

	},
	drag: function(event, ui) {

		var inst = $(this).data("draggable"), that = this;

		var checkPos = function(o) {
			var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
			var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
			var itemHeight = o.height, itemWidth = o.width;
			var itemTop = o.top, itemLeft = o.left;

			return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
		};

		$.each(inst.sortables, function(i) {
			
			//Copy over some variables to allow calling the sortable's native _intersectsWith
			this.instance.positionAbs = inst.positionAbs;
			this.instance.helperProportions = inst.helperProportions;
			this.instance.offset.click = inst.offset.click;
			
			if(this.instance._intersectsWith(this.instance.containerCache)) {

				//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
				if(!this.instance.isOver) {

					this.instance.isOver = 1;
					//Now we fake the start of dragging for the sortable instance,
					//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
					//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
					this.instance.currentItem = $(that).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
					this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
					this.instance.options.helper = function() { return ui.helper[0]; };

					event.target = this.instance.currentItem[0];
					this.instance._mouseCapture(event, true);
					this.instance._mouseStart(event, true, true);

					//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
					this.instance.offset.click.top = inst.offset.click.top;
					this.instance.offset.click.left = inst.offset.click.left;
					this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
					this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;

					inst._trigger("toSortable", event);
					inst.dropped = this.instance.element; //draggable revert needs that
					//hack so receive/update callbacks work (mostly)
					inst.currentItem = inst.element;
					this.instance.fromOutside = inst;

				}

				//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
				if(this.instance.currentItem) this.instance._mouseDrag(event);

			} else {

				//If it doesn't intersect with the sortable, and it intersected before,
				//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
				if(this.instance.isOver) {

					this.instance.isOver = 0;
					this.instance.cancelHelperRemoval = true;
					
					//Prevent reverting on this forced stop
					this.instance.options.revert = false;
					
					// The out event needs to be triggered independently
					this.instance._trigger('out', event, this.instance._uiHash(this.instance));
					
					this.instance._mouseStop(event, true);
					this.instance.options.helper = this.instance.options._helper;

					//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
					this.instance.currentItem.remove();
					if(this.instance.placeholder) this.instance.placeholder.remove();

					inst._trigger("fromSortable", event);
					inst.dropped = false; //draggable revert needs that
				}

			};

		});

	}
});

$.ui.plugin.add("draggable", "cursor", {
	start: function(event, ui) {
		var t = $('body'), o = $(this).data('draggable').options;
		if (t.css("cursor")) o._cursor = t.css("cursor");
		t.css("cursor", o.cursor);
	},
	stop: function(event, ui) {
		var o = $(this).data('draggable').options;
		if (o._cursor) $('body').css("cursor", o._cursor);
	}
});

$.ui.plugin.add("draggable", "opacity", {
	start: function(event, ui) {
		var t = $(ui.helper), o = $(this).data('draggable').options;
		if(t.css("opacity")) o._opacity = t.css("opacity");
		t.css('opacity', o.opacity);
	},
	stop: function(event, ui) {
		var o = $(this).data('draggable').options;
		if(o._opacity) $(ui.helper).css('opacity', o._opacity);
	}
});

$.ui.plugin.add("draggable", "scroll", {
	start: function(event, ui) {
		var i = $(this).data("draggable");
		if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
	},
	drag: function(event, ui) {

		var i = $(this).data("draggable"), o = i.options, scrolled = false;

		if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {

			if(!o.axis || o.axis != 'x') {
				if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
				else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
			}

			if(!o.axis || o.axis != 'y') {
				if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
				else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
			}

		} else {

			if(!o.axis || o.axis != 'x') {
				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
			}

			if(!o.axis || o.axis != 'y') {
				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
			}

		}

		if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
			$.ui.ddmanager.prepareOffsets(i, event);

	}
});

$.ui.plugin.add("draggable", "snap", {
	start: function(event, ui) {

		var i = $(this).data("draggable"), o = i.options;
		i.snapElements = [];

		$(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
			var $t = $(this); var $o = $t.offset();
			if(this != i.element[0]) i.snapElements.push({
				item: this,
				width: $t.outerWidth(), height: $t.outerHeight(),
				top: $o.top, left: $o.left
			});
		});

	},
	drag: function(event, ui) {

		var inst = $(this).data("draggable"), o = inst.options;
		var d = o.snapTolerance;

		var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;

		for (var i = inst.snapElements.length - 1; i >= 0; i--){

			var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
				t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;

			//Yes, I know, this is insane ;)
			if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
				if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
				inst.snapElements[i].snapping = false;
				continue;
			}

			if(o.snapMode != 'inner') {
				var ts = Math.abs(t - y2) <= d;
				var bs = Math.abs(b - y1) <= d;
				var ls = Math.abs(l - x2) <= d;
				var rs = Math.abs(r - x1) <= d;
				if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
				if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
				if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
			}

			var first = (ts || bs || ls || rs);

			if(o.snapMode != 'outer') {
				var ts = Math.abs(t - y1) <= d;
				var bs = Math.abs(b - y2) <= d;
				var ls = Math.abs(l - x1) <= d;
				var rs = Math.abs(r - x2) <= d;
				if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
				if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
				if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
			}

			if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);

		};

	}
});

$.ui.plugin.add("draggable", "stack", {
	start: function(event, ui) {

		var o = $(this).data("draggable").options;

		var group = $.makeArray($(o.stack)).sort(function(a,b) {
			return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
		});
		if (!group.length) { return; }
		
		var min = parseInt(group[0].style.zIndex) || 0;
		$(group).each(function(i) {
			this.style.zIndex = min + i;
		});

		this[0].style.zIndex = min + group.length;

	}
});

$.ui.plugin.add("draggable", "zIndex", {
	start: function(event, ui) {
		var t = $(ui.helper), o = $(this).data("draggable").options;
		if(t.css("zIndex")) o._zIndex = t.css("zIndex");
		t.css('zIndex', o.zIndex);
	},
	stop: function(event, ui) {
		var o = $(this).data("draggable").options;
		if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
	}
});

})(jQuery);
(function( $, undefined ) {

$.widget("ui.droppable", {
	version: "1.9.0",
	widgetEventPrefix: "drop",
	options: {
		accept: '*',
		activeClass: false,
		addClasses: true,
		greedy: false,
		hoverClass: false,
		scope: 'default',
		tolerance: 'intersect'
	},
	_create: function() {

		var o = this.options, accept = o.accept;
		this.isover = 0; this.isout = 1;

		this.accept = $.isFunction(accept) ? accept : function(d) {
			return d.is(accept);
		};

		//Store the droppable's proportions
		this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };

		// Add the reference and positions to the manager
		$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
		$.ui.ddmanager.droppables[o.scope].push(this);

		(o.addClasses && this.element.addClass("ui-droppable"));

	},

	_destroy: function() {
		var drop = $.ui.ddmanager.droppables[this.options.scope];
		for ( var i = 0; i < drop.length; i++ )
			if ( drop[i] == this )
				drop.splice(i, 1);

		this.element.removeClass("ui-droppable ui-droppable-disabled");
	},

	_setOption: function(key, value) {

		if(key == 'accept') {
			this.accept = $.isFunction(value) ? value : function(d) {
				return d.is(value);
			};
		}
		$.Widget.prototype._setOption.apply(this, arguments);
	},

	_activate: function(event) {
		var draggable = $.ui.ddmanager.current;
		if(this.options.activeClass) this.element.addClass(this.options.activeClass);
		(draggable && this._trigger('activate', event, this.ui(draggable)));
	},

	_deactivate: function(event) {
		var draggable = $.ui.ddmanager.current;
		if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
		(draggable && this._trigger('deactivate', event, this.ui(draggable)));
	},

	_over: function(event) {

		var draggable = $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element

		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
			if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
			this._trigger('over', event, this.ui(draggable));
		}

	},

	_out: function(event) {

		var draggable = $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element

		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
			if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
			this._trigger('out', event, this.ui(draggable));
		}

	},

	_drop: function(event,custom) {

		var draggable = custom || $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element

		var childrenIntersection = false;
		this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
			var inst = $.data(this, 'droppable');
			if(
				inst.options.greedy
				&& !inst.options.disabled
				&& inst.options.scope == draggable.options.scope
				&& inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
				&& $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
			) { childrenIntersection = true; return false; }
		});
		if(childrenIntersection) return false;

		if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
			if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
			if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
			this._trigger('drop', event, this.ui(draggable));
			return this.element;
		}

		return false;

	},

	ui: function(c) {
		return {
			draggable: (c.currentItem || c.element),
			helper: c.helper,
			position: c.position,
			offset: c.positionAbs
		};
	}

});

$.ui.intersect = function(draggable, droppable, toleranceMode) {

	if (!droppable.offset) return false;

	var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
		y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
	var l = droppable.offset.left, r = l + droppable.proportions.width,
		t = droppable.offset.top, b = t + droppable.proportions.height;

	switch (toleranceMode) {
		case 'fit':
			return (l <= x1 && x2 <= r
				&& t <= y1 && y2 <= b);
			break;
		case 'intersect':
			return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
				&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
				&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
				&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
			break;
		case 'pointer':
			var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
				draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
				isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
			return isOver;
			break;
		case 'touch':
			return (
					(y1 >= t && y1 <= b) ||	// Top edge touching
					(y2 >= t && y2 <= b) ||	// Bottom edge touching
					(y1 < t && y2 > b)		// Surrounded vertically
				) && (
					(x1 >= l && x1 <= r) ||	// Left edge touching
					(x2 >= l && x2 <= r) ||	// Right edge touching
					(x1 < l && x2 > r)		// Surrounded horizontally
				);
			break;
		default:
			return false;
			break;
		}

};

/*
	This manager tracks offsets of draggables and droppables
*/
$.ui.ddmanager = {
	current: null,
	droppables: { 'default': [] },
	prepareOffsets: function(t, event) {

		var m = $.ui.ddmanager.droppables[t.options.scope] || [];
		var type = event ? event.type : null; // workaround for #2317
		var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();

		droppablesLoop: for (var i = 0; i < m.length; i++) {

			if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue;	//No disabled and non-accepted
			for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
			m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; 									//If the element is not visible, continue

			if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables

			m[i].offset = m[i].element.offset();
			m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };

		}

	},
	drop: function(draggable, event) {

		var dropped = false;
		$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {

			if(!this.options) return;
			if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
				dropped = this._drop.call(this, event) || dropped;

			if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
				this.isout = 1; this.isover = 0;
				this._deactivate.call(this, event);
			}

		});
		return dropped;

	},
	dragStart: function( draggable, event ) {
		//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
		draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
			if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
		});
	},
	drag: function(draggable, event) {

		//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
		if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);

		//Run through all droppables and check their positions based on specific tolerance options
		$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {

			if(this.options.disabled || this.greedyChild || !this.visible) return;
			var intersects = $.ui.intersect(draggable, this, this.options.tolerance);

			var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
			if(!c) return;

			var parentInstance;
			if (this.options.greedy) {
				// find droppable parents with same scope
				var scope = this.options.scope;
				var parent = this.element.parents(':data(droppable)').filter(function () {
					return $.data(this, 'droppable').options.scope === scope;
				});

				if (parent.length) {
					parentInstance = $.data(parent[0], 'droppable');
					parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
				}
			}

			// we just moved into a greedy child
			if (parentInstance && c == 'isover') {
				parentInstance['isover'] = 0;
				parentInstance['isout'] = 1;
				parentInstance._out.call(parentInstance, event);
			}

			this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
			this[c == "isover" ? "_over" : "_out"].call(this, event);

			// we just moved out of a greedy child
			if (parentInstance && c == 'isout') {
				parentInstance['isout'] = 0;
				parentInstance['isover'] = 1;
				parentInstance._over.call(parentInstance, event);
			}
		});

	},
	dragStop: function( draggable, event ) {
		draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
		//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
		if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
	}
};

})(jQuery);
(function( $, undefined ) {

$.widget("ui.resizable", $.ui.mouse, {
	version: "1.9.0",
	widgetEventPrefix: "resize",
	options: {
		alsoResize: false,
		animate: false,
		animateDuration: "slow",
		animateEasing: "swing",
		aspectRatio: false,
		autoHide: false,
		containment: false,
		ghost: false,
		grid: false,
		handles: "e,s,se",
		helper: false,
		maxHeight: null,
		maxWidth: null,
		minHeight: 10,
		minWidth: 10,
		zIndex: 1000
	},
	_create: function() {

		var that = this, o = this.options;
		this.element.addClass("ui-resizable");

		$.extend(this, {
			_aspectRatio: !!(o.aspectRatio),
			aspectRatio: o.aspectRatio,
			originalElement: this.element,
			_proportionallyResizeElements: [],
			_helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null
		});

		//Wrap the element if it cannot hold child nodes
		if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {

			//Create a wrapper element and set the wrapper to the new current internal element
			this.element.wrap(
				$('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({
					position: this.element.css('position'),
					width: this.element.outerWidth(),
					height: this.element.outerHeight(),
					top: this.element.css('top'),
					left: this.element.css('left')
				})
			);

			//Overwrite the original this.element
			this.element = this.element.parent().data(
				"resizable", this.element.data('resizable')
			);

			this.elementIsWrapper = true;

			//Move margins to the wrapper
			this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
			this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});

			//Prevent Safari textarea resize
			this.originalResizeStyle = this.originalElement.css('resize');
			this.originalElement.css('resize', 'none');

			//Push the actual element to our proportionallyResize internal array
			this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' }));

			// avoid IE jump (hard set the margin)
			this.originalElement.css({ margin: this.originalElement.css('margin') });

			// fix handlers offset
			this._proportionallyResize();

		}

		this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' });
		if(this.handles.constructor == String) {

			if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw';
			var n = this.handles.split(","); this.handles = {};

			for(var i = 0; i < n.length; i++) {

				var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle;
				var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>');

				// Apply zIndex to all handles - see #7960
				axis.css({ zIndex: o.zIndex });

				//TODO : What's going on here?
				if ('se' == handle) {
					axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');
				};

				//Insert into internal handles object and append to element
				this.handles[handle] = '.ui-resizable-'+handle;
				this.element.append(axis);
			}

		}

		this._renderAxis = function(target) {

			target = target || this.element;

			for(var i in this.handles) {

				if(this.handles[i].constructor == String)
					this.handles[i] = $(this.handles[i], this.element).show();

				//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {

					var axis = $(this.handles[i], this.element), padWrapper = 0;

					//Checking the correct pad and border
					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();

					//The padding type i have to apply...
					var padPos = [ 'padding',
						/ne|nw|n/.test(i) ? 'Top' :
						/se|sw|s/.test(i) ? 'Bottom' :
						/^e$/.test(i) ? 'Right' : 'Left' ].join("");

					target.css(padPos, padWrapper);

					this._proportionallyResize();

				}

				//TODO: What's that good for? There's not anything to be executed left
				if(!$(this.handles[i]).length)
					continue;

			}
		};

		//TODO: make renderAxis a prototype function
		this._renderAxis(this.element);

		this._handles = $('.ui-resizable-handle', this.element)
			.disableSelection();

		//Matching axis name
		this._handles.mouseover(function() {
			if (!that.resizing) {
				if (this.className)
					var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
				//Axis, default = se
				that.axis = axis && axis[1] ? axis[1] : 'se';
			}
		});

		//If we want to auto hide the elements
		if (o.autoHide) {
			this._handles.hide();
			$(this.element)
				.addClass("ui-resizable-autohide")
				.mouseenter(function() {
					if (o.disabled) return;
					$(this).removeClass("ui-resizable-autohide");
					that._handles.show();
				})
				.mouseleave(function(){
					if (o.disabled) return;
					if (!that.resizing) {
						$(this).addClass("ui-resizable-autohide");
						that._handles.hide();
					}
				});
		}

		//Initialize the mouse interaction
		this._mouseInit();

	},

	_destroy: function() {

		this._mouseDestroy();

		var _destroy = function(exp) {
			$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
				.removeData("resizable").removeData("ui-resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
		};

		//TODO: Unwrap at same DOM position
		if (this.elementIsWrapper) {
			_destroy(this.element);
			var wrapper = this.element;
			wrapper.after(
				this.originalElement.css({
					position: wrapper.css('position'),
					width: wrapper.outerWidth(),
					height: wrapper.outerHeight(),
					top: wrapper.css('top'),
					left: wrapper.css('left')
				})
			).remove();
		}

		this.originalElement.css('resize', this.originalResizeStyle);
		_destroy(this.originalElement);

		return this;
	},

	_mouseCapture: function(event) {
		var handle = false;
		for (var i in this.handles) {
			if ($(this.handles[i])[0] == event.target) {
				handle = true;
			}
		}

		return !this.options.disabled && handle;
	},

	_mouseStart: function(event) {

		var o = this.options, iniPos = this.element.position(), el = this.element;

		this.resizing = true;
		this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };

		// bugfix for http://dev.jquery.com/ticket/1749
		if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
			el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left });
		}

		this._renderProxy();

		var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));

		if (o.containment) {
			curleft += $(o.containment).scrollLeft() || 0;
			curtop += $(o.containment).scrollTop() || 0;
		}

		//Store needed variables
		this.offset = this.helper.offset();
		this.position = { left: curleft, top: curtop };
		this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalPosition = { left: curleft, top: curtop };
		this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
		this.originalMousePosition = { left: event.pageX, top: event.pageY };

		//Aspect Ratio
		this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);

		var cursor = $('.ui-resizable-' + this.axis).css('cursor');
		$('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor);

		el.addClass("ui-resizable-resizing");
		this._propagate("start", event);
		return true;
	},

	_mouseDrag: function(event) {

		//Increase performance, avoid regex
		var el = this.helper, o = this.options, props = {},
			that = this, smp = this.originalMousePosition, a = this.axis;

		var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
		var trigger = this._change[a];
		if (!trigger) return false;

		// Calculate the attrs that will be change
		var data = trigger.apply(this, [event, dx, dy]);

		// Put this in the mouseDrag handler since the user can start pressing shift while resizing
		this._updateVirtualBoundaries(event.shiftKey);
		if (this._aspectRatio || event.shiftKey)
			data = this._updateRatio(data, event);

		data = this._respectSize(data, event);

		// plugins callbacks need to be called first
		this._propagate("resize", event);

		el.css({
			top: this.position.top + "px", left: this.position.left + "px",
			width: this.size.width + "px", height: this.size.height + "px"
		});

		if (!this._helper && this._proportionallyResizeElements.length)
			this._proportionallyResize();

		this._updateCache(data);

		// calling the user callback at the end
		this._trigger('resize', event, this.ui());

		return false;
	},

	_mouseStop: function(event) {

		this.resizing = false;
		var o = this.options, that = this;

		if(this._helper) {
			var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
				soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : that.sizeDiff.height,
				soffsetw = ista ? 0 : that.sizeDiff.width;

			var s = { width: (that.helper.width()  - soffsetw), height: (that.helper.height() - soffseth) },
				left = (parseInt(that.element.css('left'), 10) + (that.position.left - that.originalPosition.left)) || null,
				top = (parseInt(that.element.css('top'), 10) + (that.position.top - that.originalPosition.top)) || null;

			if (!o.animate)
				this.element.css($.extend(s, { top: top, left: left }));

			that.helper.height(that.size.height);
			that.helper.width(that.size.width);

			if (this._helper && !o.animate) this._proportionallyResize();
		}

		$('body').css('cursor', 'auto');

		this.element.removeClass("ui-resizable-resizing");

		this._propagate("stop", event);

		if (this._helper) this.helper.remove();
		return false;

	},

	_updateVirtualBoundaries: function(forceAspectRatio) {
		var o = this.options, pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b;

		b = {
			minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
			maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
			minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
			maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
		};

		if(this._aspectRatio || forceAspectRatio) {
			// We want to create an enclosing box whose aspect ration is the requested one
			// First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
			pMinWidth = b.minHeight * this.aspectRatio;
			pMinHeight = b.minWidth / this.aspectRatio;
			pMaxWidth = b.maxHeight * this.aspectRatio;
			pMaxHeight = b.maxWidth / this.aspectRatio;

			if(pMinWidth > b.minWidth) b.minWidth = pMinWidth;
			if(pMinHeight > b.minHeight) b.minHeight = pMinHeight;
			if(pMaxWidth < b.maxWidth) b.maxWidth = pMaxWidth;
			if(pMaxHeight < b.maxHeight) b.maxHeight = pMaxHeight;
		}
		this._vBoundaries = b;
	},

	_updateCache: function(data) {
		var o = this.options;
		this.offset = this.helper.offset();
		if (isNumber(data.left)) this.position.left = data.left;
		if (isNumber(data.top)) this.position.top = data.top;
		if (isNumber(data.height)) this.size.height = data.height;
		if (isNumber(data.width)) this.size.width = data.width;
	},

	_updateRatio: function(data, event) {

		var o = this.options, cpos = this.position, csize = this.size, a = this.axis;

		if (isNumber(data.height)) data.width = (data.height * this.aspectRatio);
		else if (isNumber(data.width)) data.height = (data.width / this.aspectRatio);

		if (a == 'sw') {
			data.left = cpos.left + (csize.width - data.width);
			data.top = null;
		}
		if (a == 'nw') {
			data.top = cpos.top + (csize.height - data.height);
			data.left = cpos.left + (csize.width - data.width);
		}

		return data;
	},

	_respectSize: function(data, event) {

		var el = this.helper, o = this._vBoundaries, pRatio = this._aspectRatio || event.shiftKey, a = this.axis,
				ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
					isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height);

		if (isminw) data.width = o.minWidth;
		if (isminh) data.height = o.minHeight;
		if (ismaxw) data.width = o.maxWidth;
		if (ismaxh) data.height = o.maxHeight;

		var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
		var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);

		if (isminw && cw) data.left = dw - o.minWidth;
		if (ismaxw && cw) data.left = dw - o.maxWidth;
		if (isminh && ch)	data.top = dh - o.minHeight;
		if (ismaxh && ch)	data.top = dh - o.maxHeight;

		// fixing jump error on top/left - bug #2330
		var isNotwh = !data.width && !data.height;
		if (isNotwh && !data.left && data.top) data.top = null;
		else if (isNotwh && !data.top && data.left) data.left = null;

		return data;
	},

	_proportionallyResize: function() {

		var o = this.options;
		if (!this._proportionallyResizeElements.length) return;
		var element = this.helper || this.element;

		for (var i=0; i < this._proportionallyResizeElements.length; i++) {

			var prel = this._proportionallyResizeElements[i];

			if (!this.borderDif) {
				var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
					p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];

				this.borderDif = $.map(b, function(v, i) {
					var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
					return border + padding;
				});
			}

			prel.css({
				height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
				width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
			});

		};

	},

	_renderProxy: function() {

		var el = this.element, o = this.options;
		this.elementOffset = el.offset();

		if(this._helper) {

			this.helper = this.helper || $('<div style="overflow:hidden;"></div>');

			// fix ie6 offset TODO: This seems broken
			var ie6 = $.browser.msie && $.browser.version < 7, ie6offset = (ie6 ? 1 : 0),
			pxyoffset = ( ie6 ? 2 : -1 );

			this.helper.addClass(this._helper).css({
				width: this.element.outerWidth() + pxyoffset,
				height: this.element.outerHeight() + pxyoffset,
				position: 'absolute',
				left: this.elementOffset.left - ie6offset +'px',
				top: this.elementOffset.top - ie6offset +'px',
				zIndex: ++o.zIndex //TODO: Don't modify option
			});

			this.helper
				.appendTo("body")
				.disableSelection();

		} else {
			this.helper = this.element;
		}

	},

	_change: {
		e: function(event, dx, dy) {
			return { width: this.originalSize.width + dx };
		},
		w: function(event, dx, dy) {
			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
			return { left: sp.left + dx, width: cs.width - dx };
		},
		n: function(event, dx, dy) {
			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
			return { top: sp.top + dy, height: cs.height - dy };
		},
		s: function(event, dx, dy) {
			return { height: this.originalSize.height + dy };
		},
		se: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
		},
		sw: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
		},
		ne: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
		},
		nw: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
		}
	},

	_propagate: function(n, event) {
		$.ui.plugin.call(this, n, [event, this.ui()]);
		(n != "resize" && this._trigger(n, event, this.ui()));
	},

	plugins: {},

	ui: function() {
		return {
			originalElement: this.originalElement,
			element: this.element,
			helper: this.helper,
			position: this.position,
			size: this.size,
			originalSize: this.originalSize,
			originalPosition: this.originalPosition
		};
	}

});

/*
 * Resizable Extensions
 */

$.ui.plugin.add("resizable", "alsoResize", {

	start: function (event, ui) {
		var that = $(this).data("resizable"), o = that.options;

		var _store = function (exp) {
			$(exp).each(function() {
				var el = $(this);
				el.data("resizable-alsoresize", {
					width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
					left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10)
				});
			});
		};

		if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) {
			if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
			else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
		}else{
			_store(o.alsoResize);
		}
	},

	resize: function (event, ui) {
		var that = $(this).data("resizable"), o = that.options, os = that.originalSize, op = that.originalPosition;

		var delta = {
			height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
			top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
		},

		_alsoResize = function (exp, c) {
			$(exp).each(function() {
				var el = $(this), start = $(this).data("resizable-alsoresize"), style = {}, 
					css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ['width', 'height'] : ['width', 'height', 'top', 'left'];

				$.each(css, function (i, prop) {
					var sum = (start[prop]||0) + (delta[prop]||0);
					if (sum && sum >= 0)
						style[prop] = sum || null;
				});

				el.css(style);
			});
		};

		if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
			$.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
		}else{
			_alsoResize(o.alsoResize);
		}
	},

	stop: function (event, ui) {
		$(this).removeData("resizable-alsoresize");
	}
});

$.ui.plugin.add("resizable", "animate", {

	stop: function(event, ui) {
		var that = $(this).data("resizable"), o = that.options;

		var pr = that._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
					soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : that.sizeDiff.height,
						soffsetw = ista ? 0 : that.sizeDiff.width;

		var style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
					left = (parseInt(that.element.css('left'), 10) + (that.position.left - that.originalPosition.left)) || null,
						top = (parseInt(that.element.css('top'), 10) + (that.position.top - that.originalPosition.top)) || null;

		that.element.animate(
			$.extend(style, top && left ? { top: top, left: left } : {}), {
				duration: o.animateDuration,
				easing: o.animateEasing,
				step: function() {

					var data = {
						width: parseInt(that.element.css('width'), 10),
						height: parseInt(that.element.css('height'), 10),
						top: parseInt(that.element.css('top'), 10),
						left: parseInt(that.element.css('left'), 10)
					};

					if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });

					// propagating resize, and updating values for each animation step
					that._updateCache(data);
					that._propagate("resize", event);

				}
			}
		);
	}

});

$.ui.plugin.add("resizable", "containment", {

	start: function(event, ui) {
		var that = $(this).data("resizable"), o = that.options, el = that.element;
		var oc = o.containment,	ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
		if (!ce) return;

		that.containerElement = $(ce);

		if (/document/.test(oc) || oc == document) {
			that.containerOffset = { left: 0, top: 0 };
			that.containerPosition = { left: 0, top: 0 };

			that.parentData = {
				element: $(document), left: 0, top: 0,
				width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
			};
		}

		// i'm a node, so compute top, left, right, bottom
		else {
			var element = $(ce), p = [];
			$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });

			that.containerOffset = element.offset();
			that.containerPosition = element.position();
			that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };

			var co = that.containerOffset, ch = that.containerSize.height,	cw = that.containerSize.width,
						width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);

			that.parentData = {
				element: ce, left: co.left, top: co.top, width: width, height: height
			};
		}
	},

	resize: function(event, ui) {
		var that = $(this).data("resizable"), o = that.options,
				ps = that.containerSize, co = that.containerOffset, cs = that.size, cp = that.position,
				pRatio = that._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = that.containerElement;

		if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co;

		if (cp.left < (that._helper ? co.left : 0)) {
			that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
			if (pRatio) that.size.height = that.size.width / that.aspectRatio;
			that.position.left = o.helper ? co.left : 0;
		}

		if (cp.top < (that._helper ? co.top : 0)) {
			that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
			if (pRatio) that.size.width = that.size.height * that.aspectRatio;
			that.position.top = that._helper ? co.top : 0;
		}

		that.offset.left = that.parentData.left+that.position.left;
		that.offset.top = that.parentData.top+that.position.top;

		var woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ),
					hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );

		var isParent = that.containerElement.get(0) == that.element.parent().get(0),
			isOffsetRelative = /relative|absolute/.test(that.containerElement.css('position'));

		if(isParent && isOffsetRelative) woset -= that.parentData.left;

		if (woset + that.size.width >= that.parentData.width) {
			that.size.width = that.parentData.width - woset;
			if (pRatio) that.size.height = that.size.width / that.aspectRatio;
		}

		if (hoset + that.size.height >= that.parentData.height) {
			that.size.height = that.parentData.height - hoset;
			if (pRatio) that.size.width = that.size.height * that.aspectRatio;
		}
	},

	stop: function(event, ui){
		var that = $(this).data("resizable"), o = that.options, cp = that.position,
				co = that.containerOffset, cop = that.containerPosition, ce = that.containerElement;

		var helper = $(that.helper), ho = helper.offset(), w = helper.outerWidth() - that.sizeDiff.width, h = helper.outerHeight() - that.sizeDiff.height;

		if (that._helper && !o.animate && (/relative/).test(ce.css('position')))
			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });

		if (that._helper && !o.animate && (/static/).test(ce.css('position')))
			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });

	}
});

$.ui.plugin.add("resizable", "ghost", {

	start: function(event, ui) {

		var that = $(this).data("resizable"), o = that.options, cs = that.size;

		that.ghost = that.originalElement.clone();
		that.ghost
			.css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
			.addClass('ui-resizable-ghost')
			.addClass(typeof o.ghost == 'string' ? o.ghost : '');

		that.ghost.appendTo(that.helper);

	},

	resize: function(event, ui){
		var that = $(this).data("resizable"), o = that.options;
		if (that.ghost) that.ghost.css({ position: 'relative', height: that.size.height, width: that.size.width });
	},

	stop: function(event, ui){
		var that = $(this).data("resizable"), o = that.options;
		if (that.ghost && that.helper) that.helper.get(0).removeChild(that.ghost.get(0));
	}

});

$.ui.plugin.add("resizable", "grid", {

	resize: function(event, ui) {
		var that = $(this).data("resizable"), o = that.options, cs = that.size, os = that.originalSize, op = that.originalPosition, a = that.axis, ratio = o._aspectRatio || event.shiftKey;
		o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
		var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);

		if (/^(se|s|e)$/.test(a)) {
			that.size.width = os.width + ox;
			that.size.height = os.height + oy;
		}
		else if (/^(ne)$/.test(a)) {
			that.size.width = os.width + ox;
			that.size.height = os.height + oy;
			that.position.top = op.top - oy;
		}
		else if (/^(sw)$/.test(a)) {
			that.size.width = os.width + ox;
			that.size.height = os.height + oy;
			that.position.left = op.left - ox;
		}
		else {
			that.size.width = os.width + ox;
			that.size.height = os.height + oy;
			that.position.top = op.top - oy;
			that.position.left = op.left - ox;
		}
	}

});

var num = function(v) {
	return parseInt(v, 10) || 0;
};

var isNumber = function(value) {
	return !isNaN(parseInt(value, 10));
};

})(jQuery);
(function( $, undefined ) {

$.widget("ui.selectable", $.ui.mouse, {
	version: "1.9.0",
	options: {
		appendTo: 'body',
		autoRefresh: true,
		distance: 0,
		filter: '*',
		tolerance: 'touch'
	},
	_create: function() {
		var that = this;

		this.element.addClass("ui-selectable");

		this.dragged = false;

		// cache selectee children based on filter
		var selectees;
		this.refresh = function() {
			selectees = $(that.options.filter, that.element[0]);
			selectees.addClass("ui-selectee");
			selectees.each(function() {
				var $this = $(this);
				var pos = $this.offset();
				$.data(this, "selectable-item", {
					element: this,
					$element: $this,
					left: pos.left,
					top: pos.top,
					right: pos.left + $this.outerWidth(),
					bottom: pos.top + $this.outerHeight(),
					startselected: false,
					selected: $this.hasClass('ui-selected'),
					selecting: $this.hasClass('ui-selecting'),
					unselecting: $this.hasClass('ui-unselecting')
				});
			});
		};
		this.refresh();

		this.selectees = selectees.addClass("ui-selectee");

		this._mouseInit();

		this.helper = $("<div class='ui-selectable-helper'></div>");
	},

	_destroy: function() {
		this.selectees
			.removeClass("ui-selectee")
			.removeData("selectable-item");
		this.element
			.removeClass("ui-selectable ui-selectable-disabled");
		this._mouseDestroy();
	},

	_mouseStart: function(event) {
		var that = this;

		this.opos = [event.pageX, event.pageY];

		if (this.options.disabled)
			return;

		var options = this.options;

		this.selectees = $(options.filter, this.element[0]);

		this._trigger("start", event);

		$(options.appendTo).append(this.helper);
		// position helper (lasso)
		this.helper.css({
			"left": event.clientX,
			"top": event.clientY,
			"width": 0,
			"height": 0
		});

		if (options.autoRefresh) {
			this.refresh();
		}

		this.selectees.filter('.ui-selected').each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.startselected = true;
			if (!event.metaKey && !event.ctrlKey) {
				selectee.$element.removeClass('ui-selected');
				selectee.selected = false;
				selectee.$element.addClass('ui-unselecting');
				selectee.unselecting = true;
				// selectable UNSELECTING callback
				that._trigger("unselecting", event, {
					unselecting: selectee.element
				});
			}
		});

		$(event.target).parents().andSelf().each(function() {
			var selectee = $.data(this, "selectable-item");
			if (selectee) {
				var doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass('ui-selected');
				selectee.$element
					.removeClass(doSelect ? "ui-unselecting" : "ui-selected")
					.addClass(doSelect ? "ui-selecting" : "ui-unselecting");
				selectee.unselecting = !doSelect;
				selectee.selecting = doSelect;
				selectee.selected = doSelect;
				// selectable (UN)SELECTING callback
				if (doSelect) {
					that._trigger("selecting", event, {
						selecting: selectee.element
					});
				} else {
					that._trigger("unselecting", event, {
						unselecting: selectee.element
					});
				}
				return false;
			}
		});

	},

	_mouseDrag: function(event) {
		var that = this;
		this.dragged = true;

		if (this.options.disabled)
			return;

		var options = this.options;

		var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
		if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
		if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
		this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});

		this.selectees.each(function() {
			var selectee = $.data(this, "selectable-item");
			//prevent helper from being selected if appendTo: selectable
			if (!selectee || selectee.element == that.element[0])
				return;
			var hit = false;
			if (options.tolerance == 'touch') {
				hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
			} else if (options.tolerance == 'fit') {
				hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
			}

			if (hit) {
				// SELECT
				if (selectee.selected) {
					selectee.$element.removeClass('ui-selected');
					selectee.selected = false;
				}
				if (selectee.unselecting) {
					selectee.$element.removeClass('ui-unselecting');
					selectee.unselecting = false;
				}
				if (!selectee.selecting) {
					selectee.$element.addClass('ui-selecting');
					selectee.selecting = true;
					// selectable SELECTING callback
					that._trigger("selecting", event, {
						selecting: selectee.element
					});
				}
			} else {
				// UNSELECT
				if (selectee.selecting) {
					if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
						selectee.$element.removeClass('ui-selecting');
						selectee.selecting = false;
						selectee.$element.addClass('ui-selected');
						selectee.selected = true;
					} else {
						selectee.$element.removeClass('ui-selecting');
						selectee.selecting = false;
						if (selectee.startselected) {
							selectee.$element.addClass('ui-unselecting');
							selectee.unselecting = true;
						}
						// selectable UNSELECTING callback
						that._trigger("unselecting", event, {
							unselecting: selectee.element
						});
					}
				}
				if (selectee.selected) {
					if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
						selectee.$element.removeClass('ui-selected');
						selectee.selected = false;

						selectee.$element.addClass('ui-unselecting');
						selectee.unselecting = true;
						// selectable UNSELECTING callback
						that._trigger("unselecting", event, {
							unselecting: selectee.element
						});
					}
				}
			}
		});

		return false;
	},

	_mouseStop: function(event) {
		var that = this;

		this.dragged = false;

		var options = this.options;

		$('.ui-unselecting', this.element[0]).each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.$element.removeClass('ui-unselecting');
			selectee.unselecting = false;
			selectee.startselected = false;
			that._trigger("unselected", event, {
				unselected: selectee.element
			});
		});
		$('.ui-selecting', this.element[0]).each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
			selectee.selecting = false;
			selectee.selected = true;
			selectee.startselected = true;
			that._trigger("selected", event, {
				selected: selectee.element
			});
		});
		this._trigger("stop", event);

		this.helper.remove();

		return false;
	}

});

})(jQuery);
(function( $, undefined ) {

$.widget("ui.sortable", $.ui.mouse, {
	version: "1.9.0",
	widgetEventPrefix: "sort",
	ready: false,
	options: {
		appendTo: "parent",
		axis: false,
		connectWith: false,
		containment: false,
		cursor: 'auto',
		cursorAt: false,
		dropOnEmpty: true,
		forcePlaceholderSize: false,
		forceHelperSize: false,
		grid: false,
		handle: false,
		helper: "original",
		items: '> *',
		opacity: false,
		placeholder: false,
		revert: false,
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		scope: "default",
		tolerance: "intersect",
		zIndex: 1000
	},
	_create: function() {

		var o = this.options;
		this.containerCache = {};
		this.element.addClass("ui-sortable");

		//Get the items
		this.refresh();

		//Let's determine if the items are being displayed horizontally
		this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;

		//Let's determine the parent's offset
		this.offset = this.element.offset();

		//Initialize mouse events for interaction
		this._mouseInit();

		//We're ready to go
		this.ready = true

	},

	_destroy: function() {
		this.element
			.removeClass("ui-sortable ui-sortable-disabled");
		this._mouseDestroy();

		for ( var i = this.items.length - 1; i >= 0; i-- )
			this.items[i].item.removeData(this.widgetName + "-item");

		return this;
	},

	_setOption: function(key, value){
		if ( key === "disabled" ) {
			this.options[ key ] = value;

			this.widget().toggleClass( "ui-sortable-disabled", !!value );
		} else {
			// Don't call widget base _setOption for disable as it adds ui-state-disabled class
			$.Widget.prototype._setOption.apply(this, arguments);
		}
	},

	_mouseCapture: function(event, overrideHandle) {
		var that = this;

		if (this.reverting) {
			return false;
		}

		if(this.options.disabled || this.options.type == 'static') return false;

		//We have to refresh the items data once first
		this._refreshItems(event);

		//Find out if the clicked node (or one of its parents) is a actual item in this.items
		var currentItem = null, nodes = $(event.target).parents().each(function() {
			if($.data(this, that.widgetName + '-item') == that) {
				currentItem = $(this);
				return false;
			}
		});
		if($.data(event.target, that.widgetName + '-item') == that) currentItem = $(event.target);

		if(!currentItem) return false;
		if(this.options.handle && !overrideHandle) {
			var validHandle = false;

			$(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
			if(!validHandle) return false;
		}

		this.currentItem = currentItem;
		this._removeCurrentsFromItems();
		return true;

	},

	_mouseStart: function(event, overrideHandle, noActivation) {

		var o = this.options;
		this.currentContainer = this;

		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
		this.refreshPositions();

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		//Cache the helper size
		this._cacheHelperProportions();

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Get the next scrolling parent
		this.scrollParent = this.helper.scrollParent();

		//The element's absolute position on the page minus margins
		this.offset = this.currentItem.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		// Only after we got the offset, we can change the helper's position to absolute
		// TODO: Still need to figure out a way to make relative sorting possible
		this.helper.css("position", "absolute");
		this.cssPosition = this.helper.css("position");

		//Generate the original position
		this.originalPosition = this._generatePosition(event);
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Cache the former DOM position
		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };

		//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
		if(this.helper[0] != this.currentItem[0]) {
			this.currentItem.hide();
		}

		//Create the placeholder
		this._createPlaceholder();

		//Set a containment if given in the options
		if(o.containment)
			this._setContainment();

		if(o.cursor) { // cursor option
			if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
			$('body').css("cursor", o.cursor);
		}

		if(o.opacity) { // opacity option
			if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
			this.helper.css("opacity", o.opacity);
		}

		if(o.zIndex) { // zIndex option
			if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
			this.helper.css("zIndex", o.zIndex);
		}

		//Prepare scrolling
		if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
			this.overflowOffset = this.scrollParent.offset();

		//Call callbacks
		this._trigger("start", event, this._uiHash());

		//Recache the helper size
		if(!this._preserveHelperProportions)
			this._cacheHelperProportions();


		//Post 'activate' events to possible containers
		if(!noActivation) {
			 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, this._uiHash(this)); }
		}

		//Prepare possible droppables
		if($.ui.ddmanager)
			$.ui.ddmanager.current = this;

		if ($.ui.ddmanager && !o.dropBehaviour)
			$.ui.ddmanager.prepareOffsets(this, event);

		this.dragging = true;

		this.helper.addClass("ui-sortable-helper");
		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
		return true;

	},

	_mouseDrag: function(event) {

		//Compute the helpers position
		this.position = this._generatePosition(event);
		this.positionAbs = this._convertPositionTo("absolute");

		if (!this.lastPositionAbs) {
			this.lastPositionAbs = this.positionAbs;
		}

		//Do scrolling
		if(this.options.scroll) {
			var o = this.options, scrolled = false;
			if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {

				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
				else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;

				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
				else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;

			} else {

				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);

				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);

			}

			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
				$.ui.ddmanager.prepareOffsets(this, event);
		}

		//Regenerate the absolute position used for position checks
		this.positionAbs = this._convertPositionTo("absolute");

		//Set the helper position
		if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
		if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';

		//Rearrange
		for (var i = this.items.length - 1; i >= 0; i--) {

			//Cache variables and intersection, continue if no intersection
			var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
			if (!intersection) continue;

			// Only put the placeholder inside the current Container, skip all
			// items form other containers. This works because when moving
			// an item from one container to another the
			// currentContainer is switched before the placeholder is moved.
			//
			// Without this moving items in "sub-sortables" can cause the placeholder to jitter
			// beetween the outer and inner container.
			if (item.instance !== this.currentContainer) continue;

			if (itemElement != this.currentItem[0] //cannot intersect with itself
				&&	this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
				&&	!$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
				&& (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
				//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
			) {

				this.direction = intersection == 1 ? "down" : "up";

				if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
					this._rearrange(event, item);
				} else {
					break;
				}

				this._trigger("change", event, this._uiHash());
				break;
			}
		}

		//Post events to containers
		this._contactContainers(event);

		//Interconnect with droppables
		if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);

		//Call callbacks
		this._trigger('sort', event, this._uiHash());

		this.lastPositionAbs = this.positionAbs;
		return false;

	},

	_mouseStop: function(event, noPropagation) {

		if(!event) return;

		//If we are using droppables, inform the manager about the drop
		if ($.ui.ddmanager && !this.options.dropBehaviour)
			$.ui.ddmanager.drop(this, event);

		if(this.options.revert) {
			var that = this;
			var cur = this.placeholder.offset();

			this.reverting = true;

			$(this.helper).animate({
				left: cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
				top: cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
			}, parseInt(this.options.revert, 10) || 500, function() {
				that._clear(event);
			});
		} else {
			this._clear(event, noPropagation);
		}

		return false;

	},

	cancel: function() {

		if(this.dragging) {

			this._mouseUp({ target: null });

			if(this.options.helper == "original")
				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
			else
				this.currentItem.show();

			//Post deactivating events to containers
			for (var i = this.containers.length - 1; i >= 0; i--){
				this.containers[i]._trigger("deactivate", null, this._uiHash(this));
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", null, this._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}

		if (this.placeholder) {
			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
			if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
			if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();

			$.extend(this, {
				helper: null,
				dragging: false,
				reverting: false,
				_noFinalSort: null
			});

			if(this.domPosition.prev) {
				$(this.domPosition.prev).after(this.currentItem);
			} else {
				$(this.domPosition.parent).prepend(this.currentItem);
			}
		}

		return this;

	},

	serialize: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected);
		var str = []; o = o || {};

		$(items).each(function() {
			var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
			if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
		});

		if(!str.length && o.key) {
			str.push(o.key + '=');
		}

		return str.join('&');

	},

	toArray: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected);
		var ret = []; o = o || {};

		items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
		return ret;

	},

	/* Be careful with the following core functions */
	_intersectsWith: function(item) {

		var x1 = this.positionAbs.left,
			x2 = x1 + this.helperProportions.width,
			y1 = this.positionAbs.top,
			y2 = y1 + this.helperProportions.height;

		var l = item.left,
			r = l + item.width,
			t = item.top,
			b = t + item.height;

		var dyClick = this.offset.click.top,
			dxClick = this.offset.click.left;

		var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;

		if(	   this.options.tolerance == "pointer"
			|| this.options.forcePointerForContainers
			|| (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
		) {
			return isOverElement;
		} else {

			return (l < x1 + (this.helperProportions.width / 2) // Right Half
				&& x2 - (this.helperProportions.width / 2) < r // Left Half
				&& t < y1 + (this.helperProportions.height / 2) // Bottom Half
				&& y2 - (this.helperProportions.height / 2) < b ); // Top Half

		}
	},

	_intersectsWithPointer: function(item) {

		var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
			isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
			isOverElement = isOverElementHeight && isOverElementWidth,
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (!isOverElement)
			return false;

		return this.floating ?
			( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
			: ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );

	},

	_intersectsWithSides: function(item) {

		var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
			isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (this.floating && horizontalDirection) {
			return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
		} else {
			return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
		}

	},

	_getDragVerticalDirection: function() {
		var delta = this.positionAbs.top - this.lastPositionAbs.top;
		return delta != 0 && (delta > 0 ? "down" : "up");
	},

	_getDragHorizontalDirection: function() {
		var delta = this.positionAbs.left - this.lastPositionAbs.left;
		return delta != 0 && (delta > 0 ? "right" : "left");
	},

	refresh: function(event) {
		this._refreshItems(event);
		this.refreshPositions();
		return this;
	},

	_connectWith: function() {
		var options = this.options;
		return options.connectWith.constructor == String
			? [options.connectWith]
			: options.connectWith;
	},

	_getItemsAsjQuery: function(connected) {

		var items = [];
		var queries = [];
		var connectWith = this._connectWith();

		if(connectWith && connected) {
			for (var i = connectWith.length - 1; i >= 0; i--){
				var cur = $(connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], this.widgetName);
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]);
					}
				};
			};
		}

		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]);

		for (var i = queries.length - 1; i >= 0; i--){
			queries[i][0].each(function() {
				items.push(this);
			});
		};

		return $(items);

	},

	_removeCurrentsFromItems: function() {

		var list = this.currentItem.find(":data(" + this.widgetName + "-item)");

		for (var i=0; i < this.items.length; i++) {

			for (var j=0; j < list.length; j++) {
				if(list[j] == this.items[i].item[0])
					this.items.splice(i,1);
			};

		};

	},

	_refreshItems: function(event) {

		this.items = [];
		this.containers = [this];
		var items = this.items;
		var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
		var connectWith = this._connectWith();

		if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
			for (var i = connectWith.length - 1; i >= 0; i--){
				var cur = $(connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], this.widgetName);
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
						this.containers.push(inst);
					}
				};
			};
		}

		for (var i = queries.length - 1; i >= 0; i--) {
			var targetData = queries[i][1];
			var _queries = queries[i][0];

			for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
				var item = $(_queries[j]);

				item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)

				items.push({
					item: item,
					instance: targetData,
					width: 0, height: 0,
					left: 0, top: 0
				});
			};
		};

	},

	refreshPositions: function(fast) {

		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
		if(this.offsetParent && this.helper) {
			this.offset.parent = this._getParentOffset();
		}

		for (var i = this.items.length - 1; i >= 0; i--){
			var item = this.items[i];

			//We ignore calculating positions of all connected containers when we're not over them
			if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
				continue;

			var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;

			if (!fast) {
				item.width = t.outerWidth();
				item.height = t.outerHeight();
			}

			var p = t.offset();
			item.left = p.left;
			item.top = p.top;
		};

		if(this.options.custom && this.options.custom.refreshContainers) {
			this.options.custom.refreshContainers.call(this);
		} else {
			for (var i = this.containers.length - 1; i >= 0; i--){
				var p = this.containers[i].element.offset();
				this.containers[i].containerCache.left = p.left;
				this.containers[i].containerCache.top = p.top;
				this.containers[i].containerCache.width	= this.containers[i].element.outerWidth();
				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
			};
		}

		return this;
	},

	_createPlaceholder: function(that) {
		that = that || this;
		var o = that.options;

		if(!o.placeholder || o.placeholder.constructor == String) {
			var className = o.placeholder;
			o.placeholder = {
				element: function() {

					var el = $(document.createElement(that.currentItem[0].nodeName))
						.addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
						.removeClass("ui-sortable-helper")[0];

					if(!className)
						el.style.visibility = "hidden";

					return el;
				},
				update: function(container, p) {

					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
					if(className && !o.forcePlaceholderSize) return;

					//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
					if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css('paddingTop')||0, 10) - parseInt(that.currentItem.css('paddingBottom')||0, 10)); };
					if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css('paddingLeft')||0, 10) - parseInt(that.currentItem.css('paddingRight')||0, 10)); };
				}
			};
		}

		//Create the placeholder
		that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));

		//Append it after the actual current item
		that.currentItem.after(that.placeholder);

		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
		o.placeholder.update(that, that.placeholder);

	},

	_contactContainers: function(event) {

		// get innermost container that intersects with item
		var innermostContainer = null, innermostIndex = null;


		for (var i = this.containers.length - 1; i >= 0; i--){

			// never consider a container that's located within the item itself
			if($.contains(this.currentItem[0], this.containers[i].element[0]))
				continue;

			if(this._intersectsWith(this.containers[i].containerCache)) {

				// if we've already found a container and it's more "inner" than this, then continue
				if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0]))
					continue;

				innermostContainer = this.containers[i];
				innermostIndex = i;

			} else {
				// container doesn't intersect. trigger "out" event if necessary
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", event, this._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}

		// if no intersecting containers found, return
		if(!innermostContainer) return;

		// move the item into the container if it's not there already
		if(this.containers.length === 1) {
			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
			this.containers[innermostIndex].containerCache.over = 1;
		} else if(this.currentContainer != this.containers[innermostIndex]) {

			//When entering a new container, we will find the item with the least distance and append our item near it
			var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top'];
			for (var j = this.items.length - 1; j >= 0; j--) {
				if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue;
				var cur = this.containers[innermostIndex].floating ? this.items[j].item.offset().left : this.items[j].item.offset().top;
				if(Math.abs(cur - base) < dist) {
					dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
					this.direction = (cur - base > 0) ? 'down' : 'up';
				}
			}

			if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
				return;

			this.currentContainer = this.containers[innermostIndex];
			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
			this._trigger("change", event, this._uiHash());
			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));

			//Update the placeholder
			this.options.placeholder.update(this.currentContainer, this.placeholder);

			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
			this.containers[innermostIndex].containerCache.over = 1;
		}


	},

	_createHelper: function(event) {

		var o = this.options;
		var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);

		if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
			$(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);

		if(helper[0] == this.currentItem[0])
			this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };

		if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
		if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj == 'string') {
			obj = obj.split(' ');
		}
		if ($.isArray(obj)) {
			obj = {left: +obj[0], top: +obj[1] || 0};
		}
		if ('left' in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ('right' in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ('top' in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ('bottom' in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_getParentOffset: function() {


		//Get the offsetParent and cache its position
		this.offsetParent = this.helper.offsetParent();
		var po = this.offsetParent.offset();

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
		|| (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
			po = { top: 0, left: 0 };

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};

	},

	_getRelativeOffset: function() {

		if(this.cssPosition == "relative") {
			var p = this.currentItem.position();
			return {
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
			};
		} else {
			return { top: 0, left: 0 };
		}

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var o = this.options;
		if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
		if(o.containment == 'document' || o.containment == 'window') this.containment = [
			0 - this.offset.relative.left - this.offset.parent.left,
			0 - this.offset.relative.top - this.offset.parent.top,
			$(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
			($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
		];

		if(!(/^(document|window|parent)$/).test(o.containment)) {
			var ce = $(o.containment)[0];
			var co = $(o.containment).offset();
			var over = ($(ce).css("overflow") != 'hidden');

			this.containment = [
				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
				co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
				co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
			];
		}

	},

	_convertPositionTo: function(d, pos) {

		if(!pos) pos = this.position;
		var mod = d == "absolute" ? 1 : -1;
		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		return {
			top: (
				pos.top																	// The absolute mouse position
				+ this.offset.relative.top * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.top * mod											// The offsetParent's offset without borders (offset + border)
				- ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
			),
			left: (
				pos.left																// The absolute mouse position
				+ this.offset.relative.left * mod										// Only for relative positioned nodes: Relative offset from element to offset parent
				+ this.offset.parent.left * mod											// The offsetParent's offset without borders (offset + border)
				- ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
			)
		};

	},

	_generatePosition: function(event) {

		var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		// This is another very weird special case that only happens for relative elements:
		// 1. If the css position is relative
		// 2. and the scroll parent is the document or similar to the offset parent
		// we have to refresh the relative offset during the scroll so there are no jumps
		if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
			this.offset.relative = this._getRelativeOffset();
		}

		var pageX = event.pageX;
		var pageY = event.pageY;

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		if(this.originalPosition) { //If we are not dragging yet, we won't check for options

			if(this.containment) {
				if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
				if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
				if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
				if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
			}

			if(o.grid) {
				var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
				pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
				pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

		}

		return {
			top: (
				pageY																// The absolute mouse position
				- this.offset.click.top													// Click offset (relative to the element)
				- this.offset.relative.top												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.top												// The offsetParent's offset without borders (offset + border)
				+ ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
			),
			left: (
				pageX																// The absolute mouse position
				- this.offset.click.left												// Click offset (relative to the element)
				- this.offset.relative.left												// Only for relative positioned nodes: Relative offset from element to offset parent
				- this.offset.parent.left												// The offsetParent's offset without borders (offset + border)
				+ ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
			)
		};

	},

	_rearrange: function(event, i, a, hardRefresh) {

		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));

		//Various things done here to improve the performance:
		// 1. we create a setTimeout, that calls refreshPositions
		// 2. on the instance, we have a counter variable, that get's higher after every append
		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
		// 4. this lets only the last addition to the timeout stack through
		this.counter = this.counter ? ++this.counter : 1;
		var counter = this.counter;

		this._delay(function() {
			if(counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
		});

	},

	_clear: function(event, noPropagation) {

		this.reverting = false;
		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
		// everything else normalized again
		var delayedTriggers = [];

		// We first have to update the dom position of the actual currentItem
		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
		if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
		this._noFinalSort = null;

		if(this.helper[0] == this.currentItem[0]) {
			for(var i in this._storedCSS) {
				if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
			}
			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
		} else {
			this.currentItem.show();
		}

		if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
		if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed

		// Check if the items Container has Changed and trigger appropriate
		// events.
		if (this !== this.currentContainer) {
			if(!noPropagation) {
				delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
				delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
				delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
			}
		}


		//Post events to containers
		for (var i = this.containers.length - 1; i >= 0; i--){
			if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
			if(this.containers[i].containerCache.over) {
				delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
				this.containers[i].containerCache.over = 0;
			}
		}

		//Do what was originally in plugins
		if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
		if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
		if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index

		this.dragging = false;
		if(this.cancelHelperRemoval) {
			if(!noPropagation) {
				this._trigger("beforeStop", event, this._uiHash());
				for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
				this._trigger("stop", event, this._uiHash());
			}

			this.fromOutside = false;
			return false;
		}

		if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());

		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);

		if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;

		if(!noPropagation) {
			for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
			this._trigger("stop", event, this._uiHash());
		}

		this.fromOutside = false;
		return true;

	},

	_trigger: function() {
		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
			this.cancel();
		}
	},

	_uiHash: function(_inst) {
		var inst = _inst || this;
		return {
			helper: inst.helper,
			placeholder: inst.placeholder || $([]),
			position: inst.position,
			originalPosition: inst.originalPosition,
			offset: inst.positionAbs,
			item: inst.currentItem,
			sender: _inst ? _inst.element : null
		};
	}

});

})(jQuery);
(function( $, undefined ) {

var uid = 0,
	hideProps = {},
	showProps = {};

hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
	hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
showProps.height = showProps.paddingTop = showProps.paddingBottom =
	showProps.borderTopWidth = showProps.borderBottomWidth = "show";

$.widget( "ui.accordion", {
	version: "1.9.0",
	options: {
		active: 0,
		animate: {},
		collapsible: false,
		event: "click",
		header: "> li > :first-child,> :not(li):even",
		heightStyle: "auto",
		icons: {
			activeHeader: "ui-icon-triangle-1-s",
			header: "ui-icon-triangle-1-e"
		},

		// callbacks
		activate: null,
		beforeActivate: null
	},

	_create: function() {
		var accordionId = this.accordionId = "ui-accordion-" +
				(this.element.attr( "id" ) || ++uid),
			options = this.options;

		this.prevShow = this.prevHide = $();
		this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );

		this.headers = this.element.find( options.header )
			.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
		this._hoverable( this.headers );
		this._focusable( this.headers );

		this.headers.next()
			.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
			.hide();

		// don't allow collapsible: false and active: false
		if ( !options.collapsible && options.active === false ) {
			options.active = 0;
		}
		// handle negative values
		if ( options.active < 0 ) {
			options.active += this.headers.length;
		}
		this.active = this._findActive( options.active )
			.addClass( "ui-accordion-header-active ui-state-active" )
			.toggleClass( "ui-corner-all ui-corner-top" );
		this.active.next()
			.addClass( "ui-accordion-content-active" )
			.show();

		this._createIcons();
		this.originalHeight = this.element[0].style.height;
		this.refresh();

		// ARIA
		this.element.attr( "role", "tablist" );

		this.headers
			.attr( "role", "tab" )
			.each(function( i ) {
				var header = $( this ),
					headerId = header.attr( "id" ),
					panel = header.next(),
					panelId = panel.attr( "id" );
				if ( !headerId ) {
					headerId = accordionId + "-header-" + i;
					header.attr( "id", headerId );
				}
				if ( !panelId ) {
					panelId = accordionId + "-panel-" + i;
					panel.attr( "id", panelId );
				}
				header.attr( "aria-controls", panelId );
				panel.attr( "aria-labelledby", headerId );
			})
			.next()
				.attr( "role", "tabpanel" );

		this.headers
			.not( this.active )
			.attr({
				"aria-selected": "false",
				tabIndex: -1
			})
			.next()
				.attr({
					"aria-expanded": "false",
					"aria-hidden": "true"
				})
				.hide();

		// make sure at least one header is in the tab order
		if ( !this.active.length ) {
			this.headers.eq( 0 ).attr( "tabIndex", 0 );
		} else {
			this.active.attr({
				"aria-selected": "true",
				tabIndex: 0
			})
			.next()
				.attr({
					"aria-expanded": "true",
					"aria-hidden": "false"
				});
		}

		this._on( this.headers, { keydown: "_keydown" });
		this._on( this.headers.next(), { keydown: "_panelKeyDown" });
		this._setupEvents( options.event );
	},

	_getCreateEventData: function() {
		return {
			header: this.active,
			content: !this.active.length ? $() : this.active.next()
		};
	},

	_createIcons: function() {
		var icons = this.options.icons;
		if ( icons ) {
			$( "<span>" )
				.addClass( "ui-accordion-header-icon ui-icon " + icons.header )
				.prependTo( this.headers );
			this.active.children( ".ui-accordion-header-icon" )
				.removeClass( icons.header )
				.addClass( icons.activeHeader );
			this.headers.addClass( "ui-accordion-icons" );
		}
	},

	_destroyIcons: function() {
		this.headers
			.removeClass( "ui-accordion-icons" )
			.children( ".ui-accordion-header-icon" )
				.remove();
	},

	_destroy: function() {
		var contents;

		// clean up main element
		this.element
			.removeClass( "ui-accordion ui-widget ui-helper-reset" )
			.removeAttr( "role" );

		// clean up headers
		this.headers
			.removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
			.removeAttr( "role" )
			.removeAttr( "aria-selected" )
			.removeAttr( "aria-controls" )
			.removeAttr( "tabIndex" )
			.each(function() {
				if ( /^ui-accordion/.test( this.id ) ) {
					this.removeAttribute( "id" );
				}
			});
		this._destroyIcons();

		// clean up content panels
		contents = this.headers.next()
			.css( "display", "" )
			.removeAttr( "role" )
			.removeAttr( "aria-expanded" )
			.removeAttr( "aria-hidden" )
			.removeAttr( "aria-labelledby" )
			.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
			.each(function() {
				if ( /^ui-accordion/.test( this.id ) ) {
					this.removeAttribute( "id" );
				}
			});
		if ( this.options.heightStyle !== "content" ) {
			this.element.css( "height", this.originalHeight );
			contents.css( "height", "" );
		}
	},

	_setOption: function( key, value ) {
		if ( key === "active" ) {
			// _activate() will handle invalid values and update this.options
			this._activate( value );
			return;
		}

		if ( key === "event" ) {
			if ( this.options.event ) {
				this._off( this.headers, this.options.event );
			}
			this._setupEvents( value );
		}

		this._super( key, value );

		// setting collapsible: false while collapsed; open first panel
		if ( key === "collapsible" && !value && this.options.active === false ) {
			this._activate( 0 );
		}

		if ( key === "icons" ) {
			this._destroyIcons();
			if ( value ) {
				this._createIcons();
			}
		}

		// #5332 - opacity doesn't cascade to positioned elements in IE
		// so we need to add the disabled class to the headers and panels
		if ( key === "disabled" ) {
			this.headers.add( this.headers.next() )
				.toggleClass( "ui-state-disabled", !!value );
		}
	},

	_keydown: function( event ) {
		if ( event.altKey || event.ctrlKey ) {
			return;
		}

		var keyCode = $.ui.keyCode,
			length = this.headers.length,
			currentIndex = this.headers.index( event.target ),
			toFocus = false;

		switch ( event.keyCode ) {
			case keyCode.RIGHT:
			case keyCode.DOWN:
				toFocus = this.headers[ ( currentIndex + 1 ) % length ];
				break;
			case keyCode.LEFT:
			case keyCode.UP:
				toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
				break;
			case keyCode.SPACE:
			case keyCode.ENTER:
				this._eventHandler( event );
				break;
			case keyCode.HOME:
				toFocus = this.headers[ 0 ];
				break;
			case keyCode.END:
				toFocus = this.headers[ length - 1 ];
				break;
		}

		if ( toFocus ) {
			$( event.target ).attr( "tabIndex", -1 );
			$( toFocus ).attr( "tabIndex", 0 );
			toFocus.focus();
			event.preventDefault();
		}
	},

	_panelKeyDown : function( event ) {
		if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
			$( event.currentTarget ).prev().focus();
		}
	},

	refresh: function() {
		var maxHeight, overflow,
			heightStyle = this.options.heightStyle,
			parent = this.element.parent();

		this.element.css( "height", this.originalHeight );

		if ( heightStyle === "fill" ) {
			// IE 6 treats height like minHeight, so we need to turn off overflow
			// in order to get a reliable height
			// we use the minHeight support test because we assume that only
			// browsers that don't support minHeight will treat height as minHeight
			if ( !$.support.minHeight ) {
				overflow = parent.css( "overflow" );
				parent.css( "overflow", "hidden");
			}
			maxHeight = parent.height();
			this.element.siblings( ":visible" ).each(function() {
				var elem = $( this ),
					position = elem.css( "position" );

				if ( position === "absolute" || position === "fixed" ) {
					return;
				}
				maxHeight -= elem.outerHeight( true );
			});
			if ( overflow ) {
				parent.css( "overflow", overflow );
			}

			this.headers.each(function() {
				maxHeight -= $( this ).outerHeight( true );
			});

			this.headers.next()
				.each(function() {
					$( this ).height( Math.max( 0, maxHeight -
						$( this ).innerHeight() + $( this ).height() ) );
				})
				.css( "overflow", "auto" );
		} else if ( heightStyle === "auto" ) {
			maxHeight = 0;
			this.headers.next()
				.each(function() {
					maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
				})
				.height( maxHeight );
		}

		if ( heightStyle !== "content" ) {
			this.element.height( this.element.height() );
		}
	},

	_activate: function( index ) {
		var active = this._findActive( index )[ 0 ];

		// trying to activate the already active panel
		if ( active === this.active[ 0 ] ) {
			return;
		}

		// trying to collapse, simulate a click on the currently active header
		active = active || this.active[ 0 ];

		this._eventHandler({
			target: active,
			currentTarget: active,
			preventDefault: $.noop
		});
	},

	_findActive: function( selector ) {
		return typeof selector === "number" ? this.headers.eq( selector ) : $();
	},

	_setupEvents: function( event ) {
		var events = {};
		if ( !event ) {
			return;
		}
		$.each( event.split(" "), function( index, eventName ) {
			events[ eventName ] = "_eventHandler";
		});
		this._on( this.headers, events );
	},

	_eventHandler: function( event ) {
		var options = this.options,
			active = this.active,
			clicked = $( event.currentTarget ),
			clickedIsActive = clicked[ 0 ] === active[ 0 ],
			collapsing = clickedIsActive && options.collapsible,
			toShow = collapsing ? $() : clicked.next(),
			toHide = active.next(),
			eventData = {
				oldHeader: active,
				oldPanel: toHide,
				newHeader: collapsing ? $() : clicked,
				newPanel: toShow
			};

		event.preventDefault();

		if (
				// click on active header, but not collapsible
				( clickedIsActive && !options.collapsible ) ||
				// allow canceling activation
				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
			return;
		}

		options.active = collapsing ? false : this.headers.index( clicked );

		// when the call to ._toggle() comes after the class changes
		// it causes a very odd bug in IE 8 (see #6720)
		this.active = clickedIsActive ? $() : clicked;
		this._toggle( eventData );

		// switch classes
		// corner classes on the previously active header stay after the animation
		active.removeClass( "ui-accordion-header-active ui-state-active" );
		if ( options.icons ) {
			active.children( ".ui-accordion-header-icon" )
				.removeClass( options.icons.activeHeader )
				.addClass( options.icons.header );
		}

		if ( !clickedIsActive ) {
			clicked
				.removeClass( "ui-corner-all" )
				.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
			if ( options.icons ) {
				clicked.children( ".ui-accordion-header-icon" )
					.removeClass( options.icons.header )
					.addClass( options.icons.activeHeader );
			}

			clicked
				.next()
				.addClass( "ui-accordion-content-active" );
		}
	},

	_toggle: function( data ) {
		var toShow = data.newPanel,
			toHide = this.prevShow.length ? this.prevShow : data.oldPanel;

		// handle activating a panel during the animation for another activation
		this.prevShow.add( this.prevHide ).stop( true, true );
		this.prevShow = toShow;
		this.prevHide = toHide;

		if ( this.options.animate ) {
			this._animate( toShow, toHide, data );
		} else {
			toHide.hide();
			toShow.show();
			this._toggleComplete( data );
		}

		toHide.attr({
			"aria-expanded": "false",
			"aria-hidden": "true"
		});
		toHide.prev().attr( "aria-selected", "false" );
		// if we're switching panels, remove the old header from the tab order
		// if we're opening from collapsed state, remove the previous header from the tab order
		// if we're collapsing, then keep the collapsing header in the tab order
		if ( toShow.length && toHide.length ) {
			toHide.prev().attr( "tabIndex", -1 );
		} else if ( toShow.length ) {
			this.headers.filter(function() {
				return $( this ).attr( "tabIndex" ) === 0;
			})
			.attr( "tabIndex", -1 );
		}

		toShow
			.attr({
				"aria-expanded": "true",
				"aria-hidden": "false"
			})
			.prev()
				.attr({
					"aria-selected": "true",
					tabIndex: 0
				});
	},

	_animate: function( toShow, toHide, data ) {
		var total, easing, duration,
			that = this,
			adjust = 0,
			down = toShow.length &&
				( !toHide.length || ( toShow.index() < toHide.index() ) ),
			animate = this.options.animate || {},
			options = down && animate.down || animate,
			complete = function() {
				that._toggleComplete( data );
			};

		if ( typeof options === "number" ) {
			duration = options;
		}
		if ( typeof options === "string" ) {
			easing = options;
		}
		// fall back from options to animation in case of partial down settings
		easing = easing || options.easing || animate.easing;
		duration = duration || options.duration || animate.duration;

		if ( !toHide.length ) {
			return toShow.animate( showProps, duration, easing, complete );
		}
		if ( !toShow.length ) {
			return toHide.animate( hideProps, duration, easing, complete );
		}

		total = toShow.show().outerHeight();
		toHide.animate( hideProps, {
			duration: duration,
			easing: easing,
			step: function( now, fx ) {
				fx.now = Math.round( now );
			}
		});
		toShow
			.hide()
			.animate( showProps, {
				duration: duration,
				easing: easing,
				complete: complete,
				step: function( now, fx ) {
					fx.now = Math.round( now );
					if ( fx.prop !== "height" ) {
						adjust += fx.now;
					} else if ( that.options.heightStyle !== "content" ) {
						fx.now = Math.round( total - toHide.outerHeight() - adjust );
						adjust = 0;
					}
				}
			});
	},

	_toggleComplete: function( data ) {
		var toHide = data.oldPanel;

		toHide
			.removeClass( "ui-accordion-content-active" )
			.prev()
				.removeClass( "ui-corner-top" )
				.addClass( "ui-corner-all" );

		// Work around for rendering bug in IE (#5421)
		if ( toHide.length ) {
			toHide.parent()[0].className = toHide.parent()[0].className;
		}

		this._trigger( "activate", null, data );
	}
});



// DEPRECATED
if ( $.uiBackCompat !== false ) {
	// navigation options
	(function( $, prototype ) {
		$.extend( prototype.options, {
			navigation: false,
			navigationFilter: function() {
				return this.href.toLowerCase() === location.href.toLowerCase();
			}
		});

		var _create = prototype._create;
		prototype._create = function() {
			if ( this.options.navigation ) {
				var that = this,
					headers = this.element.find( this.options.header ),
					content = headers.next(),
					current = headers.add( content )
						.find( "a" )
						.filter( this.options.navigationFilter )
						[ 0 ];
				if ( current ) {
					headers.add( content ).each( function( index ) {
						if ( $.contains( this, current ) ) {
							that.options.active = Math.floor( index / 2 );
							return false;
						}
					});
				}
			}
			_create.call( this );
		};
	}( jQuery, jQuery.ui.accordion.prototype ) );

	// height options
	(function( $, prototype ) {
		$.extend( prototype.options, {
			heightStyle: null, // remove default so we fall back to old values
			autoHeight: true, // use heightStyle: "auto"
			clearStyle: false, // use heightStyle: "content"
			fillSpace: false // use heightStyle: "fill"
		});

		var _create = prototype._create,
			_setOption = prototype._setOption;

		$.extend( prototype, {
			_create: function() {
				this.options.heightStyle = this.options.heightStyle ||
					this._mergeHeightStyle();

				_create.call( this );
			},

			_setOption: function( key, value ) {
				if ( key === "autoHeight" || key === "clearStyle" || key === "fillSpace" ) {
					this.options.heightStyle = this._mergeHeightStyle();
				}
				_setOption.apply( this, arguments );
			},

			_mergeHeightStyle: function() {
				var options = this.options;

				if ( options.fillSpace ) {
					return "fill";
				}

				if ( options.clearStyle ) {
					return "content";
				}

				if ( options.autoHeight ) {
					return "auto";
				}
			}
		});
	}( jQuery, jQuery.ui.accordion.prototype ) );

	// icon options
	(function( $, prototype ) {
		$.extend( prototype.options.icons, {
			activeHeader: null, // remove default so we fall back to old values
			headerSelected: "ui-icon-triangle-1-s"
		});

		var _createIcons = prototype._createIcons;
		prototype._createIcons = function() {
			if ( this.options.icons ) {
				this.options.icons.activeHeader = this.options.icons.activeHeader ||
					this.options.icons.headerSelected;
			}
			_createIcons.call( this );
		};
	}( jQuery, jQuery.ui.accordion.prototype ) );

	// expanded active option, activate method
	(function( $, prototype ) {
		prototype.activate = prototype._activate;

		var _findActive = prototype._findActive;
		prototype._findActive = function( index ) {
			if ( index === -1 ) {
				index = false;
			}
			if ( index && typeof index !== "number" ) {
				index = this.headers.index( this.headers.filter( index ) );
				if ( index === -1 ) {
					index = false;
				}
			}
			return _findActive.call( this, index );
		};
	}( jQuery, jQuery.ui.accordion.prototype ) );

	// resize method
	jQuery.ui.accordion.prototype.resize = jQuery.ui.accordion.prototype.refresh;

	// change events
	(function( $, prototype ) {
		$.extend( prototype.options, {
			change: null,
			changestart: null
		});

		var _trigger = prototype._trigger;
		prototype._trigger = function( type, event, data ) {
			var ret = _trigger.apply( this, arguments );
			if ( !ret ) {
				return false;
			}

			if ( type === "beforeActivate" ) {
				ret = _trigger.call( this, "changestart", event, {
					oldHeader: data.oldHeader,
					oldContent: data.oldPanel,
					newHeader: data.newHeader,
					newContent: data.newPanel
				});
			} else if ( type === "activate" ) {
				ret = _trigger.call( this, "change", event, {
					oldHeader: data.oldHeader,
					oldContent: data.oldPanel,
					newHeader: data.newHeader,
					newContent: data.newPanel
				});
			}
			return ret;
		};
	}( jQuery, jQuery.ui.accordion.prototype ) );

	// animated option
	// NOTE: this only provides support for "slide", "bounceslide", and easings
	// not the full $.ui.accordion.animations API
	(function( $, prototype ) {
		$.extend( prototype.options, {
			animate: null,
			animated: "slide"
		});

		var _create = prototype._create;
		prototype._create = function() {
			var options = this.options;
			if ( options.animate === null ) {
				if ( !options.animated ) {
					options.animate = false;
				} else if ( options.animated === "slide" ) {
					options.animate = 300;
				} else if ( options.animated === "bounceslide" ) {
					options.animate = {
						duration: 200,
						down: {
							easing: "easeOutBounce",
							duration: 1000
						}
					};
				} else {
					options.animate = options.animated;
				}
			}

			_create.call( this );
		};
	}( jQuery, jQuery.ui.accordion.prototype ) );
}

})( jQuery );
(function( $, undefined ) {

// used to prevent race conditions with remote data sources
var requestIndex = 0;

$.widget( "ui.autocomplete", {
	version: "1.9.0",
	defaultElement: "<input>",
	options: {
		appendTo: "body",
		autoFocus: false,
		delay: 300,
		minLength: 1,
		position: {
			my: "left top",
			at: "left bottom",
			collision: "none"
		},
		source: null,

		// callbacks
		change: null,
		close: null,
		focus: null,
		open: null,
		response: null,
		search: null,
		select: null
	},

	pending: 0,

	_create: function() {
		// Some browsers only repeat keydown events, not keypress events,
		// so we use the suppressKeyPress flag to determine if we've already
		// handled the keydown event. #7269
		// Unfortunately the code for & in keypress is the same as the up arrow,
		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
		// events when we know the keydown event was used to modify the
		// search term. #7799
		var suppressKeyPress, suppressKeyPressRepeat, suppressInput;

		this.isMultiLine = this._isMultiLine();
		this.valueMethod = this.element[ this.element.is( "input,textarea" ) ? "val" : "text" ];
		this.isNewMenu = true;

		this.element
			.addClass( "ui-autocomplete-input" )
			.attr( "autocomplete", "off" );

		this._on({
			keydown: function( event ) {
				if ( this.element.prop( "readOnly" ) ) {
					suppressKeyPress = true;
					suppressInput = true;
					suppressKeyPressRepeat = true;
					return;
				}

				suppressKeyPress = false;
				suppressInput = false;
				suppressKeyPressRepeat = false;
				var keyCode = $.ui.keyCode;
				switch( event.keyCode ) {
				case keyCode.PAGE_UP:
					suppressKeyPress = true;
					this._move( "previousPage", event );
					break;
				case keyCode.PAGE_DOWN:
					suppressKeyPress = true;
					this._move( "nextPage", event );
					break;
				case keyCode.UP:
					suppressKeyPress = true;
					this._keyEvent( "previous", event );
					break;
				case keyCode.DOWN:
					suppressKeyPress = true;
					this._keyEvent( "next", event );
					break;
				case keyCode.ENTER:
				case keyCode.NUMPAD_ENTER:
					// when menu is open and has focus
					if ( this.menu.active ) {
						// #6055 - Opera still allows the keypress to occur
						// which causes forms to submit
						suppressKeyPress = true;
						event.preventDefault();
						this.menu.select( event );
					}
					break;
				case keyCode.TAB:
					if ( this.menu.active ) {
						this.menu.select( event );
					}
					break;
				case keyCode.ESCAPE:
					if ( this.menu.element.is( ":visible" ) ) {
						this._value( this.term );
						this.close( event );
						// Different browsers have different default behavior for escape
						// Single press can mean undo or clear
						// Double press in IE means clear the whole form
						event.preventDefault();
					}
					break;
				default:
					suppressKeyPressRepeat = true;
					// search timeout should be triggered before the input value is changed
					this._searchTimeout( event );
					break;
				}
			},
			keypress: function( event ) {
				if ( suppressKeyPress ) {
					suppressKeyPress = false;
					event.preventDefault();
					return;
				}
				if ( suppressKeyPressRepeat ) {
					return;
				}

				// replicate some key handlers to allow them to repeat in Firefox and Opera
				var keyCode = $.ui.keyCode;
				switch( event.keyCode ) {
				case keyCode.PAGE_UP:
					this._move( "previousPage", event );
					break;
				case keyCode.PAGE_DOWN:
					this._move( "nextPage", event );
					break;
				case keyCode.UP:
					this._keyEvent( "previous", event );
					break;
				case keyCode.DOWN:
					this._keyEvent( "next", event );
					break;
				}
			},
			input: function( event ) {
				if ( suppressInput ) {
					suppressInput = false;
					event.preventDefault();
					return;
				}
				this._searchTimeout( event );
			},
			focus: function() {
				this.selectedItem = null;
				this.previous = this._value();
			},
			blur: function( event ) {
				if ( this.cancelBlur ) {
					delete this.cancelBlur;
					return;
				}

				clearTimeout( this.searching );
				this.close( event );
				this._change( event );
			}
		});

		this._initSource();
		this.menu = $( "<ul>" )
			.addClass( "ui-autocomplete" )
			.appendTo( this.document.find( this.options.appendTo || "body" )[ 0 ] )
			.menu({
				// custom key handling for now
				input: $(),
				// disable ARIA support, the live region takes care of that
				role: null
			})
			.zIndex( this.element.zIndex() + 1 )
			.hide()
			.data( "menu" );
		this._on( this.menu.element, {
			mousedown: function( event ) {
				// prevent moving focus out of the text field
				event.preventDefault();

				// IE doesn't prevent moving focus even with event.preventDefault()
				// so we set a flag to know when we should ignore the blur event
				this.cancelBlur = true;
				this._delay(function() {
					delete this.cancelBlur;
				});

				// clicking on the scrollbar causes focus to shift to the body
				// but we can't detect a mouseup or a click immediately afterward
				// so we have to track the next mousedown and close the menu if
				// the user clicks somewhere outside of the autocomplete
				var menuElement = this.menu.element[ 0 ];
				if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
					this._delay(function() {
						var that = this;
						this.document.one( "mousedown", function( event ) {
							if ( event.target !== that.element[ 0 ] &&
									event.target !== menuElement &&
									!$.contains( menuElement, event.target ) ) {
								that.close();
							}
						});
					});
				}
			},
			menufocus: function( event, ui ) {
				// #7024 - Prevent accidental activation of menu items in Firefox
				if ( this.isNewMenu ) {
					this.isNewMenu = false;
					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
						this.menu.blur();

						this.document.one( "mousemove", function() {
							$( event.target ).trigger( event.originalEvent );
						});

						return;
					}
				}

				// back compat for _renderItem using item.autocomplete, via #7810
				// TODO remove the fallback, see #8156
				var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" );
				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
					// use value to match what will end up in the input, if it was a key event
					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
						this._value( item.value );
					}
				} else {
					// Normally the input is populated with the item's value as the
					// menu is navigated, causing screen readers to notice a change and
					// announce the item. Since the focus event was canceled, this doesn't
					// happen, so we update the live region so that screen readers can
					// still notice the change and announce it.
					this.liveRegion.text( item.value );
				}
			},
			menuselect: function( event, ui ) {
				// back compat for _renderItem using item.autocomplete, via #7810
				// TODO remove the fallback, see #8156
				var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" ),
					previous = this.previous;

				// only trigger when focus was lost (click on menu)
				if ( this.element[0] !== this.document[0].activeElement ) {
					this.element.focus();
					this.previous = previous;
					// #6109 - IE triggers two focus events and the second
					// is asynchronous, so we need to reset the previous
					// term synchronously and asynchronously :-(
					this._delay(function() {
						this.previous = previous;
						this.selectedItem = item;
					});
				}

				if ( false !== this._trigger( "select", event, { item: item } ) ) {
					this._value( item.value );
				}
				// reset the term after the select event
				// this allows custom select handling to work properly
				this.term = this._value();

				this.close( event );
				this.selectedItem = item;
			}
		});

		this.liveRegion = $( "<span>", {
				role: "status",
				"aria-live": "polite"
			})
			.addClass( "ui-helper-hidden-accessible" )
			.insertAfter( this.element );

		if ( $.fn.bgiframe ) {
			 this.menu.element.bgiframe();
		}

		// turning off autocomplete prevents the browser from remembering the
		// value when navigating through history, so we re-enable autocomplete
		// if the page is unloaded before the widget is destroyed. #7790
		this._on( this.window, {
			beforeunload: function() {
				this.element.removeAttr( "autocomplete" );
			}
		});
	},

	_destroy: function() {
		clearTimeout( this.searching );
		this.element
			.removeClass( "ui-autocomplete-input" )
			.removeAttr( "autocomplete" );
		this.menu.element.remove();
		this.liveRegion.remove();
	},

	_setOption: function( key, value ) {
		this._super( key, value );
		if ( key === "source" ) {
			this._initSource();
		}
		if ( key === "appendTo" ) {
			this.menu.element.appendTo( this.document.find( value || "body" )[0] );
		}
		if ( key === "disabled" && value && this.xhr ) {
			this.xhr.abort();
		}
	},

	_isMultiLine: function() {
		// Textareas are always multi-line
		if ( this.element.is( "textarea" ) ) {
			return true;
		}
		// Inputs are always single-line, even if inside a contentEditable element
		// IE also treats inputs as contentEditable
		if ( this.element.is( "input" ) ) {
			return false;
		}
		// All other element types are determined by whether or not they're contentEditable
		return this.element.prop( "isContentEditable" );
	},

	_initSource: function() {
		var array, url,
			that = this;
		if ( $.isArray(this.options.source) ) {
			array = this.options.source;
			this.source = function( request, response ) {
				response( $.ui.autocomplete.filter( array, request.term ) );
			};
		} else if ( typeof this.options.source === "string" ) {
			url = this.options.source;
			this.source = function( request, response ) {
				if ( that.xhr ) {
					that.xhr.abort();
				}
				that.xhr = $.ajax({
					url: url,
					data: request,
					dataType: "json",
					success: function( data, status ) {
						response( data );
					},
					error: function() {
						response( [] );
					}
				});
			};
		} else {
			this.source = this.options.source;
		}
	},

	_searchTimeout: function( event ) {
		clearTimeout( this.searching );
		this.searching = this._delay(function() {
			// only search if the value has changed
			if ( this.term !== this._value() ) {
				this.selectedItem = null;
				this.search( null, event );
			}
		}, this.options.delay );
	},

	search: function( value, event ) {
		value = value != null ? value : this._value();

		// always save the actual value, not the one passed as an argument
		this.term = this._value();

		if ( value.length < this.options.minLength ) {
			return this.close( event );
		}

		if ( this._trigger( "search", event ) === false ) {
			return;
		}

		return this._search( value );
	},

	_search: function( value ) {
		this.pending++;
		this.element.addClass( "ui-autocomplete-loading" );
		this.cancelSearch = false;

		this.source( { term: value }, this._response() );
	},

	_response: function() {
		var that = this,
			index = ++requestIndex;

		return function( content ) {
			if ( index === requestIndex ) {
				that.__response( content );
			}

			that.pending--;
			if ( !that.pending ) {
				that.element.removeClass( "ui-autocomplete-loading" );
			}
		};
	},

	__response: function( content ) {
		if ( content ) {
			content = this._normalize( content );
		}
		this._trigger( "response", null, { content: content } );
		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
			this._suggest( content );
			this._trigger( "open" );
		} else {
			// use ._close() instead of .close() so we don't cancel future searches
			this._close();
		}
	},

	close: function( event ) {
		this.cancelSearch = true;
		this._close( event );
	},

	_close: function( event ) {
		if ( this.menu.element.is( ":visible" ) ) {
			this.menu.element.hide();
			this.menu.blur();
			this.isNewMenu = true;
			this._trigger( "close", event );
		}
	},

	_change: function( event ) {
		if ( this.previous !== this._value() ) {
			this._trigger( "change", event, { item: this.selectedItem } );
		}
	},

	_normalize: function( items ) {
		// assume all items have the right format when the first item is complete
		if ( items.length && items[0].label && items[0].value ) {
			return items;
		}
		return $.map( items, function( item ) {
			if ( typeof item === "string" ) {
				return {
					label: item,
					value: item
				};
			}
			return $.extend({
				label: item.label || item.value,
				value: item.value || item.label
			}, item );
		});
	},

	_suggest: function( items ) {
		var ul = this.menu.element
			.empty()
			.zIndex( this.element.zIndex() + 1 );
		this._renderMenu( ul, items );
		this.menu.refresh();

		// size and position menu
		ul.show();
		this._resizeMenu();
		ul.position( $.extend({
			of: this.element
		}, this.options.position ));

		if ( this.options.autoFocus ) {
			this.menu.next();
		}
	},

	_resizeMenu: function() {
		var ul = this.menu.element;
		ul.outerWidth( Math.max(
			// Firefox wraps long text (possibly a rounding bug)
			// so we add 1px to avoid the wrapping (#7513)
			ul.width( "" ).outerWidth() + 1,
			this.element.outerWidth()
		) );
	},

	_renderMenu: function( ul, items ) {
		var that = this;
		$.each( items, function( index, item ) {
			that._renderItemData( ul, item );
		});
	},

	_renderItemData: function( ul, item ) {
		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
	},

	_renderItem: function( ul, item ) {
		return $( "<li>" )
			.append( $( "<a>" ).text( item.label ) )
			.appendTo( ul );
	},

	_move: function( direction, event ) {
		if ( !this.menu.element.is( ":visible" ) ) {
			this.search( null, event );
			return;
		}
		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
				this.menu.isLastItem() && /^next/.test( direction ) ) {
			this._value( this.term );
			this.menu.blur();
			return;
		}
		this.menu[ direction ]( event );
	},

	widget: function() {
		return this.menu.element;
	},

	_value: function( value ) {
		return this.valueMethod.apply( this.element, arguments );
	},

	_keyEvent: function( keyEvent, event ) {
		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
			this._move( keyEvent, event );

			// prevents moving cursor to beginning/end of the text field in some browsers
			event.preventDefault();
		}
	}
});

$.extend( $.ui.autocomplete, {
	escapeRegex: function( value ) {
		return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
	},
	filter: function(array, term) {
		var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
		return $.grep( array, function(value) {
			return matcher.test( value.label || value.value || value );
		});
	}
});


// live region extension, adding a `messages` option
// NOTE: This is an experimental API. We are still investigating
// a full solution for string manipulation and internationalization.
$.widget( "ui.autocomplete", $.ui.autocomplete, {
	options: {
		messages: {
			noResults: "No search results.",
			results: function( amount ) {
				return amount + ( amount > 1 ? " results are" : " result is" ) +
					" available, use up and down arrow keys to navigate.";
			}
		}
	},

	__response: function( content ) {
		var message;
		this._superApply( arguments );
		if ( this.options.disabled || this.cancelSearch ) {
			return;
		}
		if ( content && content.length ) {
			message = this.options.messages.results( content.length );
		} else {
			message = this.options.messages.noResults;
		}
		this.liveRegion.text( message );
	}
});


}( jQuery ));
(function( $, undefined ) {

var lastActive, startXPos, startYPos, clickDragged,
	baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
	stateClasses = "ui-state-hover ui-state-active ",
	typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
	formResetHandler = function() {
		var buttons = $( this ).find( ":ui-button" );
		setTimeout(function() {
			buttons.button( "refresh" );
		}, 1 );
	},
	radioGroup = function( radio ) {
		var name = radio.name,
			form = radio.form,
			radios = $( [] );
		if ( name ) {
			if ( form ) {
				radios = $( form ).find( "[name='" + name + "']" );
			} else {
				radios = $( "[name='" + name + "']", radio.ownerDocument )
					.filter(function() {
						return !this.form;
					});
			}
		}
		return radios;
	};

$.widget( "ui.button", {
	version: "1.9.0",
	defaultElement: "<button>",
	options: {
		disabled: null,
		text: true,
		label: null,
		icons: {
			primary: null,
			secondary: null
		}
	},
	_create: function() {
		this.element.closest( "form" )
			.unbind( "reset" + this.eventNamespace )
			.bind( "reset" + this.eventNamespace, formResetHandler );

		if ( typeof this.options.disabled !== "boolean" ) {
			this.options.disabled = !!this.element.prop( "disabled" );
		} else {
			this.element.prop( "disabled", this.options.disabled );
		}

		this._determineButtonType();
		this.hasTitle = !!this.buttonElement.attr( "title" );

		var that = this,
			options = this.options,
			toggleButton = this.type === "checkbox" || this.type === "radio",
			hoverClass = "ui-state-hover" + ( !toggleButton ? " ui-state-active" : "" ),
			focusClass = "ui-state-focus";

		if ( options.label === null ) {
			options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html());
		}

		this.buttonElement
			.addClass( baseClasses )
			.attr( "role", "button" )
			.bind( "mouseenter" + this.eventNamespace, function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).addClass( "ui-state-hover" );
				if ( this === lastActive ) {
					$( this ).addClass( "ui-state-active" );
				}
			})
			.bind( "mouseleave" + this.eventNamespace, function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).removeClass( hoverClass );
			})
			.bind( "click" + this.eventNamespace, function( event ) {
				if ( options.disabled ) {
					event.preventDefault();
					event.stopImmediatePropagation();
				}
			});

		this.element
			.bind( "focus" + this.eventNamespace, function() {
				// no need to check disabled, focus won't be triggered anyway
				that.buttonElement.addClass( focusClass );
			})
			.bind( "blur" + this.eventNamespace, function() {
				that.buttonElement.removeClass( focusClass );
			});

		if ( toggleButton ) {
			this.element.bind( "change" + this.eventNamespace, function() {
				if ( clickDragged ) {
					return;
				}
				that.refresh();
			});
			// if mouse moves between mousedown and mouseup (drag) set clickDragged flag
			// prevents issue where button state changes but checkbox/radio checked state
			// does not in Firefox (see ticket #6970)
			this.buttonElement
				.bind( "mousedown" + this.eventNamespace, function( event ) {
					if ( options.disabled ) {
						return;
					}
					clickDragged = false;
					startXPos = event.pageX;
					startYPos = event.pageY;
				})
				.bind( "mouseup" + this.eventNamespace, function( event ) {
					if ( options.disabled ) {
						return;
					}
					if ( startXPos !== event.pageX || startYPos !== event.pageY ) {
						clickDragged = true;
					}
			});
		}

		if ( this.type === "checkbox" ) {
			this.buttonElement.bind( "click" + this.eventNamespace, function() {
				if ( options.disabled || clickDragged ) {
					return false;
				}
				$( this ).toggleClass( "ui-state-active" );
				that.buttonElement.attr( "aria-pressed", that.element[0].checked );
			});
		} else if ( this.type === "radio" ) {
			this.buttonElement.bind( "click" + this.eventNamespace, function() {
				if ( options.disabled || clickDragged ) {
					return false;
				}
				$( this ).addClass( "ui-state-active" );
				that.buttonElement.attr( "aria-pressed", "true" );

				var radio = that.element[ 0 ];
				radioGroup( radio )
					.not( radio )
					.map(function() {
						return $( this ).button( "widget" )[ 0 ];
					})
					.removeClass( "ui-state-active" )
					.attr( "aria-pressed", "false" );
			});
		} else {
			this.buttonElement
				.bind( "mousedown" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return false;
					}
					$( this ).addClass( "ui-state-active" );
					lastActive = this;
					that.document.one( "mouseup", function() {
						lastActive = null;
					});
				})
				.bind( "mouseup" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return false;
					}
					$( this ).removeClass( "ui-state-active" );
				})
				.bind( "keydown" + this.eventNamespace, function(event) {
					if ( options.disabled ) {
						return false;
					}
					if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) {
						$( this ).addClass( "ui-state-active" );
					}
				})
				.bind( "keyup" + this.eventNamespace, function() {
					$( this ).removeClass( "ui-state-active" );
				});

			if ( this.buttonElement.is("a") ) {
				this.buttonElement.keyup(function(event) {
					if ( event.keyCode === $.ui.keyCode.SPACE ) {
						// TODO pass through original event correctly (just as 2nd argument doesn't work)
						$( this ).click();
					}
				});
			}
		}

		// TODO: pull out $.Widget's handling for the disabled option into
		// $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
		// be overridden by individual plugins
		this._setOption( "disabled", options.disabled );
		this._resetButton();
	},

	_determineButtonType: function() {
		var ancestor, labelSelector, checked;

		if ( this.element.is("[type=checkbox]") ) {
			this.type = "checkbox";
		} else if ( this.element.is("[type=radio]") ) {
			this.type = "radio";
		} else if ( this.element.is("input") ) {
			this.type = "input";
		} else {
			this.type = "button";
		}

		if ( this.type === "checkbox" || this.type === "radio" ) {
			// we don't search against the document in case the element
			// is disconnected from the DOM
			ancestor = this.element.parents().last();
			labelSelector = "label[for='" + this.element.attr("id") + "']";
			this.buttonElement = ancestor.find( labelSelector );
			if ( !this.buttonElement.length ) {
				ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
				this.buttonElement = ancestor.filter( labelSelector );
				if ( !this.buttonElement.length ) {
					this.buttonElement = ancestor.find( labelSelector );
				}
			}
			this.element.addClass( "ui-helper-hidden-accessible" );

			checked = this.element.is( ":checked" );
			if ( checked ) {
				this.buttonElement.addClass( "ui-state-active" );
			}
			this.buttonElement.prop( "aria-pressed", checked );
		} else {
			this.buttonElement = this.element;
		}
	},

	widget: function() {
		return this.buttonElement;
	},

	_destroy: function() {
		this.element
			.removeClass( "ui-helper-hidden-accessible" );
		this.buttonElement
			.removeClass( baseClasses + " " + stateClasses + " " + typeClasses )
			.removeAttr( "role" )
			.removeAttr( "aria-pressed" )
			.html( this.buttonElement.find(".ui-button-text").html() );

		if ( !this.hasTitle ) {
			this.buttonElement.removeAttr( "title" );
		}
	},

	_setOption: function( key, value ) {
		this._super( key, value );
		if ( key === "disabled" ) {
			if ( value ) {
				this.element.prop( "disabled", true );
			} else {
				this.element.prop( "disabled", false );
			}
			return;
		}
		this._resetButton();
	},

	refresh: function() {
		var isDisabled = this.element.is( ":disabled" );
		if ( isDisabled !== this.options.disabled ) {
			this._setOption( "disabled", isDisabled );
		}
		if ( this.type === "radio" ) {
			radioGroup( this.element[0] ).each(function() {
				if ( $( this ).is( ":checked" ) ) {
					$( this ).button( "widget" )
						.addClass( "ui-state-active" )
						.attr( "aria-pressed", "true" );
				} else {
					$( this ).button( "widget" )
						.removeClass( "ui-state-active" )
						.attr( "aria-pressed", "false" );
				}
			});
		} else if ( this.type === "checkbox" ) {
			if ( this.element.is( ":checked" ) ) {
				this.buttonElement
					.addClass( "ui-state-active" )
					.attr( "aria-pressed", "true" );
			} else {
				this.buttonElement
					.removeClass( "ui-state-active" )
					.attr( "aria-pressed", "false" );
			}
		}
	},

	_resetButton: function() {
		if ( this.type === "input" ) {
			if ( this.options.label ) {
				this.element.val( this.options.label );
			}
			return;
		}
		var buttonElement = this.buttonElement.removeClass( typeClasses ),
			buttonText = $( "<span></span>", this.document[0] )
				.addClass( "ui-button-text" )
				.html( this.options.label )
				.appendTo( buttonElement.empty() )
				.text(),
			icons = this.options.icons,
			multipleIcons = icons.primary && icons.secondary,
			buttonClasses = [];

		if ( icons.primary || icons.secondary ) {
			if ( this.options.text ) {
				buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
			}

			if ( icons.primary ) {
				buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
			}

			if ( icons.secondary ) {
				buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
			}

			if ( !this.options.text ) {
				buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );

				if ( !this.hasTitle ) {
					buttonElement.attr( "title", $.trim( buttonText ) );
				}
			}
		} else {
			buttonClasses.push( "ui-button-text-only" );
		}
		buttonElement.addClass( buttonClasses.join( " " ) );
	}
});

$.widget( "ui.buttonset", {
	version: "1.9.0",
	options: {
		items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(button)"
	},

	_create: function() {
		this.element.addClass( "ui-buttonset" );
	},

	_init: function() {
		this.refresh();
	},

	_setOption: function( key, value ) {
		if ( key === "disabled" ) {
			this.buttons.button( "option", key, value );
		}

		this._super( key, value );
	},

	refresh: function() {
		var rtl = this.element.css( "direction" ) === "rtl";

		this.buttons = this.element.find( this.options.items )
			.filter( ":ui-button" )
				.button( "refresh" )
			.end()
			.not( ":ui-button" )
				.button()
			.end()
			.map(function() {
				return $( this ).button( "widget" )[ 0 ];
			})
				.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
				.filter( ":first" )
					.addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
				.end()
				.filter( ":last" )
					.addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
				.end()
			.end();
	},

	_destroy: function() {
		this.element.removeClass( "ui-buttonset" );
		this.buttons
			.map(function() {
				return $( this ).button( "widget" )[ 0 ];
			})
				.removeClass( "ui-corner-left ui-corner-right" )
			.end()
			.button( "destroy" );
	}
});

}( jQuery ) );
(function( $, undefined ) {

$.extend($.ui, { datepicker: { version: "1.9.0" } });

var PROP_NAME = 'datepicker';
var dpuuid = new Date().getTime();
var instActive;

/* Date picker manager.
   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
   Settings for (groups of) date pickers are maintained in an instance object,
   allowing multiple different settings on the same page. */

function Datepicker() {
	this.debug = false; // Change this to true to start debugging
	this._curInst = null; // The current instance in use
	this._keyEvent = false; // If the last event was a key event
	this._disabledInputs = []; // List of date picker inputs that have been disabled
	this._datepickerShowing = false; // True if the popup picker is showing , false if not
	this._inDialog = false; // True if showing within a "dialog", false if not
	this._mainDivId = 'ui-datepicker-div'; // The ID of the main datepicker division
	this._inlineClass = 'ui-datepicker-inline'; // The name of the inline marker class
	this._appendClass = 'ui-datepicker-append'; // The name of the append marker class
	this._triggerClass = 'ui-datepicker-trigger'; // The name of the trigger marker class
	this._dialogClass = 'ui-datepicker-dialog'; // The name of the dialog marker class
	this._disableClass = 'ui-datepicker-disabled'; // The name of the disabled covering marker class
	this._unselectableClass = 'ui-datepicker-unselectable'; // The name of the unselectable cell marker class
	this._currentClass = 'ui-datepicker-current-day'; // The name of the current day marker class
	this._dayOverClass = 'ui-datepicker-days-cell-over'; // The name of the day hover marker class
	this.regional = []; // Available regional settings, indexed by language code
	this.regional[''] = { // Default regional settings
		closeText: 'Done', // Display text for close link
		prevText: 'Prev', // Display text for previous month link
		nextText: 'Next', // Display text for next month link
		currentText: 'Today', // Display text for current month link
		monthNames: ['January','February','March','April','May','June',
			'July','August','September','October','November','December'], // Names of months for drop-down and formatting
		monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting
		dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting
		dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting
		dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Column headings for days starting at Sunday
		weekHeader: 'Wk', // Column header for week of the year
		dateFormat: 'mm/dd/yy', // See format options on parseDate
		firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
		isRTL: false, // True if right-to-left language, false if left-to-right
		showMonthAfterYear: false, // True if the year select precedes month, false for month then year
		yearSuffix: '' // Additional text to append to the year in the month headers
	};
	this._defaults = { // Global defaults for all the date picker instances
		showOn: 'focus', // 'focus' for popup on focus,
			// 'button' for trigger button, or 'both' for either
		showAnim: 'fadeIn', // Name of jQuery animation for popup
		showOptions: {}, // Options for enhanced animations
		defaultDate: null, // Used when field is blank: actual date,
			// +/-number for offset from today, null for today
		appendText: '', // Display text following the input box, e.g. showing the format
		buttonText: '...', // Text for trigger button
		buttonImage: '', // URL for trigger button image
		buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
		hideIfNoPrevNext: false, // True to hide next/previous month links
			// if not applicable, false to just disable them
		navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
		gotoCurrent: false, // True if today link goes back to current selection instead
		changeMonth: false, // True if month can be selected directly, false if only prev/next
		changeYear: false, // True if year can be selected directly, false if only prev/next
		yearRange: 'c-10:c+10', // Range of years to display in drop-down,
			// either relative to today's year (-nn:+nn), relative to currently displayed year
			// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
		showOtherMonths: false, // True to show dates in other months, false to leave blank
		selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
		showWeek: false, // True to show week of the year, false to not show it
		calculateWeek: this.iso8601Week, // How to calculate the week of the year,
			// takes a Date and returns the number of the week for it
		shortYearCutoff: '+10', // Short year values < this are in the current century,
			// > this are in the previous century,
			// string value starting with '+' for current year + value
		minDate: null, // The earliest selectable date, or null for no limit
		maxDate: null, // The latest selectable date, or null for no limit
		duration: 'fast', // Duration of display/closure
		beforeShowDay: null, // Function that takes a date and returns an array with
			// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or '',
			// [2] = cell title (optional), e.g. $.datepicker.noWeekends
		beforeShow: null, // Function that takes an input field and
			// returns a set of custom settings for the date picker
		onSelect: null, // Define a callback function when a date is selected
		onChangeMonthYear: null, // Define a callback function when the month or year is changed
		onClose: null, // Define a callback function when the datepicker is closed
		numberOfMonths: 1, // Number of months to show at a time
		showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
		stepMonths: 1, // Number of months to step back/forward
		stepBigMonths: 12, // Number of months to step back/forward for the big links
		altField: '', // Selector for an alternate field to store selected dates into
		altFormat: '', // The date format to use for the alternate field
		constrainInput: true, // The input is constrained by the current date format
		showButtonPanel: false, // True to show button panel, false to not show it
		autoSize: false, // True to size the input for the date format, false to leave as is
		disabled: false // The initial disabled state
	};
	$.extend(this._defaults, this.regional['']);
	this.dpDiv = bindHover($('<div id="' + this._mainDivId + '" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'));
}

$.extend(Datepicker.prototype, {
	/* Class name added to elements to indicate already configured with a date picker. */
	markerClassName: 'hasDatepicker',
	
	//Keep track of the maximum number of rows displayed (see #7043)
	maxRows: 4,

	/* Debug logging (if enabled). */
	log: function () {
		if (this.debug)
			console.log.apply('', arguments);
	},
	
	// TODO rename to "widget" when switching to widget factory
	_widgetDatepicker: function() {
		return this.dpDiv;
	},

	/* Override the default settings for all instances of the date picker.
	   @param  settings  object - the new settings to use as defaults (anonymous object)
	   @return the manager object */
	setDefaults: function(settings) {
		extendRemove(this._defaults, settings || {});
		return this;
	},

	/* Attach the date picker to a jQuery selection.
	   @param  target    element - the target input field or division or span
	   @param  settings  object - the new settings to use for this date picker instance (anonymous) */
	_attachDatepicker: function(target, settings) {
		// check for settings on the control itself - in namespace 'date:'
		var inlineSettings = null;
		for (var attrName in this._defaults) {
			var attrValue = target.getAttribute('date:' + attrName);
			if (attrValue) {
				inlineSettings = inlineSettings || {};
				try {
					inlineSettings[attrName] = eval(attrValue);
				} catch (err) {
					inlineSettings[attrName] = attrValue;
				}
			}
		}
		var nodeName = target.nodeName.toLowerCase();
		var inline = (nodeName == 'div' || nodeName == 'span');
		if (!target.id) {
			this.uuid += 1;
			target.id = 'dp' + this.uuid;
		}
		var inst = this._newInst($(target), inline);
		inst.settings = $.extend({}, settings || {}, inlineSettings || {});
		if (nodeName == 'input') {
			this._connectDatepicker(target, inst);
		} else if (inline) {
			this._inlineDatepicker(target, inst);
		}
	},

	/* Create a new instance object. */
	_newInst: function(target, inline) {
		var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars
		return {id: id, input: target, // associated target
			selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
			drawMonth: 0, drawYear: 0, // month being drawn
			inline: inline, // is datepicker inline or not
			dpDiv: (!inline ? this.dpDiv : // presentation div
			bindHover($('<div class="' + this._inlineClass + ' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')))};
	},

	/* Attach the date picker to an input field. */
	_connectDatepicker: function(target, inst) {
		var input = $(target);
		inst.append = $([]);
		inst.trigger = $([]);
		if (input.hasClass(this.markerClassName))
			return;
		this._attachments(input, inst);
		input.addClass(this.markerClassName).keydown(this._doKeyDown).
			keypress(this._doKeyPress).keyup(this._doKeyUp).
			bind("setData.datepicker", function(event, key, value) {
				inst.settings[key] = value;
			}).bind("getData.datepicker", function(event, key) {
				return this._get(inst, key);
			});
		this._autoSize(inst);
		$.data(target, PROP_NAME, inst);
		//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
		if( inst.settings.disabled ) {
			this._disableDatepicker( target );
		}
	},

	/* Make attachments based on settings. */
	_attachments: function(input, inst) {
		var appendText = this._get(inst, 'appendText');
		var isRTL = this._get(inst, 'isRTL');
		if (inst.append)
			inst.append.remove();
		if (appendText) {
			inst.append = $('<span class="' + this._appendClass + '">' + appendText + '</span>');
			input[isRTL ? 'before' : 'after'](inst.append);
		}
		input.unbind('focus', this._showDatepicker);
		if (inst.trigger)
			inst.trigger.remove();
		var showOn = this._get(inst, 'showOn');
		if (showOn == 'focus' || showOn == 'both') // pop-up date picker when in the marked field
			input.focus(this._showDatepicker);
		if (showOn == 'button' || showOn == 'both') { // pop-up date picker when button clicked
			var buttonText = this._get(inst, 'buttonText');
			var buttonImage = this._get(inst, 'buttonImage');
			inst.trigger = $(this._get(inst, 'buttonImageOnly') ?
				$('<img/>').addClass(this._triggerClass).
					attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
				$('<button type="button"></button>').addClass(this._triggerClass).
					html(buttonImage == '' ? buttonText : $('<img/>').attr(
					{ src:buttonImage, alt:buttonText, title:buttonText })));
			input[isRTL ? 'before' : 'after'](inst.trigger);
			inst.trigger.click(function() {
				if ($.datepicker._datepickerShowing && $.datepicker._lastInput == input[0])
					$.datepicker._hideDatepicker();
				else if ($.datepicker._datepickerShowing && $.datepicker._lastInput != input[0]) {
					$.datepicker._hideDatepicker(); 
					$.datepicker._showDatepicker(input[0]);
				} else
					$.datepicker._showDatepicker(input[0]);
				return false;
			});
		}
	},

	/* Apply the maximum length for the date format. */
	_autoSize: function(inst) {
		if (this._get(inst, 'autoSize') && !inst.inline) {
			var date = new Date(2009, 12 - 1, 20); // Ensure double digits
			var dateFormat = this._get(inst, 'dateFormat');
			if (dateFormat.match(/[DM]/)) {
				var findMax = function(names) {
					var max = 0;
					var maxI = 0;
					for (var i = 0; i < names.length; i++) {
						if (names[i].length > max) {
							max = names[i].length;
							maxI = i;
						}
					}
					return maxI;
				};
				date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
					'monthNames' : 'monthNamesShort'))));
				date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
					'dayNames' : 'dayNamesShort'))) + 20 - date.getDay());
			}
			inst.input.attr('size', this._formatDate(inst, date).length);
		}
	},

	/* Attach an inline date picker to a div. */
	_inlineDatepicker: function(target, inst) {
		var divSpan = $(target);
		if (divSpan.hasClass(this.markerClassName))
			return;
		divSpan.addClass(this.markerClassName).append(inst.dpDiv).
			bind("setData.datepicker", function(event, key, value){
				inst.settings[key] = value;
			}).bind("getData.datepicker", function(event, key){
				return this._get(inst, key);
			});
		$.data(target, PROP_NAME, inst);
		this._setDate(inst, this._getDefaultDate(inst), true);
		this._updateDatepicker(inst);
		this._updateAlternate(inst);
		//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
		if( inst.settings.disabled ) {
			this._disableDatepicker( target );
		}
		// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
		// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
		inst.dpDiv.css( "display", "block" );
	},

	/* Pop-up the date picker in a "dialog" box.
	   @param  input     element - ignored
	   @param  date      string or Date - the initial date to display
	   @param  onSelect  function - the function to call when a date is selected
	   @param  settings  object - update the dialog date picker instance's settings (anonymous object)
	   @param  pos       int[2] - coordinates for the dialog's position within the screen or
	                     event - with x/y coordinates or
	                     leave empty for default (screen centre)
	   @return the manager object */
	_dialogDatepicker: function(input, date, onSelect, settings, pos) {
		var inst = this._dialogInst; // internal instance
		if (!inst) {
			this.uuid += 1;
			var id = 'dp' + this.uuid;
			this._dialogInput = $('<input type="text" id="' + id +
				'" style="position: absolute; top: -100px; width: 0px;"/>');
			this._dialogInput.keydown(this._doKeyDown);
			$('body').append(this._dialogInput);
			inst = this._dialogInst = this._newInst(this._dialogInput, false);
			inst.settings = {};
			$.data(this._dialogInput[0], PROP_NAME, inst);
		}
		extendRemove(inst.settings, settings || {});
		date = (date && date.constructor == Date ? this._formatDate(inst, date) : date);
		this._dialogInput.val(date);

		this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
		if (!this._pos) {
			var browserWidth = document.documentElement.clientWidth;
			var browserHeight = document.documentElement.clientHeight;
			var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
			var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
			this._pos = // should use actual width/height below
				[(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
		}

		// move input on screen for focus, but hidden behind dialog
		this._dialogInput.css('left', (this._pos[0] + 20) + 'px').css('top', this._pos[1] + 'px');
		inst.settings.onSelect = onSelect;
		this._inDialog = true;
		this.dpDiv.addClass(this._dialogClass);
		this._showDatepicker(this._dialogInput[0]);
		if ($.blockUI)
			$.blockUI(this.dpDiv);
		$.data(this._dialogInput[0], PROP_NAME, inst);
		return this;
	},

	/* Detach a datepicker from its control.
	   @param  target    element - the target input field or division or span */
	_destroyDatepicker: function(target) {
		var $target = $(target);
		var inst = $.data(target, PROP_NAME);
		if (!$target.hasClass(this.markerClassName)) {
			return;
		}
		var nodeName = target.nodeName.toLowerCase();
		$.removeData(target, PROP_NAME);
		if (nodeName == 'input') {
			inst.append.remove();
			inst.trigger.remove();
			$target.removeClass(this.markerClassName).
				unbind('focus', this._showDatepicker).
				unbind('keydown', this._doKeyDown).
				unbind('keypress', this._doKeyPress).
				unbind('keyup', this._doKeyUp);
		} else if (nodeName == 'div' || nodeName == 'span')
			$target.removeClass(this.markerClassName).empty();
	},

	/* Enable the date picker to a jQuery selection.
	   @param  target    element - the target input field or division or span */
	_enableDatepicker: function(target) {
		var $target = $(target);
		var inst = $.data(target, PROP_NAME);
		if (!$target.hasClass(this.markerClassName)) {
			return;
		}
		var nodeName = target.nodeName.toLowerCase();
		if (nodeName == 'input') {
			target.disabled = false;
			inst.trigger.filter('button').
				each(function() { this.disabled = false; }).end().
				filter('img').css({opacity: '1.0', cursor: ''});
		}
		else if (nodeName == 'div' || nodeName == 'span') {
			var inline = $target.children('.' + this._inlineClass);
			inline.children().removeClass('ui-state-disabled');
			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
				prop("disabled", false);
		}
		this._disabledInputs = $.map(this._disabledInputs,
			function(value) { return (value == target ? null : value); }); // delete entry
	},

	/* Disable the date picker to a jQuery selection.
	   @param  target    element - the target input field or division or span */
	_disableDatepicker: function(target) {
		var $target = $(target);
		var inst = $.data(target, PROP_NAME);
		if (!$target.hasClass(this.markerClassName)) {
			return;
		}
		var nodeName = target.nodeName.toLowerCase();
		if (nodeName == 'input') {
			target.disabled = true;
			inst.trigger.filter('button').
				each(function() { this.disabled = true; }).end().
				filter('img').css({opacity: '0.5', cursor: 'default'});
		}
		else if (nodeName == 'div' || nodeName == 'span') {
			var inline = $target.children('.' + this._inlineClass);
			inline.children().addClass('ui-state-disabled');
			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
				prop("disabled", true);
		}
		this._disabledInputs = $.map(this._disabledInputs,
			function(value) { return (value == target ? null : value); }); // delete entry
		this._disabledInputs[this._disabledInputs.length] = target;
	},

	/* Is the first field in a jQuery collection disabled as a datepicker?
	   @param  target    element - the target input field or division or span
	   @return boolean - true if disabled, false if enabled */
	_isDisabledDatepicker: function(target) {
		if (!target) {
			return false;
		}
		for (var i = 0; i < this._disabledInputs.length; i++) {
			if (this._disabledInputs[i] == target)
				return true;
		}
		return false;
	},

	/* Retrieve the instance data for the target control.
	   @param  target  element - the target input field or division or span
	   @return  object - the associated instance data
	   @throws  error if a jQuery problem getting data */
	_getInst: function(target) {
		try {
			return $.data(target, PROP_NAME);
		}
		catch (err) {
			throw 'Missing instance data for this datepicker';
		}
	},

	/* Update or retrieve the settings for a date picker attached to an input field or division.
	   @param  target  element - the target input field or division or span
	   @param  name    object - the new settings to update or
	                   string - the name of the setting to change or retrieve,
	                   when retrieving also 'all' for all instance settings or
	                   'defaults' for all global defaults
	   @param  value   any - the new value for the setting
	                   (omit if above is an object or to retrieve a value) */
	_optionDatepicker: function(target, name, value) {
		var inst = this._getInst(target);
		if (arguments.length == 2 && typeof name == 'string') {
			return (name == 'defaults' ? $.extend({}, $.datepicker._defaults) :
				(inst ? (name == 'all' ? $.extend({}, inst.settings) :
				this._get(inst, name)) : null));
		}
		var settings = name || {};
		if (typeof name == 'string') {
			settings = {};
			settings[name] = value;
		}
		if (inst) {
			if (this._curInst == inst) {
				this._hideDatepicker();
			}
			var date = this._getDateDatepicker(target, true);
			var minDate = this._getMinMaxDate(inst, 'min');
			var maxDate = this._getMinMaxDate(inst, 'max');
			extendRemove(inst.settings, settings);
			// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
			if (minDate !== null && settings['dateFormat'] !== undefined && settings['minDate'] === undefined)
				inst.settings.minDate = this._formatDate(inst, minDate);
			if (maxDate !== null && settings['dateFormat'] !== undefined && settings['maxDate'] === undefined)
				inst.settings.maxDate = this._formatDate(inst, maxDate);
			this._attachments($(target), inst);
			this._autoSize(inst);
			this._setDate(inst, date);
			this._updateAlternate(inst);
			this._updateDatepicker(inst);
		}
	},

	// change method deprecated
	_changeDatepicker: function(target, name, value) {
		this._optionDatepicker(target, name, value);
	},

	/* Redraw the date picker attached to an input field or division.
	   @param  target  element - the target input field or division or span */
	_refreshDatepicker: function(target) {
		var inst = this._getInst(target);
		if (inst) {
			this._updateDatepicker(inst);
		}
	},

	/* Set the dates for a jQuery selection.
	   @param  target   element - the target input field or division or span
	   @param  date     Date - the new date */
	_setDateDatepicker: function(target, date) {
		var inst = this._getInst(target);
		if (inst) {
			this._setDate(inst, date);
			this._updateDatepicker(inst);
			this._updateAlternate(inst);
		}
	},

	/* Get the date(s) for the first entry in a jQuery selection.
	   @param  target     element - the target input field or division or span
	   @param  noDefault  boolean - true if no default date is to be used
	   @return Date - the current date */
	_getDateDatepicker: function(target, noDefault) {
		var inst = this._getInst(target);
		if (inst && !inst.inline)
			this._setDateFromField(inst, noDefault);
		return (inst ? this._getDate(inst) : null);
	},

	/* Handle keystrokes. */
	_doKeyDown: function(event) {
		var inst = $.datepicker._getInst(event.target);
		var handled = true;
		var isRTL = inst.dpDiv.is('.ui-datepicker-rtl');
		inst._keyEvent = true;
		if ($.datepicker._datepickerShowing)
			switch (event.keyCode) {
				case 9: $.datepicker._hideDatepicker();
						handled = false;
						break; // hide on tab out
				case 13: var sel = $('td.' + $.datepicker._dayOverClass + ':not(.' + 
									$.datepicker._currentClass + ')', inst.dpDiv);
						if (sel[0])
							$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
							var onSelect = $.datepicker._get(inst, 'onSelect');
							if (onSelect) {
								var dateStr = $.datepicker._formatDate(inst);

								// trigger custom callback
								onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
							}
						else
							$.datepicker._hideDatepicker();
						return false; // don't submit the form
						break; // select the value on enter
				case 27: $.datepicker._hideDatepicker();
						break; // hide on escape
				case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
							-$.datepicker._get(inst, 'stepBigMonths') :
							-$.datepicker._get(inst, 'stepMonths')), 'M');
						break; // previous month/year on page up/+ ctrl
				case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
							+$.datepicker._get(inst, 'stepBigMonths') :
							+$.datepicker._get(inst, 'stepMonths')), 'M');
						break; // next month/year on page down/+ ctrl
				case 35: if (event.ctrlKey || event.metaKey) $.datepicker._clearDate(event.target);
						handled = event.ctrlKey || event.metaKey;
						break; // clear on ctrl or command +end
				case 36: if (event.ctrlKey || event.metaKey) $.datepicker._gotoToday(event.target);
						handled = event.ctrlKey || event.metaKey;
						break; // current on ctrl or command +home
				case 37: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), 'D');
						handled = event.ctrlKey || event.metaKey;
						// -1 day on ctrl or command +left
						if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
									-$.datepicker._get(inst, 'stepBigMonths') :
									-$.datepicker._get(inst, 'stepMonths')), 'M');
						// next month/year on alt +left on Mac
						break;
				case 38: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, -7, 'D');
						handled = event.ctrlKey || event.metaKey;
						break; // -1 week on ctrl or command +up
				case 39: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), 'D');
						handled = event.ctrlKey || event.metaKey;
						// +1 day on ctrl or command +right
						if (event.originalEvent.altKey) $.datepicker._adjustDate(event.target, (event.ctrlKey ?
									+$.datepicker._get(inst, 'stepBigMonths') :
									+$.datepicker._get(inst, 'stepMonths')), 'M');
						// next month/year on alt +right
						break;
				case 40: if (event.ctrlKey || event.metaKey) $.datepicker._adjustDate(event.target, +7, 'D');
						handled = event.ctrlKey || event.metaKey;
						break; // +1 week on ctrl or command +down
				default: handled = false;
			}
		else if (event.keyCode == 36 && event.ctrlKey) // display the date picker on ctrl+home
			$.datepicker._showDatepicker(this);
		else {
			handled = false;
		}
		if (handled) {
			event.preventDefault();
			event.stopPropagation();
		}
	},

	/* Filter entered characters - based on date format. */
	_doKeyPress: function(event) {
		var inst = $.datepicker._getInst(event.target);
		if ($.datepicker._get(inst, 'constrainInput')) {
			var chars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat'));
			var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode);
			return event.ctrlKey || event.metaKey || (chr < ' ' || !chars || chars.indexOf(chr) > -1);
		}
	},

	/* Synchronise manual entry and field/alternate field. */
	_doKeyUp: function(event) {
		var inst = $.datepicker._getInst(event.target);
		if (inst.input.val() != inst.lastVal) {
			try {
				var date = $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
					(inst.input ? inst.input.val() : null),
					$.datepicker._getFormatConfig(inst));
				if (date) { // only if valid
					$.datepicker._setDateFromField(inst);
					$.datepicker._updateAlternate(inst);
					$.datepicker._updateDatepicker(inst);
				}
			}
			catch (err) {
				$.datepicker.log(err);
			}
		}
		return true;
	},

	/* Pop-up the date picker for a given input field.
	   If false returned from beforeShow event handler do not show. 
	   @param  input  element - the input field attached to the date picker or
	                  event - if triggered by focus */
	_showDatepicker: function(input) {
		input = input.target || input;
		if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger
			input = $('input', input.parentNode)[0];
		if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput == input) // already here
			return;
		var inst = $.datepicker._getInst(input);
		if ($.datepicker._curInst && $.datepicker._curInst != inst) {
			$.datepicker._curInst.dpDiv.stop(true, true);
			if ( inst && $.datepicker._datepickerShowing ) {
				$.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
			}
		}
		var beforeShow = $.datepicker._get(inst, 'beforeShow');
		var beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
		if(beforeShowSettings === false){
			//false
			return;
		}
		extendRemove(inst.settings, beforeShowSettings);
		inst.lastVal = null;
		$.datepicker._lastInput = input;
		$.datepicker._setDateFromField(inst);
		if ($.datepicker._inDialog) // hide cursor
			input.value = '';
		if (!$.datepicker._pos) { // position below input
			$.datepicker._pos = $.datepicker._findPos(input);
			$.datepicker._pos[1] += input.offsetHeight; // add the height
		}
		var isFixed = false;
		$(input).parents().each(function() {
			isFixed |= $(this).css('position') == 'fixed';
			return !isFixed;
		});
		var offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
		$.datepicker._pos = null;
		//to avoid flashes on Firefox
		inst.dpDiv.empty();
		// determine sizing offscreen
		inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'});
		$.datepicker._updateDatepicker(inst);
		// fix width for dynamic number of date pickers
		// and adjust position before showing
		offset = $.datepicker._checkOffset(inst, offset, isFixed);
		inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
			'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
			left: offset.left + 'px', top: offset.top + 'px'});
		if (!inst.inline) {
			var showAnim = $.datepicker._get(inst, 'showAnim');
			var duration = $.datepicker._get(inst, 'duration');
			var postProcess = function() {
				var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
				if( !! cover.length ){
					var borders = $.datepicker._getBorders(inst.dpDiv);
					cover.css({left: -borders[0], top: -borders[1],
						width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()});
				}
			};
			inst.dpDiv.zIndex($(input).zIndex()+1);
			$.datepicker._datepickerShowing = true;

			// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
			if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) )
				inst.dpDiv.show(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
			else
				inst.dpDiv[showAnim || 'show']((showAnim ? duration : null), postProcess);
			if (!showAnim || !duration)
				postProcess();
			if (inst.input.is(':visible') && !inst.input.is(':disabled'))
				inst.input.focus();
			$.datepicker._curInst = inst;
		}
	},

	/* Generate the date picker content. */
	_updateDatepicker: function(inst) {
		this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
		var borders = $.datepicker._getBorders(inst.dpDiv);
		instActive = inst; // for delegate hover events
		inst.dpDiv.empty().append(this._generateHTML(inst));
		this._attachHandlers(inst);
		var cover = inst.dpDiv.find('iframe.ui-datepicker-cover'); // IE6- only
		if( !!cover.length ){ //avoid call to outerXXXX() when not in IE6
			cover.css({left: -borders[0], top: -borders[1], width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()})
		}
		inst.dpDiv.find('.' + this._dayOverClass + ' a').mouseover();
		var numMonths = this._getNumberOfMonths(inst);
		var cols = numMonths[1];
		var width = 17;
		inst.dpDiv.removeClass('ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4').width('');
		if (cols > 1)
			inst.dpDiv.addClass('ui-datepicker-multi-' + cols).css('width', (width * cols) + 'em');
		inst.dpDiv[(numMonths[0] != 1 || numMonths[1] != 1 ? 'add' : 'remove') +
			'Class']('ui-datepicker-multi');
		inst.dpDiv[(this._get(inst, 'isRTL') ? 'add' : 'remove') +
			'Class']('ui-datepicker-rtl');
		if (inst == $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input &&
				// #6694 - don't focus the input if it's already focused
				// this breaks the change event in IE
				inst.input.is(':visible') && !inst.input.is(':disabled') && inst.input[0] != document.activeElement)
			inst.input.focus();
		// deffered render of the years select (to avoid flashes on Firefox) 
		if( inst.yearshtml ){
			var origyearshtml = inst.yearshtml;
			setTimeout(function(){
				//assure that inst.yearshtml didn't change.
				if( origyearshtml === inst.yearshtml && inst.yearshtml ){
					inst.dpDiv.find('select.ui-datepicker-year:first').replaceWith(inst.yearshtml);
				}
				origyearshtml = inst.yearshtml = null;
			}, 0);
		}
	},

	/* Retrieve the size of left and top borders for an element.
	   @param  elem  (jQuery object) the element of interest
	   @return  (number[2]) the left and top borders */
	_getBorders: function(elem) {
		var convert = function(value) {
			return {thin: 1, medium: 2, thick: 3}[value] || value;
		};
		return [parseFloat(convert(elem.css('border-left-width'))),
			parseFloat(convert(elem.css('border-top-width')))];
	},

	/* Check positioning to remain on screen. */
	_checkOffset: function(inst, offset, isFixed) {
		var dpWidth = inst.dpDiv.outerWidth();
		var dpHeight = inst.dpDiv.outerHeight();
		var inputWidth = inst.input ? inst.input.outerWidth() : 0;
		var inputHeight = inst.input ? inst.input.outerHeight() : 0;
		var viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft());
		var viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());

		offset.left -= (this._get(inst, 'isRTL') ? (dpWidth - inputWidth) : 0);
		offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0;
		offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;

		// now check if datepicker is showing outside window viewport - move to a better place if so.
		offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
			Math.abs(offset.left + dpWidth - viewWidth) : 0);
		offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
			Math.abs(dpHeight + inputHeight) : 0);

		return offset;
	},

	/* Find an object's position on the screen. */
	_findPos: function(obj) {
		var inst = this._getInst(obj);
		var isRTL = this._get(inst, 'isRTL');
		while (obj && (obj.type == 'hidden' || obj.nodeType != 1 || $.expr.filters.hidden(obj))) {
			obj = obj[isRTL ? 'previousSibling' : 'nextSibling'];
		}
		var position = $(obj).offset();
		return [position.left, position.top];
	},

	/* Hide the date picker from view.
	   @param  input  element - the input field attached to the date picker */
	_hideDatepicker: function(input) {
		var inst = this._curInst;
		if (!inst || (input && inst != $.data(input, PROP_NAME)))
			return;
		if (this._datepickerShowing) {
			var showAnim = this._get(inst, 'showAnim');
			var duration = this._get(inst, 'duration');
			var postProcess = function() {
				$.datepicker._tidyDialog(inst);
			};

			// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
			if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) )
				inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess);
			else
				inst.dpDiv[(showAnim == 'slideDown' ? 'slideUp' :
					(showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
			if (!showAnim)
				postProcess();
			this._datepickerShowing = false;
			var onClose = this._get(inst, 'onClose');
			if (onClose)
				onClose.apply((inst.input ? inst.input[0] : null),
					[(inst.input ? inst.input.val() : ''), inst]);
			this._lastInput = null;
			if (this._inDialog) {
				this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' });
				if ($.blockUI) {
					$.unblockUI();
					$('body').append(this.dpDiv);
				}
			}
			this._inDialog = false;
		}
	},

	/* Tidy up after a dialog display. */
	_tidyDialog: function(inst) {
		inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-datepicker-calendar');
	},

	/* Close date picker if clicked elsewhere. */
	_checkExternalClick: function(event) {
		if (!$.datepicker._curInst)
			return;

		var $target = $(event.target),
			inst = $.datepicker._getInst($target[0]);

		if ( ( ( $target[0].id != $.datepicker._mainDivId &&
				$target.parents('#' + $.datepicker._mainDivId).length == 0 &&
				!$target.hasClass($.datepicker.markerClassName) &&
				!$target.closest("." + $.datepicker._triggerClass).length &&
				$.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
			( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst != inst ) )
			$.datepicker._hideDatepicker();
	},

	/* Adjust one of the date sub-fields. */
	_adjustDate: function(id, offset, period) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		if (this._isDisabledDatepicker(target[0])) {
			return;
		}
		this._adjustInstDate(inst, offset +
			(period == 'M' ? this._get(inst, 'showCurrentAtPos') : 0), // undo positioning
			period);
		this._updateDatepicker(inst);
	},

	/* Action for current link. */
	_gotoToday: function(id) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		if (this._get(inst, 'gotoCurrent') && inst.currentDay) {
			inst.selectedDay = inst.currentDay;
			inst.drawMonth = inst.selectedMonth = inst.currentMonth;
			inst.drawYear = inst.selectedYear = inst.currentYear;
		}
		else {
			var date = new Date();
			inst.selectedDay = date.getDate();
			inst.drawMonth = inst.selectedMonth = date.getMonth();
			inst.drawYear = inst.selectedYear = date.getFullYear();
		}
		this._notifyChange(inst);
		this._adjustDate(target);
	},

	/* Action for selecting a new month/year. */
	_selectMonthYear: function(id, select, period) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		inst['selected' + (period == 'M' ? 'Month' : 'Year')] =
		inst['draw' + (period == 'M' ? 'Month' : 'Year')] =
			parseInt(select.options[select.selectedIndex].value,10);
		this._notifyChange(inst);
		this._adjustDate(target);
	},

	/* Action for selecting a day. */
	_selectDay: function(id, month, year, td) {
		var target = $(id);
		if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
			return;
		}
		var inst = this._getInst(target[0]);
		inst.selectedDay = inst.currentDay = $('a', td).html();
		inst.selectedMonth = inst.currentMonth = month;
		inst.selectedYear = inst.currentYear = year;
		this._selectDate(id, this._formatDate(inst,
			inst.currentDay, inst.currentMonth, inst.currentYear));
	},

	/* Erase the input field and hide the date picker. */
	_clearDate: function(id) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		this._selectDate(target, '');
	},

	/* Update the input field with the selected date. */
	_selectDate: function(id, dateStr) {
		var target = $(id);
		var inst = this._getInst(target[0]);
		dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
		if (inst.input)
			inst.input.val(dateStr);
		this._updateAlternate(inst);
		var onSelect = this._get(inst, 'onSelect');
		if (onSelect)
			onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);  // trigger custom callback
		else if (inst.input)
			inst.input.trigger('change'); // fire the change event
		if (inst.inline)
			this._updateDatepicker(inst);
		else {
			this._hideDatepicker();
			this._lastInput = inst.input[0];
			if (typeof(inst.input[0]) != 'object')
				inst.input.focus(); // restore focus
			this._lastInput = null;
		}
	},

	/* Update any alternate field to synchronise with the main field. */
	_updateAlternate: function(inst) {
		var altField = this._get(inst, 'altField');
		if (altField) { // update alternate field too
			var altFormat = this._get(inst, 'altFormat') || this._get(inst, 'dateFormat');
			var date = this._getDate(inst);
			var dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
			$(altField).each(function() { $(this).val(dateStr); });
		}
	},

	/* Set as beforeShowDay function to prevent selection of weekends.
	   @param  date  Date - the date to customise
	   @return [boolean, string] - is this date selectable?, what is its CSS class? */
	noWeekends: function(date) {
		var day = date.getDay();
		return [(day > 0 && day < 6), ''];
	},

	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
	   @param  date  Date - the date to get the week for
	   @return  number - the number of the week within the year that contains this date */
	iso8601Week: function(date) {
		var checkDate = new Date(date.getTime());
		// Find Thursday of this week starting on Monday
		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
		var time = checkDate.getTime();
		checkDate.setMonth(0); // Compare with Jan 1
		checkDate.setDate(1);
		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
	},

	/* Parse a string value into a date object.
	   See formatDate below for the possible formats.

	   @param  format    string - the expected format of the date
	   @param  value     string - the date in the above format
	   @param  settings  Object - attributes include:
	                     shortYearCutoff  number - the cutoff year for determining the century (optional)
	                     dayNamesShort    string[7] - abbreviated names of the days from Sunday (optional)
	                     dayNames         string[7] - names of the days from Sunday (optional)
	                     monthNamesShort  string[12] - abbreviated names of the months (optional)
	                     monthNames       string[12] - names of the months (optional)
	   @return  Date - the extracted date value or null if value is blank */
	parseDate: function (format, value, settings) {
		if (format == null || value == null)
			throw 'Invalid arguments';
		value = (typeof value == 'object' ? value.toString() : value + '');
		if (value == '')
			return null;
		var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff;
		shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
				new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
		var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
		var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
		var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
		var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
		var year = -1;
		var month = -1;
		var day = -1;
		var doy = -1;
		var literal = false;
		// Check whether a format character is doubled
		var lookAhead = function(match) {
			var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
			if (matches)
				iFormat++;
			return matches;
		};
		// Extract a number from the string value
		var getNumber = function(match) {
			var isDoubled = lookAhead(match);
			var size = (match == '@' ? 14 : (match == '!' ? 20 :
				(match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2))));
			var digits = new RegExp('^\\d{1,' + size + '}');
			var num = value.substring(iValue).match(digits);
			if (!num)
				throw 'Missing number at position ' + iValue;
			iValue += num[0].length;
			return parseInt(num[0], 10);
		};
		// Extract a name from the string value and convert to an index
		var getName = function(match, shortNames, longNames) {
			var names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
				return [ [k, v] ];
			}).sort(function (a, b) {
				return -(a[1].length - b[1].length);
			});
			var index = -1;
			$.each(names, function (i, pair) {
				var name = pair[1];
				if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) {
					index = pair[0];
					iValue += name.length;
					return false;
				}
			});
			if (index != -1)
				return index + 1;
			else
				throw 'Unknown name at position ' + iValue;
		};
		// Confirm that a literal character matches the string value
		var checkLiteral = function() {
			if (value.charAt(iValue) != format.charAt(iFormat))
				throw 'Unexpected literal at position ' + iValue;
			iValue++;
		};
		var iValue = 0;
		for (var iFormat = 0; iFormat < format.length; iFormat++) {
			if (literal)
				if (format.charAt(iFormat) == "'" && !lookAhead("'"))
					literal = false;
				else
					checkLiteral();
			else
				switch (format.charAt(iFormat)) {
					case 'd':
						day = getNumber('d');
						break;
					case 'D':
						getName('D', dayNamesShort, dayNames);
						break;
					case 'o':
						doy = getNumber('o');
						break;
					case 'm':
						month = getNumber('m');
						break;
					case 'M':
						month = getName('M', monthNamesShort, monthNames);
						break;
					case 'y':
						year = getNumber('y');
						break;
					case '@':
						var date = new Date(getNumber('@'));
						year = date.getFullYear();
						month = date.getMonth() + 1;
						day = date.getDate();
						break;
					case '!':
						var date = new Date((getNumber('!') - this._ticksTo1970) / 10000);
						year = date.getFullYear();
						month = date.getMonth() + 1;
						day = date.getDate();
						break;
					case "'":
						if (lookAhead("'"))
							checkLiteral();
						else
							literal = true;
						break;
					default:
						checkLiteral();
				}
		}
		if (iValue < value.length){
			var extra = value.substr(iValue);
			if (!/^\s+/.test(extra)) {
				throw "Extra/unparsed characters found in date: " + extra;
			}
		}
		if (year == -1)
			year = new Date().getFullYear();
		else if (year < 100)
			year += new Date().getFullYear() - new Date().getFullYear() % 100 +
				(year <= shortYearCutoff ? 0 : -100);
		if (doy > -1) {
			month = 1;
			day = doy;
			do {
				var dim = this._getDaysInMonth(year, month - 1);
				if (day <= dim)
					break;
				month++;
				day -= dim;
			} while (true);
		}
		var date = this._daylightSavingAdjust(new Date(year, month - 1, day));
		if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day)
			throw 'Invalid date'; // E.g. 31/02/00
		return date;
	},

	/* Standard date formats. */
	ATOM: 'yy-mm-dd', // RFC 3339 (ISO 8601)
	COOKIE: 'D, dd M yy',
	ISO_8601: 'yy-mm-dd',
	RFC_822: 'D, d M y',
	RFC_850: 'DD, dd-M-y',
	RFC_1036: 'D, d M y',
	RFC_1123: 'D, d M yy',
	RFC_2822: 'D, d M yy',
	RSS: 'D, d M y', // RFC 822
	TICKS: '!',
	TIMESTAMP: '@',
	W3C: 'yy-mm-dd', // ISO 8601

	_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
		Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),

	/* Format a date object into a string value.
	   The format can be combinations of the following:
	   d  - day of month (no leading zero)
	   dd - day of month (two digit)
	   o  - day of year (no leading zeros)
	   oo - day of year (three digit)
	   D  - day name short
	   DD - day name long
	   m  - month of year (no leading zero)
	   mm - month of year (two digit)
	   M  - month name short
	   MM - month name long
	   y  - year (two digit)
	   yy - year (four digit)
	   @ - Unix timestamp (ms since 01/01/1970)
	   ! - Windows ticks (100ns since 01/01/0001)
	   '...' - literal text
	   '' - single quote

	   @param  format    string - the desired format of the date
	   @param  date      Date - the date value to format
	   @param  settings  Object - attributes include:
	                     dayNamesShort    string[7] - abbreviated names of the days from Sunday (optional)
	                     dayNames         string[7] - names of the days from Sunday (optional)
	                     monthNamesShort  string[12] - abbreviated names of the months (optional)
	                     monthNames       string[12] - names of the months (optional)
	   @return  string - the date in the above format */
	formatDate: function (format, date, settings) {
		if (!date)
			return '';
		var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
		var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
		var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
		var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
		// Check whether a format character is doubled
		var lookAhead = function(match) {
			var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
			if (matches)
				iFormat++;
			return matches;
		};
		// Format a number, with leading zero if necessary
		var formatNumber = function(match, value, len) {
			var num = '' + value;
			if (lookAhead(match))
				while (num.length < len)
					num = '0' + num;
			return num;
		};
		// Format a name, short or long as requested
		var formatName = function(match, value, shortNames, longNames) {
			return (lookAhead(match) ? longNames[value] : shortNames[value]);
		};
		var output = '';
		var literal = false;
		if (date)
			for (var iFormat = 0; iFormat < format.length; iFormat++) {
				if (literal)
					if (format.charAt(iFormat) == "'" && !lookAhead("'"))
						literal = false;
					else
						output += format.charAt(iFormat);
				else
					switch (format.charAt(iFormat)) {
						case 'd':
							output += formatNumber('d', date.getDate(), 2);
							break;
						case 'D':
							output += formatName('D', date.getDay(), dayNamesShort, dayNames);
							break;
						case 'o':
							output += formatNumber('o',
								Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
							break;
						case 'm':
							output += formatNumber('m', date.getMonth() + 1, 2);
							break;
						case 'M':
							output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
							break;
						case 'y':
							output += (lookAhead('y') ? date.getFullYear() :
								(date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
							break;
						case '@':
							output += date.getTime();
							break;
						case '!':
							output += date.getTime() * 10000 + this._ticksTo1970;
							break;
						case "'":
							if (lookAhead("'"))
								output += "'";
							else
								literal = true;
							break;
						default:
							output += format.charAt(iFormat);
					}
			}
		return output;
	},

	/* Extract all possible characters from the date format. */
	_possibleChars: function (format) {
		var chars = '';
		var literal = false;
		// Check whether a format character is doubled
		var lookAhead = function(match) {
			var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
			if (matches)
				iFormat++;
			return matches;
		};
		for (var iFormat = 0; iFormat < format.length; iFormat++)
			if (literal)
				if (format.charAt(iFormat) == "'" && !lookAhead("'"))
					literal = false;
				else
					chars += format.charAt(iFormat);
			else
				switch (format.charAt(iFormat)) {
					case 'd': case 'm': case 'y': case '@':
						chars += '0123456789';
						break;
					case 'D': case 'M':
						return null; // Accept anything
					case "'":
						if (lookAhead("'"))
							chars += "'";
						else
							literal = true;
						break;
					default:
						chars += format.charAt(iFormat);
				}
		return chars;
	},

	/* Get a setting value, defaulting if necessary. */
	_get: function(inst, name) {
		return inst.settings[name] !== undefined ?
			inst.settings[name] : this._defaults[name];
	},

	/* Parse existing date and initialise date picker. */
	_setDateFromField: function(inst, noDefault) {
		if (inst.input.val() == inst.lastVal) {
			return;
		}
		var dateFormat = this._get(inst, 'dateFormat');
		var dates = inst.lastVal = inst.input ? inst.input.val() : null;
		var date, defaultDate;
		date = defaultDate = this._getDefaultDate(inst);
		var settings = this._getFormatConfig(inst);
		try {
			date = this.parseDate(dateFormat, dates, settings) || defaultDate;
		} catch (event) {
			this.log(event);
			dates = (noDefault ? '' : dates);
		}
		inst.selectedDay = date.getDate();
		inst.drawMonth = inst.selectedMonth = date.getMonth();
		inst.drawYear = inst.selectedYear = date.getFullYear();
		inst.currentDay = (dates ? date.getDate() : 0);
		inst.currentMonth = (dates ? date.getMonth() : 0);
		inst.currentYear = (dates ? date.getFullYear() : 0);
		this._adjustInstDate(inst);
	},

	/* Retrieve the default date shown on opening. */
	_getDefaultDate: function(inst) {
		return this._restrictMinMax(inst,
			this._determineDate(inst, this._get(inst, 'defaultDate'), new Date()));
	},

	/* A date may be specified as an exact value or a relative one. */
	_determineDate: function(inst, date, defaultDate) {
		var offsetNumeric = function(offset) {
			var date = new Date();
			date.setDate(date.getDate() + offset);
			return date;
		};
		var offsetString = function(offset) {
			try {
				return $.datepicker.parseDate($.datepicker._get(inst, 'dateFormat'),
					offset, $.datepicker._getFormatConfig(inst));
			}
			catch (e) {
				// Ignore
			}
			var date = (offset.toLowerCase().match(/^c/) ?
				$.datepicker._getDate(inst) : null) || new Date();
			var year = date.getFullYear();
			var month = date.getMonth();
			var day = date.getDate();
			var pattern = /([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;
			var matches = pattern.exec(offset);
			while (matches) {
				switch (matches[2] || 'd') {
					case 'd' : case 'D' :
						day += parseInt(matches[1],10); break;
					case 'w' : case 'W' :
						day += parseInt(matches[1],10) * 7; break;
					case 'm' : case 'M' :
						month += parseInt(matches[1],10);
						day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
						break;
					case 'y': case 'Y' :
						year += parseInt(matches[1],10);
						day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
						break;
				}
				matches = pattern.exec(offset);
			}
			return new Date(year, month, day);
		};
		var newDate = (date == null || date === '' ? defaultDate : (typeof date == 'string' ? offsetString(date) :
			(typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
		newDate = (newDate && newDate.toString() == 'Invalid Date' ? defaultDate : newDate);
		if (newDate) {
			newDate.setHours(0);
			newDate.setMinutes(0);
			newDate.setSeconds(0);
			newDate.setMilliseconds(0);
		}
		return this._daylightSavingAdjust(newDate);
	},

	/* Handle switch to/from daylight saving.
	   Hours may be non-zero on daylight saving cut-over:
	   > 12 when midnight changeover, but then cannot generate
	   midnight datetime, so jump to 1AM, otherwise reset.
	   @param  date  (Date) the date to check
	   @return  (Date) the corrected date */
	_daylightSavingAdjust: function(date) {
		if (!date) return null;
		date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
		return date;
	},

	/* Set the date(s) directly. */
	_setDate: function(inst, date, noChange) {
		var clear = !date;
		var origMonth = inst.selectedMonth;
		var origYear = inst.selectedYear;
		var newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
		inst.selectedDay = inst.currentDay = newDate.getDate();
		inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
		inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
		if ((origMonth != inst.selectedMonth || origYear != inst.selectedYear) && !noChange)
			this._notifyChange(inst);
		this._adjustInstDate(inst);
		if (inst.input) {
			inst.input.val(clear ? '' : this._formatDate(inst));
		}
	},

	/* Retrieve the date(s) directly. */
	_getDate: function(inst) {
		var startDate = (!inst.currentYear || (inst.input && inst.input.val() == '') ? null :
			this._daylightSavingAdjust(new Date(
			inst.currentYear, inst.currentMonth, inst.currentDay)));
			return startDate;
	},

	/* Attach the onxxx handlers.  These are declared statically so
	 * they work with static code transformers like Caja.
	 */
	_attachHandlers: function(inst) {
		var stepMonths = this._get(inst, 'stepMonths');
		var id = '#' + inst.id.replace( /\\\\/g, "\\" );
		inst.dpDiv.find('[data-handler]').map(function () {
			var handler = {
				prev: function () {
					window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, -stepMonths, 'M');
				},
				next: function () {
					window['DP_jQuery_' + dpuuid].datepicker._adjustDate(id, +stepMonths, 'M');
				},
				hide: function () {
					window['DP_jQuery_' + dpuuid].datepicker._hideDatepicker();
				},
				today: function () {
					window['DP_jQuery_' + dpuuid].datepicker._gotoToday(id);
				},
				selectDay: function () {
					window['DP_jQuery_' + dpuuid].datepicker._selectDay(id, +this.getAttribute('data-month'), +this.getAttribute('data-year'), this);
					return false;
				},
				selectMonth: function () {
					window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'M');
					return false;
				},
				selectYear: function () {
					window['DP_jQuery_' + dpuuid].datepicker._selectMonthYear(id, this, 'Y');
					return false;
				}
			};
			$(this).bind(this.getAttribute('data-event'), handler[this.getAttribute('data-handler')]);
		});
	},
	
	/* Generate the HTML for the current state of the date picker. */
	_generateHTML: function(inst) {
		var today = new Date();
		today = this._daylightSavingAdjust(
			new Date(today.getFullYear(), today.getMonth(), today.getDate())); // clear time
		var isRTL = this._get(inst, 'isRTL');
		var showButtonPanel = this._get(inst, 'showButtonPanel');
		var hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext');
		var navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat');
		var numMonths = this._getNumberOfMonths(inst);
		var showCurrentAtPos = this._get(inst, 'showCurrentAtPos');
		var stepMonths = this._get(inst, 'stepMonths');
		var isMultiMonth = (numMonths[0] != 1 || numMonths[1] != 1);
		var currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
			new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
		var minDate = this._getMinMaxDate(inst, 'min');
		var maxDate = this._getMinMaxDate(inst, 'max');
		var drawMonth = inst.drawMonth - showCurrentAtPos;
		var drawYear = inst.drawYear;
		if (drawMonth < 0) {
			drawMonth += 12;
			drawYear--;
		}
		if (maxDate) {
			var maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
				maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
			maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
			while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
				drawMonth--;
				if (drawMonth < 0) {
					drawMonth = 11;
					drawYear--;
				}
			}
		}
		inst.drawMonth = drawMonth;
		inst.drawYear = drawYear;
		var prevText = this._get(inst, 'prevText');
		prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
			this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
			this._getFormatConfig(inst)));
		var prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
			'<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click"' +
			' title="' + prevText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>' :
			(hideIfNoPrevNext ? '' : '<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+ prevText +'"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>'));
		var nextText = this._get(inst, 'nextText');
		nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
			this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
			this._getFormatConfig(inst)));
		var next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
			'<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click"' +
			' title="' + nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>' :
			(hideIfNoPrevNext ? '' : '<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+ nextText + '"><span class="ui-icon ui-icon-circle-triangle-' + ( isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>'));
		var currentText = this._get(inst, 'currentText');
		var gotoDate = (this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today);
		currentText = (!navigationAsDateFormat ? currentText :
			this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
		var controls = (!inst.inline ? '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">' +
			this._get(inst, 'closeText') + '</button>' : '');
		var buttonPanel = (showButtonPanel) ? '<div class="ui-datepicker-buttonpane ui-widget-content">' + (isRTL ? controls : '') +
			(this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click"' +
			'>' + currentText + '</button>' : '') + (isRTL ? '' : controls) + '</div>' : '';
		var firstDay = parseInt(this._get(inst, 'firstDay'),10);
		firstDay = (isNaN(firstDay) ? 0 : firstDay);
		var showWeek = this._get(inst, 'showWeek');
		var dayNames = this._get(inst, 'dayNames');
		var dayNamesShort = this._get(inst, 'dayNamesShort');
		var dayNamesMin = this._get(inst, 'dayNamesMin');
		var monthNames = this._get(inst, 'monthNames');
		var monthNamesShort = this._get(inst, 'monthNamesShort');
		var beforeShowDay = this._get(inst, 'beforeShowDay');
		var showOtherMonths = this._get(inst, 'showOtherMonths');
		var selectOtherMonths = this._get(inst, 'selectOtherMonths');
		var calculateWeek = this._get(inst, 'calculateWeek') || this.iso8601Week;
		var defaultDate = this._getDefaultDate(inst);
		var html = '';
		for (var row = 0; row < numMonths[0]; row++) {
			var group = '';
			this.maxRows = 4;
			for (var col = 0; col < numMonths[1]; col++) {
				var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
				var cornerClass = ' ui-corner-all';
				var calender = '';
				if (isMultiMonth) {
					calender += '<div class="ui-datepicker-group';
					if (numMonths[1] > 1)
						switch (col) {
							case 0: calender += ' ui-datepicker-group-first';
								cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left'); break;
							case numMonths[1]-1: calender += ' ui-datepicker-group-last';
								cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right'); break;
							default: calender += ' ui-datepicker-group-middle'; cornerClass = ''; break;
						}
					calender += '">';
				}
				calender += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' +
					(/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') +
					(/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') +
					this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
					row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
					'</div><table class="ui-datepicker-calendar"><thead>' +
					'<tr>';
				var thead = (showWeek ? '<th class="ui-datepicker-week-col">' + this._get(inst, 'weekHeader') + '</th>' : '');
				for (var dow = 0; dow < 7; dow++) { // days of the week
					var day = (dow + firstDay) % 7;
					thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' +
						'<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>';
				}
				calender += thead + '</tr></thead><tbody>';
				var daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
				if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth)
					inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
				var leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
				var curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
				var numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
				this.maxRows = numRows;
				var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
				for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows
					calender += '<tr>';
					var tbody = (!showWeek ? '' : '<td class="ui-datepicker-week-col">' +
						this._get(inst, 'calculateWeek')(printDate) + '</td>');
					for (var dow = 0; dow < 7; dow++) { // create date picker days
						var daySettings = (beforeShowDay ?
							beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, '']);
						var otherMonth = (printDate.getMonth() != drawMonth);
						var unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
							(minDate && printDate < minDate) || (maxDate && printDate > maxDate);
						tbody += '<td class="' +
							((dow + firstDay + 6) % 7 >= 5 ? ' ui-datepicker-week-end' : '') + // highlight weekends
							(otherMonth ? ' ui-datepicker-other-month' : '') + // highlight days from other months
							((printDate.getTime() == selectedDate.getTime() && drawMonth == inst.selectedMonth && inst._keyEvent) || // user pressed key
							(defaultDate.getTime() == printDate.getTime() && defaultDate.getTime() == selectedDate.getTime()) ?
							// or defaultDate is current printedDate and defaultDate is selectedDate
							' ' + this._dayOverClass : '') + // highlight selected day
							(unselectable ? ' ' + this._unselectableClass + ' ui-state-disabled': '') +  // highlight unselectable days
							(otherMonth && !showOtherMonths ? '' : ' ' + daySettings[1] + // highlight custom dates
							(printDate.getTime() == currentDate.getTime() ? ' ' + this._currentClass : '') + // highlight selected day
							(printDate.getTime() == today.getTime() ? ' ui-datepicker-today' : '')) + '"' + // highlight today (if different)
							((!otherMonth || showOtherMonths) && daySettings[2] ? ' title="' + daySettings[2] + '"' : '') + // cell title
							(unselectable ? '' : ' data-handler="selectDay" data-event="click" data-month="' + printDate.getMonth() + '" data-year="' + printDate.getFullYear() + '"') + '>' + // actions
							(otherMonth && !showOtherMonths ? '&#xa0;' : // display for other months
							(unselectable ? '<span class="ui-state-default">' + printDate.getDate() + '</span>' : '<a class="ui-state-default' +
							(printDate.getTime() == today.getTime() ? ' ui-state-highlight' : '') +
							(printDate.getTime() == currentDate.getTime() ? ' ui-state-active' : '') + // highlight selected day
							(otherMonth ? ' ui-priority-secondary' : '') + // distinguish dates from other months
							'" href="#">' + printDate.getDate() + '</a>')) + '</td>'; // display selectable date
						printDate.setDate(printDate.getDate() + 1);
						printDate = this._daylightSavingAdjust(printDate);
					}
					calender += tbody + '</tr>';
				}
				drawMonth++;
				if (drawMonth > 11) {
					drawMonth = 0;
					drawYear++;
				}
				calender += '</tbody></table>' + (isMultiMonth ? '</div>' + 
							((numMonths[0] > 0 && col == numMonths[1]-1) ? '<div class="ui-datepicker-row-break"></div>' : '') : '');
				group += calender;
			}
			html += group;
		}
		html += buttonPanel + ($.browser.msie && parseInt($.browser.version,10) < 7 && !inst.inline ?
			'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>' : '');
		inst._keyEvent = false;
		return html;
	},

	/* Generate the month and year header. */
	_generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
			secondary, monthNames, monthNamesShort) {
		var changeMonth = this._get(inst, 'changeMonth');
		var changeYear = this._get(inst, 'changeYear');
		var showMonthAfterYear = this._get(inst, 'showMonthAfterYear');
		var html = '<div class="ui-datepicker-title">';
		var monthHtml = '';
		// month selection
		if (secondary || !changeMonth)
			monthHtml += '<span class="ui-datepicker-month">' + monthNames[drawMonth] + '</span>';
		else {
			var inMinYear = (minDate && minDate.getFullYear() == drawYear);
			var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear);
			monthHtml += '<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">';
			for (var month = 0; month < 12; month++) {
				if ((!inMinYear || month >= minDate.getMonth()) &&
						(!inMaxYear || month <= maxDate.getMonth()))
					monthHtml += '<option value="' + month + '"' +
						(month == drawMonth ? ' selected="selected"' : '') +
						'>' + monthNamesShort[month] + '</option>';
			}
			monthHtml += '</select>';
		}
		if (!showMonthAfterYear)
			html += monthHtml + (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '');
		// year selection
		if ( !inst.yearshtml ) {
			inst.yearshtml = '';
			if (secondary || !changeYear)
				html += '<span class="ui-datepicker-year">' + drawYear + '</span>';
			else {
				// determine range of years to display
				var years = this._get(inst, 'yearRange').split(':');
				var thisYear = new Date().getFullYear();
				var determineYear = function(value) {
					var year = (value.match(/c[+-].*/) ? drawYear + parseInt(value.substring(1), 10) :
						(value.match(/[+-].*/) ? thisYear + parseInt(value, 10) :
						parseInt(value, 10)));
					return (isNaN(year) ? thisYear : year);
				};
				var year = determineYear(years[0]);
				var endYear = Math.max(year, determineYear(years[1] || ''));
				year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
				endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
				inst.yearshtml += '<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">';
				for (; year <= endYear; year++) {
					inst.yearshtml += '<option value="' + year + '"' +
						(year == drawYear ? ' selected="selected"' : '') +
						'>' + year + '</option>';
				}
				inst.yearshtml += '</select>';
				
				html += inst.yearshtml;
				inst.yearshtml = null;
			}
		}
		html += this._get(inst, 'yearSuffix');
		if (showMonthAfterYear)
			html += (secondary || !(changeMonth && changeYear) ? '&#xa0;' : '') + monthHtml;
		html += '</div>'; // Close datepicker_header
		return html;
	},

	/* Adjust one of the date sub-fields. */
	_adjustInstDate: function(inst, offset, period) {
		var year = inst.drawYear + (period == 'Y' ? offset : 0);
		var month = inst.drawMonth + (period == 'M' ? offset : 0);
		var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) +
			(period == 'D' ? offset : 0);
		var date = this._restrictMinMax(inst,
			this._daylightSavingAdjust(new Date(year, month, day)));
		inst.selectedDay = date.getDate();
		inst.drawMonth = inst.selectedMonth = date.getMonth();
		inst.drawYear = inst.selectedYear = date.getFullYear();
		if (period == 'M' || period == 'Y')
			this._notifyChange(inst);
	},

	/* Ensure a date is within any min/max bounds. */
	_restrictMinMax: function(inst, date) {
		var minDate = this._getMinMaxDate(inst, 'min');
		var maxDate = this._getMinMaxDate(inst, 'max');
		var newDate = (minDate && date < minDate ? minDate : date);
		newDate = (maxDate && newDate > maxDate ? maxDate : newDate);
		return newDate;
	},

	/* Notify change of month/year. */
	_notifyChange: function(inst) {
		var onChange = this._get(inst, 'onChangeMonthYear');
		if (onChange)
			onChange.apply((inst.input ? inst.input[0] : null),
				[inst.selectedYear, inst.selectedMonth + 1, inst]);
	},

	/* Determine the number of months to show. */
	_getNumberOfMonths: function(inst) {
		var numMonths = this._get(inst, 'numberOfMonths');
		return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths));
	},

	/* Determine the current maximum date - ensure no time components are set. */
	_getMinMaxDate: function(inst, minMax) {
		return this._determineDate(inst, this._get(inst, minMax + 'Date'), null);
	},

	/* Find the number of days in a given month. */
	_getDaysInMonth: function(year, month) {
		return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
	},

	/* Find the day of the week of the first of a month. */
	_getFirstDayOfMonth: function(year, month) {
		return new Date(year, month, 1).getDay();
	},

	/* Determines if we should allow a "next/prev" month display change. */
	_canAdjustMonth: function(inst, offset, curYear, curMonth) {
		var numMonths = this._getNumberOfMonths(inst);
		var date = this._daylightSavingAdjust(new Date(curYear,
			curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));
		if (offset < 0)
			date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
		return this._isInRange(inst, date);
	},

	/* Is the given date in the accepted range? */
	_isInRange: function(inst, date) {
		var minDate = this._getMinMaxDate(inst, 'min');
		var maxDate = this._getMinMaxDate(inst, 'max');
		return ((!minDate || date.getTime() >= minDate.getTime()) &&
			(!maxDate || date.getTime() <= maxDate.getTime()));
	},

	/* Provide the configuration settings for formatting/parsing. */
	_getFormatConfig: function(inst) {
		var shortYearCutoff = this._get(inst, 'shortYearCutoff');
		shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
			new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
		return {shortYearCutoff: shortYearCutoff,
			dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'),
			monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')};
	},

	/* Format the given date for display. */
	_formatDate: function(inst, day, month, year) {
		if (!day) {
			inst.currentDay = inst.selectedDay;
			inst.currentMonth = inst.selectedMonth;
			inst.currentYear = inst.selectedYear;
		}
		var date = (day ? (typeof day == 'object' ? day :
			this._daylightSavingAdjust(new Date(year, month, day))) :
			this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
		return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst));
	}
});

/*
 * Bind hover events for datepicker elements.
 * Done via delegate so the binding only occurs once in the lifetime of the parent div.
 * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
 */ 
function bindHover(dpDiv) {
	var selector = 'button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a';
	return dpDiv.delegate(selector, 'mouseout', function() {
			$(this).removeClass('ui-state-hover');
			if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).removeClass('ui-datepicker-prev-hover');
			if (this.className.indexOf('ui-datepicker-next') != -1) $(this).removeClass('ui-datepicker-next-hover');
		})
		.delegate(selector, 'mouseover', function(){
			if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) {
				$(this).parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover');
				$(this).addClass('ui-state-hover');
				if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).addClass('ui-datepicker-prev-hover');
				if (this.className.indexOf('ui-datepicker-next') != -1) $(this).addClass('ui-datepicker-next-hover');
			}
		});
}

/* jQuery extend now ignores nulls! */
function extendRemove(target, props) {
	$.extend(target, props);
	for (var name in props)
		if (props[name] == null || props[name] == undefined)
			target[name] = props[name];
	return target;
};

/* Invoke the datepicker functionality.
   @param  options  string - a command, optionally followed by additional parameters or
	                Object - settings for attaching new datepicker functionality
   @return  jQuery object */
$.fn.datepicker = function(options){
	
	/* Verify an empty collection wasn't passed - Fixes #6976 */
	if ( !this.length ) {
		return this;
	}
	
	/* Initialise the date picker. */
	if (!$.datepicker.initialized) {
		$(document).mousedown($.datepicker._checkExternalClick).
			find(document.body).append($.datepicker.dpDiv);
		$.datepicker.initialized = true;
	}

	var otherArgs = Array.prototype.slice.call(arguments, 1);
	if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate' || options == 'widget'))
		return $.datepicker['_' + options + 'Datepicker'].
			apply($.datepicker, [this[0]].concat(otherArgs));
	if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string')
		return $.datepicker['_' + options + 'Datepicker'].
			apply($.datepicker, [this[0]].concat(otherArgs));
	return this.each(function() {
		typeof options == 'string' ?
			$.datepicker['_' + options + 'Datepicker'].
				apply($.datepicker, [this].concat(otherArgs)) :
			$.datepicker._attachDatepicker(this, options);
	});
};

$.datepicker = new Datepicker(); // singleton instance
$.datepicker.initialized = false;
$.datepicker.uuid = new Date().getTime();
$.datepicker.version = "1.9.0";

// Workaround for #4055
// Add another global to avoid noConflict issues with inline event handlers
window['DP_jQuery_' + dpuuid] = $;

})(jQuery);
(function( $, undefined ) {

var uiDialogClasses = "ui-dialog ui-widget ui-widget-content ui-corner-all ",
	sizeRelatedOptions = {
		buttons: true,
		height: true,
		maxHeight: true,
		maxWidth: true,
		minHeight: true,
		minWidth: true,
		width: true
	},
	resizableRelatedOptions = {
		maxHeight: true,
		maxWidth: true,
		minHeight: true,
		minWidth: true
	};

$.widget("ui.dialog", {
	version: "1.9.0",
	options: {
		autoOpen: true,
		buttons: {},
		closeOnEscape: true,
		closeText: "close",
		dialogClass: "",
		draggable: true,
		hide: null,
		height: "auto",
		maxHeight: false,
		maxWidth: false,
		minHeight: 150,
		minWidth: 150,
		modal: false,
		position: {
			my: "center",
			at: "center",
			of: window,
			collision: "fit",
			// ensure that the titlebar is never outside the document
			using: function( pos ) {
				var topOffset = $( this ).css( pos ).offset().top;
				if ( topOffset < 0 ) {
					$( this ).css( "top", pos.top - topOffset );
				}
			}
		},
		resizable: true,
		show: null,
		stack: true,
		title: "",
		width: 300,
		zIndex: 1000
	},

	_create: function() {
		this.originalTitle = this.element.attr( "title" );
		// #5742 - .attr() might return a DOMElement
		if ( typeof this.originalTitle !== "string" ) {
			this.originalTitle = "";
		}
		this.oldPosition = {
			parent: this.element.parent(),
			index: this.element.parent().children().index( this.element )
		};
		this.options.title = this.options.title || this.originalTitle;
		var that = this,
			options = this.options,

			title = options.title || "&#160;",

			uiDialog = ( this.uiDialog = $( "<div>" ) )
				.addClass( uiDialogClasses + options.dialogClass )
				.css({
					display: "none",
					outline: 0, // TODO: move to stylesheet
					zIndex: options.zIndex
				})
				// setting tabIndex makes the div focusable
				.attr( "tabIndex", -1)
				.keydown(function( event ) {
					if ( options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
							event.keyCode === $.ui.keyCode.ESCAPE ) {
						that.close( event );
						event.preventDefault();
					}
				})
				.mousedown(function( event ) {
					that.moveToTop( false, event );
				})
				.appendTo( "body" ),

			uiDialogContent = this.element
				.show()
				.removeAttr( "title" )
				.addClass( "ui-dialog-content ui-widget-content" )
				.appendTo( uiDialog ),

			uiDialogTitlebar = ( this.uiDialogTitlebar = $( "<div>" ) )
				.addClass( "ui-dialog-titlebar  ui-widget-header  " +
					"ui-corner-all  ui-helper-clearfix" )
				.prependTo( uiDialog ),

			uiDialogTitlebarClose = $( "<a href='#'></a>" )
				.addClass( "ui-dialog-titlebar-close  ui-corner-all" )
				.attr( "role", "button" )
				.click(function( event ) {
					event.preventDefault();
					that.close( event );
				})
				.appendTo( uiDialogTitlebar ),

			uiDialogTitlebarCloseText = ( this.uiDialogTitlebarCloseText = $( "<span>" ) )
				.addClass( "ui-icon ui-icon-closethick" )
				.text( options.closeText )
				.appendTo( uiDialogTitlebarClose ),

			uiDialogTitle = $( "<span>" )
				.uniqueId()
				.addClass( "ui-dialog-title" )
				.html( title )
				.prependTo( uiDialogTitlebar ),

			uiDialogButtonPane = ( this.uiDialogButtonPane = $( "<div>" ) )
				.addClass( "ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" ),

			uiButtonSet = ( this.uiButtonSet = $( "<div>" ) )
				.addClass( "ui-dialog-buttonset" )
				.appendTo( uiDialogButtonPane );

		uiDialog.attr({
			role: "dialog",
			"aria-labelledby": uiDialogTitle.attr( "id" )
		});

		uiDialogTitlebar.find( "*" ).add( uiDialogTitlebar ).disableSelection();
		this._hoverable( uiDialogTitlebarClose );
		this._focusable( uiDialogTitlebarClose );

		if ( options.draggable && $.fn.draggable ) {
			this._makeDraggable();
		}
		if ( options.resizable && $.fn.resizable ) {
			this._makeResizable();
		}

		this._createButtons( options.buttons );
		this._isOpen = false;

		if ( $.fn.bgiframe ) {
			uiDialog.bgiframe();
		}

		// prevent tabbing out of modal dialogs
		this._on( uiDialog, { keydown: function( event ) {
			if ( !options.modal || event.keyCode !== $.ui.keyCode.TAB ) {
				return;
			}

			var tabbables = $( ":tabbable", uiDialog ),
				first = tabbables.filter( ":first" ),
				last  = tabbables.filter( ":last" );

			if ( event.target === last[0] && !event.shiftKey ) {
				first.focus( 1 );
				return false;
			} else if ( event.target === first[0] && event.shiftKey ) {
				last.focus( 1 );
				return false;
			}
		}});
	},

	_init: function() {
		if ( this.options.autoOpen ) {
			this.open();
		}
	},

	_destroy: function() {
		var next,
			oldPosition = this.oldPosition;

		if ( this.overlay ) {
			this.overlay.destroy();
		}
		this.uiDialog.hide();
		this.element
			.removeClass( "ui-dialog-content ui-widget-content" )
			.hide()
			.appendTo( "body" );
		this.uiDialog.remove();

		if ( this.originalTitle ) {
			this.element.attr( "title", this.originalTitle );
		}

		next = oldPosition.parent.children().eq( oldPosition.index );
		// Don't try to place the dialog next to itself (#8613)
		if ( next.length && next[ 0 ] !== this.element[ 0 ] ) {
			next.before( this.element );
		} else {
			oldPosition.parent.append( this.element );
		}
	},

	widget: function() {
		return this.uiDialog;
	},

	close: function( event ) {
		var that = this,
			maxZ, thisZ;

		if ( !this._isOpen ) {
			return;
		}

		if ( false === this._trigger( "beforeClose", event ) ) {
			return;
		}

		this._isOpen = false;

		if ( this.overlay ) {
			this.overlay.destroy();
		}

		if ( this.options.hide ) {
			this.uiDialog.hide( this.options.hide, function() {
				that._trigger( "close", event );
			});
		} else {
			this.uiDialog.hide();
			this._trigger( "close", event );
		}

		$.ui.dialog.overlay.resize();

		// adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
		if ( this.options.modal ) {
			maxZ = 0;
			$( ".ui-dialog" ).each(function() {
				if ( this !== that.uiDialog[0] ) {
					thisZ = $( this ).css( "z-index" );
					if ( !isNaN( thisZ ) ) {
						maxZ = Math.max( maxZ, thisZ );
					}
				}
			});
			$.ui.dialog.maxZ = maxZ;
		}

		return this;
	},

	isOpen: function() {
		return this._isOpen;
	},

	// the force parameter allows us to move modal dialogs to their correct
	// position on open
	moveToTop: function( force, event ) {
		var options = this.options,
			saveScroll;

		if ( ( options.modal && !force ) ||
				( !options.stack && !options.modal ) ) {
			return this._trigger( "focus", event );
		}

		if ( options.zIndex > $.ui.dialog.maxZ ) {
			$.ui.dialog.maxZ = options.zIndex;
		}
		if ( this.overlay ) {
			$.ui.dialog.maxZ += 1;
			$.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ;
			this.overlay.$el.css( "z-index", $.ui.dialog.overlay.maxZ );
		}

		// Save and then restore scroll
		// Opera 9.5+ resets when parent z-index is changed.
		// http://bugs.jqueryui.com/ticket/3193
		saveScroll = {
			scrollTop: this.element.scrollTop(),
			scrollLeft: this.element.scrollLeft()
		};
		$.ui.dialog.maxZ += 1;
		this.uiDialog.css( "z-index", $.ui.dialog.maxZ );
		this.element.attr( saveScroll );
		this._trigger( "focus", event );

		return this;
	},

	open: function() {
		if ( this._isOpen ) {
			return;
		}

		var hasFocus,
			options = this.options,
			uiDialog = this.uiDialog;

		this._size();
		this._position( options.position );
		uiDialog.show( options.show );
		this.overlay = options.modal ? new $.ui.dialog.overlay( this ) : null;
		this.moveToTop( true );

		// set focus to the first tabbable element in the content area or the first button
		// if there are no tabbable elements, set focus on the dialog itself
		hasFocus = this.element.find( ":tabbable" );
		if ( !hasFocus.length ) {
			hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
			if ( !hasFocus.length ) {
				hasFocus = uiDialog;
			}
		}
		hasFocus.eq( 0 ).focus();

		this._isOpen = true;
		this._trigger( "open" );

		return this;
	},

	_createButtons: function( buttons ) {
		var uiDialogButtonPane, uiButtonSet,
			that = this,
			hasButtons = false;

		// if we already have a button pane, remove it
		this.uiDialogButtonPane.remove();
		this.uiButtonSet.empty();

		if ( typeof buttons === "object" && buttons !== null ) {
			$.each( buttons, function() {
				return !(hasButtons = true);
			});
		}
		if ( hasButtons ) {
			$.each( buttons, function( name, props ) {
				props = $.isFunction( props ) ?
					{ click: props, text: name } :
					props;
				var button = $( "<button type='button'>" )
					.attr( props, true )
					.unbind( "click" )
					.click(function() {
						props.click.apply( that.element[0], arguments );
					})
					.appendTo( that.uiButtonSet );
				if ( $.fn.button ) {
					button.button();
				}
			});
			this.uiDialog.addClass( "ui-dialog-buttons" );
			this.uiDialogButtonPane.appendTo( this.uiDialog );
		} else {
			this.uiDialog.removeClass( "ui-dialog-buttons" );
		}
	},

	_makeDraggable: function() {
		var that = this,
			options = this.options;

		function filteredUi( ui ) {
			return {
				position: ui.position,
				offset: ui.offset
			};
		}

		this.uiDialog.draggable({
			cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
			handle: ".ui-dialog-titlebar",
			containment: "document",
			start: function( event, ui ) {
				$( this )
					.addClass( "ui-dialog-dragging" );
				that._trigger( "dragStart", event, filteredUi( ui ) );
			},
			drag: function( event, ui ) {
				that._trigger( "drag", event, filteredUi( ui ) );
			},
			stop: function( event, ui ) {
				options.position = [
					ui.position.left - that.document.scrollLeft(),
					ui.position.top - that.document.scrollTop()
				];
				$( this )
					.removeClass( "ui-dialog-dragging" );
				that._trigger( "dragStop", event, filteredUi( ui ) );
				$.ui.dialog.overlay.resize();
			}
		});
	},

	_makeResizable: function( handles ) {
		handles = (handles === undefined ? this.options.resizable : handles);
		var that = this,
			options = this.options,
			// .ui-resizable has position: relative defined in the stylesheet
			// but dialogs have to use absolute or fixed positioning
			position = this.uiDialog.css( "position" ),
			resizeHandles = typeof handles === 'string' ?
				handles	:
				"n,e,s,w,se,sw,ne,nw";

		function filteredUi( ui ) {
			return {
				originalPosition: ui.originalPosition,
				originalSize: ui.originalSize,
				position: ui.position,
				size: ui.size
			};
		}

		this.uiDialog.resizable({
			cancel: ".ui-dialog-content",
			containment: "document",
			alsoResize: this.element,
			maxWidth: options.maxWidth,
			maxHeight: options.maxHeight,
			minWidth: options.minWidth,
			minHeight: this._minHeight(),
			handles: resizeHandles,
			start: function( event, ui ) {
				$( this ).addClass( "ui-dialog-resizing" );
				that._trigger( "resizeStart", event, filteredUi( ui ) );
			},
			resize: function( event, ui ) {
				that._trigger( "resize", event, filteredUi( ui ) );
			},
			stop: function( event, ui ) {
				$( this ).removeClass( "ui-dialog-resizing" );
				options.height = $( this ).height();
				options.width = $( this ).width();
				that._trigger( "resizeStop", event, filteredUi( ui ) );
				$.ui.dialog.overlay.resize();
			}
		})
		.css( "position", position )
		.find( ".ui-resizable-se" )
			.addClass( "ui-icon ui-icon-grip-diagonal-se" );
	},

	_minHeight: function() {
		var options = this.options;

		if ( options.height === "auto" ) {
			return options.minHeight;
		} else {
			return Math.min( options.minHeight, options.height );
		}
	},

	_position: function( position ) {
		var myAt = [],
			offset = [ 0, 0 ],
			isVisible;

		if ( position ) {
			// deep extending converts arrays to objects in jQuery <= 1.3.2 :-(
	//		if (typeof position == 'string' || $.isArray(position)) {
	//			myAt = $.isArray(position) ? position : position.split(' ');

			if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) {
				myAt = position.split ? position.split( " " ) : [ position[ 0 ], position[ 1 ] ];
				if ( myAt.length === 1 ) {
					myAt[ 1 ] = myAt[ 0 ];
				}

				$.each( [ "left", "top" ], function( i, offsetPosition ) {
					if ( +myAt[ i ] === myAt[ i ] ) {
						offset[ i ] = myAt[ i ];
						myAt[ i ] = offsetPosition;
					}
				});

				position = {
					my: myAt.join( " " ),
					at: myAt.join( " " ),
					offset: offset.join( " " )
				};
			}

			position = $.extend( {}, $.ui.dialog.prototype.options.position, position );
		} else {
			position = $.ui.dialog.prototype.options.position;
		}

		// need to show the dialog to get the actual offset in the position plugin
		isVisible = this.uiDialog.is( ":visible" );
		if ( !isVisible ) {
			this.uiDialog.show();
		}
		this.uiDialog.position( position );
		if ( !isVisible ) {
			this.uiDialog.hide();
		}
	},

	_setOptions: function( options ) {
		var that = this,
			resizableOptions = {},
			resize = false;

		$.each( options, function( key, value ) {
			that._setOption( key, value );

			if ( key in sizeRelatedOptions ) {
				resize = true;
			}
			if ( key in resizableRelatedOptions ) {
				resizableOptions[ key ] = value;
			}
		});

		if ( resize ) {
			this._size();
		}
		if ( this.uiDialog.is( ":data(resizable)" ) ) {
			this.uiDialog.resizable( "option", resizableOptions );
		}
	},

	_setOption: function( key, value ) {
		var isDraggable, isResizable,
			uiDialog = this.uiDialog;

		switch ( key ) {
			case "buttons":
				this._createButtons( value );
				break;
			case "closeText":
				// ensure that we always pass a string
				this.uiDialogTitlebarCloseText.text( "" + value );
				break;
			case "dialogClass":
				uiDialog
					.removeClass( this.options.dialogClass )
					.addClass( uiDialogClasses + value );
				break;
			case "disabled":
				if ( value ) {
					uiDialog.addClass( "ui-dialog-disabled" );
				} else {
					uiDialog.removeClass( "ui-dialog-disabled" );
				}
				break;
			case "draggable":
				isDraggable = uiDialog.is( ":data(draggable)" );
				if ( isDraggable && !value ) {
					uiDialog.draggable( "destroy" );
				}

				if ( !isDraggable && value ) {
					this._makeDraggable();
				}
				break;
			case "position":
				this._position( value );
				break;
			case "resizable":
				// currently resizable, becoming non-resizable
				isResizable = uiDialog.is( ":data(resizable)" );
				if ( isResizable && !value ) {
					uiDialog.resizable( "destroy" );
				}

				// currently resizable, changing handles
				if ( isResizable && typeof value === "string" ) {
					uiDialog.resizable( "option", "handles", value );
				}

				// currently non-resizable, becoming resizable
				if ( !isResizable && value !== false ) {
					this._makeResizable( value );
				}
				break;
			case "title":
				// convert whatever was passed in o a string, for html() to not throw up
				$( ".ui-dialog-title", this.uiDialogTitlebar )
					.html( "" + ( value || "&#160;" ) );
				break;
		}

		this._super( key, value );
	},

	_size: function() {
		/* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
		 * divs will both have width and height set, so we need to reset them
		 */
		var nonContentHeight, minContentHeight, autoHeight,
			options = this.options,
			isVisible = this.uiDialog.is( ":visible" );

		// reset content sizing
		this.element.show().css({
			width: "auto",
			minHeight: 0,
			height: 0
		});

		if ( options.minWidth > options.width ) {
			options.width = options.minWidth;
		}

		// reset wrapper sizing
		// determine the height of all the non-content elements
		nonContentHeight = this.uiDialog.css({
				height: "auto",
				width: options.width
			})
			.outerHeight();
		minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );

		if ( options.height === "auto" ) {
			// only needed for IE6 support
			if ( $.support.minHeight ) {
				this.element.css({
					minHeight: minContentHeight,
					height: "auto"
				});
			} else {
				this.uiDialog.show();
				autoHeight = this.element.css( "height", "auto" ).height();
				if ( !isVisible ) {
					this.uiDialog.hide();
				}
				this.element.height( Math.max( autoHeight, minContentHeight ) );
			}
		} else {
			this.element.height( Math.max( options.height - nonContentHeight, 0 ) );
		}

		if (this.uiDialog.is( ":data(resizable)" ) ) {
			this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
		}
	}
});

$.extend($.ui.dialog, {
	uuid: 0,
	maxZ: 0,

	getTitleId: function($el) {
		var id = $el.attr( "id" );
		if ( !id ) {
			this.uuid += 1;
			id = this.uuid;
		}
		return "ui-dialog-title-" + id;
	},

	overlay: function( dialog ) {
		this.$el = $.ui.dialog.overlay.create( dialog );
	}
});

$.extend( $.ui.dialog.overlay, {
	instances: [],
	// reuse old instances due to IE memory leak with alpha transparency (see #5185)
	oldInstances: [],
	maxZ: 0,
	events: $.map(
		"focus,mousedown,mouseup,keydown,keypress,click".split( "," ),
		function( event ) {
			return event + ".dialog-overlay";
		}
	).join( " " ),
	create: function( dialog ) {
		if ( this.instances.length === 0 ) {
			// prevent use of anchors and inputs
			// we use a setTimeout in case the overlay is created from an
			// event that we're going to be cancelling (see #2804)
			setTimeout(function() {
				// handle $(el).dialog().dialog('close') (see #4065)
				if ( $.ui.dialog.overlay.instances.length ) {
					$( document ).bind( $.ui.dialog.overlay.events, function( event ) {
						// stop events if the z-index of the target is < the z-index of the overlay
						// we cannot return true when we don't want to cancel the event (#3523)
						if ( $( event.target ).zIndex() < $.ui.dialog.overlay.maxZ ) {
							return false;
						}
					});
				}
			}, 1 );

			// handle window resize
			$( window ).bind( "resize.dialog-overlay", $.ui.dialog.overlay.resize );
		}

		var $el = ( this.oldInstances.pop() || $( "<div>" ).addClass( "ui-widget-overlay" ) );

		// allow closing by pressing the escape key
		$( document ).bind( "keydown.dialog-overlay", function( event ) {
			var instances = $.ui.dialog.overlay.instances;
			// only react to the event if we're the top overlay
			if ( instances.length !== 0 && instances[ instances.length - 1 ] === $el &&
				dialog.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
				event.keyCode === $.ui.keyCode.ESCAPE ) {

				dialog.close( event );
				event.preventDefault();
			}
		});

		$el.appendTo( document.body ).css({
			width: this.width(),
			height: this.height()
		});

		if ( $.fn.bgiframe ) {
			$el.bgiframe();
		}

		this.instances.push( $el );
		return $el;
	},

	destroy: function( $el ) {
		var indexOf = $.inArray( $el, this.instances ),
			maxZ = 0;

		if ( indexOf !== -1 ) {
			this.oldInstances.push( this.instances.splice( indexOf, 1 )[ 0 ] );
		}

		if ( this.instances.length === 0 ) {
			$( [ document, window ] ).unbind( ".dialog-overlay" );
		}

		$el.height( 0 ).width( 0 ).remove();

		// adjust the maxZ to allow other modal dialogs to continue to work (see #4309)
		$.each( this.instances, function() {
			maxZ = Math.max( maxZ, this.css( "z-index" ) );
		});
		this.maxZ = maxZ;
	},

	height: function() {
		var scrollHeight,
			offsetHeight;
		// handle IE
		if ( $.browser.msie ) {
			scrollHeight = Math.max(
				document.documentElement.scrollHeight,
				document.body.scrollHeight
			);
			offsetHeight = Math.max(
				document.documentElement.offsetHeight,
				document.body.offsetHeight
			);

			if ( scrollHeight < offsetHeight ) {
				return $( window ).height() + "px";
			} else {
				return scrollHeight + "px";
			}
		// handle "good" browsers
		} else {
			return $( document ).height() + "px";
		}
	},

	width: function() {
		var scrollWidth,
			offsetWidth;
		// handle IE
		if ( $.browser.msie ) {
			scrollWidth = Math.max(
				document.documentElement.scrollWidth,
				document.body.scrollWidth
			);
			offsetWidth = Math.max(
				document.documentElement.offsetWidth,
				document.body.offsetWidth
			);

			if ( scrollWidth < offsetWidth ) {
				return $( window ).width() + "px";
			} else {
				return scrollWidth + "px";
			}
		// handle "good" browsers
		} else {
			return $( document ).width() + "px";
		}
	},

	resize: function() {
		/* If the dialog is draggable and the user drags it past the
		 * right edge of the window, the document becomes wider so we
		 * need to stretch the overlay. If the user then drags the
		 * dialog back to the left, the document will become narrower,
		 * so we need to shrink the overlay to the appropriate size.
		 * This is handled by shrinking the overlay before setting it
		 * to the full document size.
		 */
		var $overlays = $( [] );
		$.each( $.ui.dialog.overlay.instances, function() {
			$overlays = $overlays.add( this );
		});

		$overlays.css({
			width: 0,
			height: 0
		}).css({
			width: $.ui.dialog.overlay.width(),
			height: $.ui.dialog.overlay.height()
		});
	}
});

$.extend( $.ui.dialog.overlay.prototype, {
	destroy: function() {
		$.ui.dialog.overlay.destroy( this.$el );
	}
});

}( jQuery ) );
(function( $, undefined ) {

var mouseHandled = false;

$.widget( "ui.menu", {
	version: "1.9.0",
	defaultElement: "<ul>",
	delay: 300,
	options: {
		icons: {
			submenu: "ui-icon-carat-1-e"
		},
		menus: "ul",
		position: {
			my: "left top",
			at: "right top"
		},
		role: "menu",

		// callbacks
		blur: null,
		focus: null,
		select: null
	},

	_create: function() {
		this.activeMenu = this.element;
		this.element
			.uniqueId()
			.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
			.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
			.attr({
				role: this.options.role,
				tabIndex: 0
			})
			// need to catch all clicks on disabled menu
			// not possible through _on
			.bind( "click" + this.eventNamespace, $.proxy(function( event ) {
				if ( this.options.disabled ) {
					event.preventDefault();
				}
			}, this ));

		if ( this.options.disabled ) {
			this.element
				.addClass( "ui-state-disabled" )
				.attr( "aria-disabled", "true" );
		}

		this._on({
			// Prevent focus from sticking to links inside menu after clicking
			// them (focus should always stay on UL during navigation).
			"mousedown .ui-menu-item > a": function( event ) {
				event.preventDefault();
			},
			"click .ui-state-disabled > a": function( event ) {
				event.preventDefault();
			},
			"click .ui-menu-item:has(a)": function( event ) {
				var target = $( event.target ).closest( ".ui-menu-item" );
				if ( !mouseHandled && target.not( ".ui-state-disabled" ).length ) {
					mouseHandled = true;

					this.select( event );
					// Open submenu on click
					if ( target.has( ".ui-menu" ).length ) {
						this.expand( event );
					} else if ( !this.element.is( ":focus" ) ) {
						// Redirect focus to the menu
						this.element.trigger( "focus", [ true ] );

						// If the active item is on the top level, let it stay active.
						// Otherwise, blur the active item since it is no longer visible.
						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
							clearTimeout( this.timer );
						}
					}
				}
			},
			"mouseenter .ui-menu-item": function( event ) {
				var target = $( event.currentTarget );
				// Remove ui-state-active class from siblings of the newly focused menu item
				// to avoid a jump caused by adjacent elements both having a class with a border
				target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
				this.focus( event, target );
			},
			mouseleave: "collapseAll",
			"mouseleave .ui-menu": "collapseAll",
			focus: function( event, keepActiveItem ) {
				// If there's already an active item, keep it active
				// If not, activate the first item
				var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );

				if ( !keepActiveItem ) {
					this.focus( event, item );
				}
			},
			blur: function( event ) {
				this._delay(function() {
					if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
						this.collapseAll( event );
					}
				});
			},
			keydown: "_keydown"
		});

		this.refresh();

		// Clicks outside of a menu collapse any open menus
		this._on( this.document, {
			click: function( event ) {
				if ( !$( event.target ).closest( ".ui-menu" ).length ) {
					this.collapseAll( event );
				}

				// Reset the mouseHandled flag
				mouseHandled = false;
			}
		});
	},

	_destroy: function() {
		// Destroy (sub)menus
		this.element
			.removeAttr( "aria-activedescendant" )
			.find( ".ui-menu" ).andSelf()
				.removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
				.removeAttr( "role" )
				.removeAttr( "tabIndex" )
				.removeAttr( "aria-labelledby" )
				.removeAttr( "aria-expanded" )
				.removeAttr( "aria-hidden" )
				.removeAttr( "aria-disabled" )
				.removeUniqueId()
				.show();

		// Destroy menu items
		this.element.find( ".ui-menu-item" )
			.removeClass( "ui-menu-item" )
			.removeAttr( "role" )
			.removeAttr( "aria-disabled" )
			.children( "a" )
				.removeUniqueId()
				.removeClass( "ui-corner-all ui-state-hover" )
				.removeAttr( "tabIndex" )
				.removeAttr( "role" )
				.removeAttr( "aria-haspopup" )
				.children().each( function() {
					var elem = $( this );
					if ( elem.data( "ui-menu-submenu-carat" ) ) {
						elem.remove();
					}
				});

		// Destroy menu dividers
		this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
	},

	_keydown: function( event ) {
		var match, prev, character, skip, regex,
			preventDefault = true;

		function escape( value ) {
			return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
		}

		switch ( event.keyCode ) {
		case $.ui.keyCode.PAGE_UP:
			this.previousPage( event );
			break;
		case $.ui.keyCode.PAGE_DOWN:
			this.nextPage( event );
			break;
		case $.ui.keyCode.HOME:
			this._move( "first", "first", event );
			break;
		case $.ui.keyCode.END:
			this._move( "last", "last", event );
			break;
		case $.ui.keyCode.UP:
			this.previous( event );
			break;
		case $.ui.keyCode.DOWN:
			this.next( event );
			break;
		case $.ui.keyCode.LEFT:
			this.collapse( event );
			break;
		case $.ui.keyCode.RIGHT:
			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
				this.expand( event );
			}
			break;
		case $.ui.keyCode.ENTER:
		case $.ui.keyCode.SPACE:
			this._activate( event );
			break;
		case $.ui.keyCode.ESCAPE:
			this.collapse( event );
			break;
		default:
			preventDefault = false;
			prev = this.previousFilter || "";
			character = String.fromCharCode( event.keyCode );
			skip = false;

			clearTimeout( this.filterTimer );

			if ( character === prev ) {
				skip = true;
			} else {
				character = prev + character;
			}

			regex = new RegExp( "^" + escape( character ), "i" );
			match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
				return regex.test( $( this ).children( "a" ).text() );
			});
			match = skip && match.index( this.active.next() ) !== -1 ?
				this.active.nextAll( ".ui-menu-item" ) :
				match;

			// If no matches on the current filter, reset to the last character pressed
			// to move down the menu to the first item that starts with that character
			if ( !match.length ) {
				character = String.fromCharCode( event.keyCode );
				regex = new RegExp( "^" + escape( character ), "i" );
				match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
					return regex.test( $( this ).children( "a" ).text() );
				});
			}

			if ( match.length ) {
				this.focus( event, match );
				if ( match.length > 1 ) {
					this.previousFilter = character;
					this.filterTimer = this._delay(function() {
						delete this.previousFilter;
					}, 1000 );
				} else {
					delete this.previousFilter;
				}
			} else {
				delete this.previousFilter;
			}
		}

		if ( preventDefault ) {
			event.preventDefault();
		}
	},

	_activate: function( event ) {
		if ( !this.active.is( ".ui-state-disabled" ) ) {
			if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
				this.expand( event );
			} else {
				this.select( event );
			}
		}
	},

	refresh: function() {
		// Initialize nested menus
		var menus,
			icon = this.options.icons.submenu,
			submenus = this.element.find( this.options.menus + ":not(.ui-menu)" )
				.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
				.hide()
				.attr({
					role: this.options.role,
					"aria-hidden": "true",
					"aria-expanded": "false"
				});

		// Don't refresh list items that are already adapted
		menus = submenus.add( this.element );

		menus.children( ":not(.ui-menu-item):has(a)" )
			.addClass( "ui-menu-item" )
			.attr( "role", "presentation" )
			.children( "a" )
				.uniqueId()
				.addClass( "ui-corner-all" )
				.attr({
					tabIndex: -1,
					role: this._itemRole()
				});

		// Initialize unlinked menu-items containing spaces and/or dashes only as dividers
		menus.children( ":not(.ui-menu-item)" ).each(function() {
			var item = $( this );
			// hyphen, em dash, en dash
			if ( !/[^\-—–\s]/.test( item.text() ) ) {
				item.addClass( "ui-widget-content ui-menu-divider" );
			}
		});

		// Add aria-disabled attribute to any disabled menu item
		menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );

		submenus.each(function() {
			var menu = $( this ),
				item = menu.prev( "a" ),
				submenuCarat = $( "<span>" )
					.addClass( "ui-menu-icon ui-icon " + icon )
					.data( "ui-menu-submenu-carat", true );

			item
				.attr( "aria-haspopup", "true" )
				.prepend( submenuCarat );
			menu.attr( "aria-labelledby", item.attr( "id" ) );
		});

		// If the active item has been removed, blur the menu
		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
			this.blur();
		}
	},

	_itemRole: function() {
		return {
			menu: "menuitem",
			listbox: "option"
		}[ this.options.role ];
	},

	focus: function( event, item ) {
		var nested, focused;
		this.blur( event, event && event.type === "focus" );

		this._scrollIntoView( item );

		this.active = item.first();
		focused = this.active.children( "a" ).addClass( "ui-state-focus" );
		// Only update aria-activedescendant if there's a role
		// otherwise we assume focus is managed elsewhere
		if ( this.options.role ) {
			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
		}

		// Highlight active parent menu item, if any
		this.active
			.parent()
			.closest( ".ui-menu-item" )
			.children( "a:first" )
			.addClass( "ui-state-active" );

		if ( event && event.type === "keydown" ) {
			this._close();
		} else {
			this.timer = this._delay(function() {
				this._close();
			}, this.delay );
		}

		nested = item.children( ".ui-menu" );
		if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
			this._startOpening(nested);
		}
		this.activeMenu = item.parent();

		this._trigger( "focus", event, { item: item } );
	},

	_scrollIntoView: function( item ) {
		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
		if ( this._hasScroll() ) {
			borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
			paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
			scroll = this.activeMenu.scrollTop();
			elementHeight = this.activeMenu.height();
			itemHeight = item.height();

			if ( offset < 0 ) {
				this.activeMenu.scrollTop( scroll + offset );
			} else if ( offset + itemHeight > elementHeight ) {
				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
			}
		}
	},

	blur: function( event, fromFocus ) {
		if ( !fromFocus ) {
			clearTimeout( this.timer );
		}

		if ( !this.active ) {
			return;
		}

		this.active.children( "a" ).removeClass( "ui-state-focus" );
		this.active = null;

		this._trigger( "blur", event, { item: this.active } );
	},

	_startOpening: function( submenu ) {
		clearTimeout( this.timer );

		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
		// shift in the submenu position when mousing over the carat icon
		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
			return;
		}

		this.timer = this._delay(function() {
			this._close();
			this._open( submenu );
		}, this.delay );
	},

	_open: function( submenu ) {
		var position = $.extend({
			of: this.active
		}, this.options.position );

		clearTimeout( this.timer );
		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
			.hide()
			.attr( "aria-hidden", "true" );

		submenu
			.show()
			.removeAttr( "aria-hidden" )
			.attr( "aria-expanded", "true" )
			.position( position );
	},

	collapseAll: function( event, all ) {
		clearTimeout( this.timer );
		this.timer = this._delay(function() {
			// If we were passed an event, look for the submenu that contains the event
			var currentMenu = all ? this.element :
				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );

			// If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
			if ( !currentMenu.length ) {
				currentMenu = this.element;
			}

			this._close( currentMenu );

			this.blur( event );
			this.activeMenu = currentMenu;
		}, this.delay );
	},

	// With no arguments, closes the currently active menu - if nothing is active
	// it closes all menus.  If passed an argument, it will search for menus BELOW
	_close: function( startMenu ) {
		if ( !startMenu ) {
			startMenu = this.active ? this.active.parent() : this.element;
		}

		startMenu
			.find( ".ui-menu" )
				.hide()
				.attr( "aria-hidden", "true" )
				.attr( "aria-expanded", "false" )
			.end()
			.find( "a.ui-state-active" )
				.removeClass( "ui-state-active" );
	},

	collapse: function( event ) {
		var newItem = this.active &&
			this.active.parent().closest( ".ui-menu-item", this.element );
		if ( newItem && newItem.length ) {
			this._close();
			this.focus( event, newItem );
		}
	},

	expand: function( event ) {
		var newItem = this.active &&
			this.active
				.children( ".ui-menu " )
				.children( ".ui-menu-item" )
				.first();

		if ( newItem && newItem.length ) {
			this._open( newItem.parent() );

			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
			this._delay(function() {
				this.focus( event, newItem );
			});
		}
	},

	next: function( event ) {
		this._move( "next", "first", event );
	},

	previous: function( event ) {
		this._move( "prev", "last", event );
	},

	isFirstItem: function() {
		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
	},

	isLastItem: function() {
		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
	},

	_move: function( direction, filter, event ) {
		var next;
		if ( this.active ) {
			if ( direction === "first" || direction === "last" ) {
				next = this.active
					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
					.eq( -1 );
			} else {
				next = this.active
					[ direction + "All" ]( ".ui-menu-item" )
					.eq( 0 );
			}
		}
		if ( !next || !next.length || !this.active ) {
			next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
		}

		this.focus( event, next );
	},

	nextPage: function( event ) {
		var item, base, height;

		if ( !this.active ) {
			this.next( event );
			return;
		}
		if ( this.isLastItem() ) {
			return;
		}
		if ( this._hasScroll() ) {
			base = this.active.offset().top;
			height = this.element.height();
			this.active.nextAll( ".ui-menu-item" ).each(function() {
				item = $( this );
				return item.offset().top - base - height < 0;
			});

			this.focus( event, item );
		} else {
			this.focus( event, this.activeMenu.children( ".ui-menu-item" )
				[ !this.active ? "first" : "last" ]() );
		}
	},

	previousPage: function( event ) {
		var item, base, height;
		if ( !this.active ) {
			this.next( event );
			return;
		}
		if ( this.isFirstItem() ) {
			return;
		}
		if ( this._hasScroll() ) {
			base = this.active.offset().top;
			height = this.element.height();
			this.active.prevAll( ".ui-menu-item" ).each(function() {
				item = $( this );
				return item.offset().top - base + height > 0;
			});

			this.focus( event, item );
		} else {
			this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
		}
	},

	_hasScroll: function() {
		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
	},

	select: function( event ) {
		// TODO: It should never be possible to not have an active item at this
		// point, but the tests don't trigger mouseenter before click.
		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
		var ui = { item: this.active };
		if ( !this.active.has( ".ui-menu" ).length ) {
			this.collapseAll( event, true );
		}
		this._trigger( "select", event, ui );
	}
});

}( jQuery ));
(function( $, undefined ) {

$.widget( "ui.progressbar", {
	version: "1.9.0",
	options: {
		value: 0,
		max: 100
	},

	min: 0,

	_create: function() {
		this.element
			.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
			.attr({
				role: "progressbar",
				"aria-valuemin": this.min,
				"aria-valuemax": this.options.max,
				"aria-valuenow": this._value()
			});

		this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
			.appendTo( this.element );

		this.oldValue = this._value();
		this._refreshValue();
	},

	_destroy: function() {
		this.element
			.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
			.removeAttr( "role" )
			.removeAttr( "aria-valuemin" )
			.removeAttr( "aria-valuemax" )
			.removeAttr( "aria-valuenow" );

		this.valueDiv.remove();
	},

	value: function( newValue ) {
		if ( newValue === undefined ) {
			return this._value();
		}

		this._setOption( "value", newValue );
		return this;
	},

	_setOption: function( key, value ) {
		if ( key === "value" ) {
			this.options.value = value;
			this._refreshValue();
			if ( this._value() === this.options.max ) {
				this._trigger( "complete" );
			}
		}

		this._super( key, value );
	},

	_value: function() {
		var val = this.options.value;
		// normalize invalid value
		if ( typeof val !== "number" ) {
			val = 0;
		}
		return Math.min( this.options.max, Math.max( this.min, val ) );
	},

	_percentage: function() {
		return 100 * this._value() / this.options.max;
	},

	_refreshValue: function() {
		var value = this.value(),
			percentage = this._percentage();

		if ( this.oldValue !== value ) {
			this.oldValue = value;
			this._trigger( "change" );
		}

		this.valueDiv
			.toggle( value > this.min )
			.toggleClass( "ui-corner-right", value === this.options.max )
			.width( percentage.toFixed(0) + "%" );
		this.element.attr( "aria-valuenow", value );
	}
});

})( jQuery );
(function( $, undefined ) {

// number of pages in a slider
// (how many times can you page up/down to go through the whole range)
var numPages = 5;

$.widget( "ui.slider", $.ui.mouse, {
	version: "1.9.0",
	widgetEventPrefix: "slide",

	options: {
		animate: false,
		distance: 0,
		max: 100,
		min: 0,
		orientation: "horizontal",
		range: false,
		step: 1,
		value: 0,
		values: null
	},

	_create: function() {
		var i,
			o = this.options,
			existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
			handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
			handleCount = ( o.values && o.values.length ) || 1,
			handles = [];

		this._keySliding = false;
		this._mouseSliding = false;
		this._animateOff = true;
		this._handleIndex = null;
		this._detectOrientation();
		this._mouseInit();

		this.element
			.addClass( "ui-slider" +
				" ui-slider-" + this.orientation +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all" +
				( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );

		this.range = $([]);

		if ( o.range ) {
			if ( o.range === true ) {
				if ( !o.values ) {
					o.values = [ this._valueMin(), this._valueMin() ];
				}
				if ( o.values.length && o.values.length !== 2 ) {
					o.values = [ o.values[0], o.values[0] ];
				}
			}

			this.range = $( "<div></div>" )
				.appendTo( this.element )
				.addClass( "ui-slider-range" +
				// note: this isn't the most fittingly semantic framework class for this element,
				// but worked best visually with a variety of themes
				" ui-widget-header" +
				( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
		}

		for ( i = existingHandles.length; i < handleCount; i++ ) {
			handles.push( handle );
		}

		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );

		this.handle = this.handles.eq( 0 );

		this.handles.add( this.range ).filter( "a" )
			.click(function( event ) {
				event.preventDefault();
			})
			.mouseenter(function() {
				if ( !o.disabled ) {
					$( this ).addClass( "ui-state-hover" );
				}
			})
			.mouseleave(function() {
				$( this ).removeClass( "ui-state-hover" );
			})
			.focus(function() {
				if ( !o.disabled ) {
					$( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
					$( this ).addClass( "ui-state-focus" );
				} else {
					$( this ).blur();
				}
			})
			.blur(function() {
				$( this ).removeClass( "ui-state-focus" );
			});

		this.handles.each(function( i ) {
			$( this ).data( "ui-slider-handle-index", i );
		});

		this._on( this.handles, {
			keydown: function( event ) {
				var allowed, curVal, newVal, step,
					index = $( event.target ).data( "ui-slider-handle-index" );

				switch ( event.keyCode ) {
					case $.ui.keyCode.HOME:
					case $.ui.keyCode.END:
					case $.ui.keyCode.PAGE_UP:
					case $.ui.keyCode.PAGE_DOWN:
					case $.ui.keyCode.UP:
					case $.ui.keyCode.RIGHT:
					case $.ui.keyCode.DOWN:
					case $.ui.keyCode.LEFT:
						event.preventDefault();
						if ( !this._keySliding ) {
							this._keySliding = true;
							$( event.target ).addClass( "ui-state-active" );
							allowed = this._start( event, index );
							if ( allowed === false ) {
								return;
							}
						}
						break;
				}

				step = this.options.step;
				if ( this.options.values && this.options.values.length ) {
					curVal = newVal = this.values( index );
				} else {
					curVal = newVal = this.value();
				}

				switch ( event.keyCode ) {
					case $.ui.keyCode.HOME:
						newVal = this._valueMin();
						break;
					case $.ui.keyCode.END:
						newVal = this._valueMax();
						break;
					case $.ui.keyCode.PAGE_UP:
						newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
						break;
					case $.ui.keyCode.PAGE_DOWN:
						newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
						break;
					case $.ui.keyCode.UP:
					case $.ui.keyCode.RIGHT:
						if ( curVal === this._valueMax() ) {
							return;
						}
						newVal = this._trimAlignValue( curVal + step );
						break;
					case $.ui.keyCode.DOWN:
					case $.ui.keyCode.LEFT:
						if ( curVal === this._valueMin() ) {
							return;
						}
						newVal = this._trimAlignValue( curVal - step );
						break;
				}

				this._slide( event, index, newVal );
			},
			keyup: function( event ) {
				var index = $( event.target ).data( "ui-slider-handle-index" );

				if ( this._keySliding ) {
					this._keySliding = false;
					this._stop( event, index );
					this._change( event, index );
					$( event.target ).removeClass( "ui-state-active" );
				}
			}
		});

		this._refreshValue();

		this._animateOff = false;
	},

	_destroy: function() {
		this.handles.remove();
		this.range.remove();

		this.element
			.removeClass( "ui-slider" +
				" ui-slider-horizontal" +
				" ui-slider-vertical" +
				" ui-slider-disabled" +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all" );

		this._mouseDestroy();
	},

	_mouseCapture: function( event ) {
		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
			that = this,
			o = this.options;

		if ( o.disabled ) {
			return false;
		}

		this.elementSize = {
			width: this.element.outerWidth(),
			height: this.element.outerHeight()
		};
		this.elementOffset = this.element.offset();

		position = { x: event.pageX, y: event.pageY };
		normValue = this._normValueFromMouse( position );
		distance = this._valueMax() - this._valueMin() + 1;
		this.handles.each(function( i ) {
			var thisDistance = Math.abs( normValue - that.values(i) );
			if ( distance > thisDistance ) {
				distance = thisDistance;
				closestHandle = $( this );
				index = i;
			}
		});

		// workaround for bug #3736 (if both handles of a range are at 0,
		// the first is always used as the one with least distance,
		// and moving it is obviously prevented by preventing negative ranges)
		if( o.range === true && this.values(1) === o.min ) {
			index += 1;
			closestHandle = $( this.handles[index] );
		}

		allowed = this._start( event, index );
		if ( allowed === false ) {
			return false;
		}
		this._mouseSliding = true;

		this._handleIndex = index;

		closestHandle
			.addClass( "ui-state-active" )
			.focus();

		offset = closestHandle.offset();
		mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
			top: event.pageY - offset.top -
				( closestHandle.height() / 2 ) -
				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
		};

		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
			this._slide( event, index, normValue );
		}
		this._animateOff = true;
		return true;
	},

	_mouseStart: function( event ) {
		return true;
	},

	_mouseDrag: function( event ) {
		var position = { x: event.pageX, y: event.pageY },
			normValue = this._normValueFromMouse( position );

		this._slide( event, this._handleIndex, normValue );

		return false;
	},

	_mouseStop: function( event ) {
		this.handles.removeClass( "ui-state-active" );
		this._mouseSliding = false;

		this._stop( event, this._handleIndex );
		this._change( event, this._handleIndex );

		this._handleIndex = null;
		this._clickOffset = null;
		this._animateOff = false;

		return false;
	},

	_detectOrientation: function() {
		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
	},

	_normValueFromMouse: function( position ) {
		var pixelTotal,
			pixelMouse,
			percentMouse,
			valueTotal,
			valueMouse;

		if ( this.orientation === "horizontal" ) {
			pixelTotal = this.elementSize.width;
			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
		} else {
			pixelTotal = this.elementSize.height;
			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
		}

		percentMouse = ( pixelMouse / pixelTotal );
		if ( percentMouse > 1 ) {
			percentMouse = 1;
		}
		if ( percentMouse < 0 ) {
			percentMouse = 0;
		}
		if ( this.orientation === "vertical" ) {
			percentMouse = 1 - percentMouse;
		}

		valueTotal = this._valueMax() - this._valueMin();
		valueMouse = this._valueMin() + percentMouse * valueTotal;

		return this._trimAlignValue( valueMouse );
	},

	_start: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}
		return this._trigger( "start", event, uiHash );
	},

	_slide: function( event, index, newVal ) {
		var otherVal,
			newValues,
			allowed;

		if ( this.options.values && this.options.values.length ) {
			otherVal = this.values( index ? 0 : 1 );

			if ( ( this.options.values.length === 2 && this.options.range === true ) &&
					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
				) {
				newVal = otherVal;
			}

			if ( newVal !== this.values( index ) ) {
				newValues = this.values();
				newValues[ index ] = newVal;
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal,
					values: newValues
				} );
				otherVal = this.values( index ? 0 : 1 );
				if ( allowed !== false ) {
					this.values( index, newVal, true );
				}
			}
		} else {
			if ( newVal !== this.value() ) {
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal
				} );
				if ( allowed !== false ) {
					this.value( newVal );
				}
			}
		}
	},

	_stop: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}

		this._trigger( "stop", event, uiHash );
	},

	_change: function( event, index ) {
		if ( !this._keySliding && !this._mouseSliding ) {
			var uiHash = {
				handle: this.handles[ index ],
				value: this.value()
			};
			if ( this.options.values && this.options.values.length ) {
				uiHash.value = this.values( index );
				uiHash.values = this.values();
			}

			this._trigger( "change", event, uiHash );
		}
	},

	value: function( newValue ) {
		if ( arguments.length ) {
			this.options.value = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, 0 );
			return;
		}

		return this._value();
	},

	values: function( index, newValue ) {
		var vals,
			newValues,
			i;

		if ( arguments.length > 1 ) {
			this.options.values[ index ] = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, index );
			return;
		}

		if ( arguments.length ) {
			if ( $.isArray( arguments[ 0 ] ) ) {
				vals = this.options.values;
				newValues = arguments[ 0 ];
				for ( i = 0; i < vals.length; i += 1 ) {
					vals[ i ] = this._trimAlignValue( newValues[ i ] );
					this._change( null, i );
				}
				this._refreshValue();
			} else {
				if ( this.options.values && this.options.values.length ) {
					return this._values( index );
				} else {
					return this.value();
				}
			}
		} else {
			return this._values();
		}
	},

	_setOption: function( key, value ) {
		var i,
			valsLength = 0;

		if ( $.isArray( this.options.values ) ) {
			valsLength = this.options.values.length;
		}

		$.Widget.prototype._setOption.apply( this, arguments );

		switch ( key ) {
			case "disabled":
				if ( value ) {
					this.handles.filter( ".ui-state-focus" ).blur();
					this.handles.removeClass( "ui-state-hover" );
					this.handles.prop( "disabled", true );
					this.element.addClass( "ui-disabled" );
				} else {
					this.handles.prop( "disabled", false );
					this.element.removeClass( "ui-disabled" );
				}
				break;
			case "orientation":
				this._detectOrientation();
				this.element
					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
					.addClass( "ui-slider-" + this.orientation );
				this._refreshValue();
				break;
			case "value":
				this._animateOff = true;
				this._refreshValue();
				this._change( null, 0 );
				this._animateOff = false;
				break;
			case "values":
				this._animateOff = true;
				this._refreshValue();
				for ( i = 0; i < valsLength; i += 1 ) {
					this._change( null, i );
				}
				this._animateOff = false;
				break;
		}
	},

	//internal value getter
	// _value() returns value trimmed by min and max, aligned by step
	_value: function() {
		var val = this.options.value;
		val = this._trimAlignValue( val );

		return val;
	},

	//internal values getter
	// _values() returns array of values trimmed by min and max, aligned by step
	// _values( index ) returns single value trimmed by min and max, aligned by step
	_values: function( index ) {
		var val,
			vals,
			i;

		if ( arguments.length ) {
			val = this.options.values[ index ];
			val = this._trimAlignValue( val );

			return val;
		} else {
			// .slice() creates a copy of the array
			// this copy gets trimmed by min and max and then returned
			vals = this.options.values.slice();
			for ( i = 0; i < vals.length; i+= 1) {
				vals[ i ] = this._trimAlignValue( vals[ i ] );
			}

			return vals;
		}
	},

	// returns the step-aligned value that val is closest to, between (inclusive) min and max
	_trimAlignValue: function( val ) {
		if ( val <= this._valueMin() ) {
			return this._valueMin();
		}
		if ( val >= this._valueMax() ) {
			return this._valueMax();
		}
		var step = ( this.options.step > 0 ) ? this.options.step : 1,
			valModStep = (val - this._valueMin()) % step,
			alignValue = val - valModStep;

		if ( Math.abs(valModStep) * 2 >= step ) {
			alignValue += ( valModStep > 0 ) ? step : ( -step );
		}

		// Since JavaScript has problems with large floats, round
		// the final value to 5 digits after the decimal point (see #4124)
		return parseFloat( alignValue.toFixed(5) );
	},

	_valueMin: function() {
		return this.options.min;
	},

	_valueMax: function() {
		return this.options.max;
	},

	_refreshValue: function() {
		var lastValPercent, valPercent, value, valueMin, valueMax,
			oRange = this.options.range,
			o = this.options,
			that = this,
			animate = ( !this._animateOff ) ? o.animate : false,
			_set = {};

		if ( this.options.values && this.options.values.length ) {
			this.handles.each(function( i, j ) {
				valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
				if ( that.options.range === true ) {
					if ( that.orientation === "horizontal" ) {
						if ( i === 0 ) {
							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
						}
						if ( i === 1 ) {
							that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					} else {
						if ( i === 0 ) {
							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
						}
						if ( i === 1 ) {
							that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					}
				}
				lastValPercent = valPercent;
			});
		} else {
			value = this.value();
			valueMin = this._valueMin();
			valueMax = this._valueMax();
			valPercent = ( valueMax !== valueMin ) ?
					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
					0;
			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );

			if ( oRange === "min" && this.orientation === "horizontal" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "horizontal" ) {
				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
			if ( oRange === "min" && this.orientation === "vertical" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "vertical" ) {
				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
		}
	}

});

}(jQuery));
(function( $ ) {

function modifier( fn ) {
	return function() {
		var previous = this.element.val();
		fn.apply( this, arguments );
		this._refresh();
		if ( previous !== this.element.val() ) {
			this._trigger( "change" );
		}
	};
}

$.widget( "ui.spinner", {
	version: "1.9.0",
	defaultElement: "<input>",
	widgetEventPrefix: "spin",
	options: {
		culture: null,
		icons: {
			down: "ui-icon-triangle-1-s",
			up: "ui-icon-triangle-1-n"
		},
		incremental: true,
		max: null,
		min: null,
		numberFormat: null,
		page: 10,
		step: 1,

		change: null,
		spin: null,
		start: null,
		stop: null
	},

	_create: function() {
		// handle string values that need to be parsed
		this._setOption( "max", this.options.max );
		this._setOption( "min", this.options.min );
		this._setOption( "step", this.options.step );

		// format the value, but don't constrain
		this._value( this.element.val(), true );

		this._draw();
		this._on( this._events );
		this._refresh();

		// turning off autocomplete prevents the browser from remembering the
		// value when navigating through history, so we re-enable autocomplete
		// if the page is unloaded before the widget is destroyed. #7790
		this._on( this.window, {
			beforeunload: function() {
				this.element.removeAttr( "autocomplete" );
			}
		});
	},

	_getCreateOptions: function() {
		var options = {},
			element = this.element;

		$.each( [ "min", "max", "step" ], function( i, option ) {
			var value = element.attr( option );
			if ( value !== undefined && value.length ) {
				options[ option ] = value;
			}
		});

		return options;
	},

	_events: {
		keydown: function( event ) {
			if ( this._start( event ) && this._keydown( event ) ) {
				event.preventDefault();
			}
		},
		keyup: "_stop",
		focus: function() {
			this.uiSpinner.addClass( "ui-state-active" );
			this.previous = this.element.val();
		},
		blur: function( event ) {
			if ( this.cancelBlur ) {
				delete this.cancelBlur;
				return;
			}

			this._refresh();
			this.uiSpinner.removeClass( "ui-state-active" );
			if ( this.previous !== this.element.val() ) {
				this._trigger( "change", event );
			}
		},
		mousewheel: function( event, delta ) {
			if ( !delta ) {
				return;
			}
			if ( !this.spinning && !this._start( event ) ) {
				return false;
			}

			this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
			clearTimeout( this.mousewheelTimer );
			this.mousewheelTimer = this._delay(function() {
				if ( this.spinning ) {
					this._stop( event );
				}
			}, 100 );
			event.preventDefault();
		},
		"mousedown .ui-spinner-button": function( event ) {
			var previous;

			// We never want the buttons to have focus; whenever the user is
			// interacting with the spinner, the focus should be on the input.
			// If the input is focused then this.previous is properly set from
			// when the input first received focus. If the input is not focused
			// then we need to set this.previous based on the value before spinning.
			previous = this.element[0] === this.document[0].activeElement ?
				this.previous : this.element.val();
			function checkFocus() {
				var isActive = this.element[0] === this.document[0].activeElement;
				if ( !isActive ) {
					this.element.focus();
					this.previous = previous;
					// support: IE
					// IE sets focus asynchronously, so we need to check if focus
					// moved off of the input because the user clicked on the button.
					this._delay(function() {
						this.previous = previous;
					});
				}
			}

			// ensure focus is on (or stays on) the text field
			event.preventDefault();
			checkFocus.call( this );

			// support: IE
			// IE doesn't prevent moving focus even with event.preventDefault()
			// so we set a flag to know when we should ignore the blur event
			// and check (again) if focus moved off of the input.
			this.cancelBlur = true;
			this._delay(function() {
				delete this.cancelBlur;
				checkFocus.call( this );
			});

			if ( this._start( event ) === false ) {
				return;
			}

			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
		},
		"mouseup .ui-spinner-button": "_stop",
		"mouseenter .ui-spinner-button": function( event ) {
			// button will add ui-state-active if mouse was down while mouseleave and kept down
			if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
				return;
			}

			if ( this._start( event ) === false ) {
				return false;
			}
			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
		},
		// TODO: do we really want to consider this a stop?
		// shouldn't we just stop the repeater and wait until mouseup before
		// we trigger the stop event?
		"mouseleave .ui-spinner-button": "_stop"
	},

	_draw: function() {
		var uiSpinner = this.uiSpinner = this.element
			.addClass( "ui-spinner-input" )
			.attr( "autocomplete", "off" )
			.wrap( this._uiSpinnerHtml() )
			.parent()
				// add buttons
				.append( this._buttonHtml() );
		this._hoverable( uiSpinner );

		this.element.attr( "role", "spinbutton" );

		// button bindings
		this.buttons = uiSpinner.find( ".ui-spinner-button" )
			.attr( "tabIndex", -1 )
			.button()
			.removeClass( "ui-corner-all" );

		// IE 6 doesn't understand height: 50% for the buttons
		// unless the wrapper has an explicit height
		if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
				uiSpinner.height() > 0 ) {
			uiSpinner.height( uiSpinner.height() );
		}

		// disable spinner if element was already disabled
		if ( this.options.disabled ) {
			this.disable();
		}
	},

	_keydown: function( event ) {
		var options = this.options,
			keyCode = $.ui.keyCode;

		switch ( event.keyCode ) {
		case keyCode.UP:
			this._repeat( null, 1, event );
			return true;
		case keyCode.DOWN:
			this._repeat( null, -1, event );
			return true;
		case keyCode.PAGE_UP:
			this._repeat( null, options.page, event );
			return true;
		case keyCode.PAGE_DOWN:
			this._repeat( null, -options.page, event );
			return true;
		}

		return false;
	},

	_uiSpinnerHtml: function() {
		return "<span class='ui-spinner ui-state-default ui-widget ui-widget-content ui-corner-all'></span>";
	},

	_buttonHtml: function() {
		return "" +
			"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
				"<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
			"</a>" +
			"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
				"<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
			"</a>";
	},

	_start: function( event ) {
		if ( !this.spinning && this._trigger( "start", event ) === false ) {
			return false;
		}

		if ( !this.counter ) {
			this.counter = 1;
		}
		this.spinning = true;
		return true;
	},

	_repeat: function( i, steps, event ) {
		i = i || 500;

		clearTimeout( this.timer );
		this.timer = this._delay(function() {
			this._repeat( 40, steps, event );
		}, i );

		this._spin( steps * this.options.step, event );
	},

	_spin: function( step, event ) {
		var value = this.value() || 0;

		if ( !this.counter ) {
			this.counter = 1;
		}

		value = this._adjustValue( value + step * this._increment( this.counter ) );

		if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
			this._value( value );
			this.counter++;
		}
	},

	_increment: function( i ) {
		var incremental = this.options.incremental;

		if ( incremental ) {
			return $.isFunction( incremental ) ?
				incremental( i ) :
				Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 );
		}

		return 1;
	},

	_precision: function() {
		var precision = this._precisionOf( this.options.step );
		if ( this.options.min !== null ) {
			precision = Math.max( precision, this._precisionOf( this.options.min ) );
		}
		return precision;
	},

	_precisionOf: function( num ) {
		var str = num.toString(),
			decimal = str.indexOf( "." );
		return decimal === -1 ? 0 : str.length - decimal - 1;
	},

	_adjustValue: function( value ) {
		var base, aboveMin,
			options = this.options;

		// make sure we're at a valid step
		// - find out where we are relative to the base (min or 0)
		base = options.min !== null ? options.min : 0;
		aboveMin = value - base;
		// - round to the nearest step
		aboveMin = Math.round(aboveMin / options.step) * options.step;
		// - rounding is based on 0, so adjust back to our base
		value = base + aboveMin;

		// fix precision from bad JS floating point math
		value = parseFloat( value.toFixed( this._precision() ) );

		// clamp the value
		if ( options.max !== null && value > options.max) {
			return options.max;
		}
		if ( options.min !== null && value < options.min ) {
			return options.min;
		}

		return value;
	},

	_stop: function( event ) {
		if ( !this.spinning ) {
			return;
		}

		clearTimeout( this.timer );
		clearTimeout( this.mousewheelTimer );
		this.counter = 0;
		this.spinning = false;
		this._trigger( "stop", event );
	},

	_setOption: function( key, value ) {
		if ( key === "culture" || key === "numberFormat" ) {
			var prevValue = this._parse( this.element.val() );
			this.options[ key ] = value;
			this.element.val( this._format( prevValue ) );
			return;
		}

		if ( key === "max" || key === "min" || key === "step" ) {
			if ( typeof value === "string" ) {
				value = this._parse( value );
			}
		}

		this._super( key, value );

		if ( key === "disabled" ) {
			if ( value ) {
				this.element.prop( "disabled", true );
				this.buttons.button( "disable" );
			} else {
				this.element.prop( "disabled", false );
				this.buttons.button( "enable" );
			}
		}
	},

	_setOptions: modifier(function( options ) {
		this._super( options );
		this._value( this.element.val() );
	}),

	_parse: function( val ) {
		if ( typeof val === "string" && val !== "" ) {
			val = window.Globalize && this.options.numberFormat ?
				Globalize.parseFloat( val, 10, this.options.culture ) : +val;
		}
		return val === "" || isNaN( val ) ? null : val;
	},

	_format: function( value ) {
		if ( value === "" ) {
			return "";
		}
		return window.Globalize && this.options.numberFormat ?
			Globalize.format( value, this.options.numberFormat, this.options.culture ) :
			value;
	},

	_refresh: function() {
		this.element.attr({
			"aria-valuemin": this.options.min,
			"aria-valuemax": this.options.max,
			// TODO: what should we do with values that can't be parsed?
			"aria-valuenow": this._parse( this.element.val() )
		});
	},

	// update the value without triggering change
	_value: function( value, allowAny ) {
		var parsed;
		if ( value !== "" ) {
			parsed = this._parse( value );
			if ( parsed !== null ) {
				if ( !allowAny ) {
					parsed = this._adjustValue( parsed );
				}
				value = this._format( parsed );
			}
		}
		this.element.val( value );
		this._refresh();
	},

	_destroy: function() {
		this.element
			.removeClass( "ui-spinner-input" )
			.prop( "disabled", false )
			.removeAttr( "autocomplete" )
			.removeAttr( "role" )
			.removeAttr( "aria-valuemin" )
			.removeAttr( "aria-valuemax" )
			.removeAttr( "aria-valuenow" );
		this.uiSpinner.replaceWith( this.element );
	},

	stepUp: modifier(function( steps ) {
		this._stepUp( steps );
	}),
	_stepUp: function( steps ) {
		this._spin( (steps || 1) * this.options.step );
	},

	stepDown: modifier(function( steps ) {
		this._stepDown( steps );
	}),
	_stepDown: function( steps ) {
		this._spin( (steps || 1) * -this.options.step );
	},

	pageUp: modifier(function( pages ) {
		this._stepUp( (pages || 1) * this.options.page );
	}),

	pageDown: modifier(function( pages ) {
		this._stepDown( (pages || 1) * this.options.page );
	}),

	value: function( newVal ) {
		if ( !arguments.length ) {
			return this._parse( this.element.val() );
		}
		modifier( this._value ).call( this, newVal );
	},

	widget: function() {
		return this.uiSpinner;
	}
});

}( jQuery ) );
(function( $, undefined ) {

var tabId = 0,
	rhash = /#.*$/;

function getNextTabId() {
	return ++tabId;
}

function isLocal( anchor ) {
	// clone the node to work around IE 6 not normalizing the href property
	// if it's manually set, i.e., a.href = "#foo" kills the normalization
	anchor = anchor.cloneNode( false );
	return anchor.hash.length > 1 &&
		anchor.href.replace( rhash, "" ) === location.href.replace( rhash, "" );
}

$.widget( "ui.tabs", {
	version: "1.9.0",
	delay: 300,
	options: {
		active: null,
		collapsible: false,
		event: "click",
		heightStyle: "content",
		hide: null,
		show: null,

		// callbacks
		activate: null,
		beforeActivate: null,
		beforeLoad: null,
		load: null
	},

	_create: function() {
		var panel,
			that = this,
			options = this.options,
			active = options.active;

		this.running = false;

		this.element
			.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
			.toggleClass( "ui-tabs-collapsible", options.collapsible )
			// Prevent users from focusing disabled tabs via click
			.delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
				if ( $( this ).is( ".ui-state-disabled" ) ) {
					event.preventDefault();
				}
			})
			// support: IE <9
			// Preventing the default action in mousedown doesn't prevent IE
			// from focusing the element, so if the anchor gets focused, blur.
			// We don't have to worry about focusing the previously focused
			// element since clicking on a non-focusable element should focus
			// the body anyway.
			.delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
				if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
					this.blur();
				}
			});

		this._processTabs();

		if ( active === null ) {
			// check the fragment identifier in the URL
			if ( location.hash ) {
				this.anchors.each(function( i, anchor ) {
					if ( anchor.hash === location.hash ) {
						active = i;
						return false;
					}
				});
			}

			// check for a tab marked active via a class
			if ( active === null ) {
				active = this.tabs.filter( ".ui-tabs-active" ).index();
			}

			// no active tab, set to false
			if ( active === null || active === -1 ) {
				active = this.tabs.length ? 0 : false;
			}
		}

		// handle numbers: negative, out of range
		if ( active !== false ) {
			active = this.tabs.index( this.tabs.eq( active ) );
			if ( active === -1 ) {
				active = options.collapsible ? false : 0;
			}
		}
		options.active = active;

		// don't allow collapsible: false and active: false
		if ( !options.collapsible && options.active === false && this.anchors.length ) {
			options.active = 0;
		}

		// Take disabling tabs via class attribute from HTML
		// into account and update option properly.
		if ( $.isArray( options.disabled ) ) {
			options.disabled = $.unique( options.disabled.concat(
				$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
					return that.tabs.index( li );
				})
			) ).sort();
		}

		// check for length avoids error when initializing empty list
		if ( this.options.active !== false && this.anchors.length ) {
			this.active = this._findActive( this.options.active );
		} else {
			this.active = $();
		}

		this._refresh();

		if ( this.active.length ) {
			this.load( options.active );
		}
	},

	_getCreateEventData: function() {
		return {
			tab: this.active,
			panel: !this.active.length ? $() : this._getPanelForTab( this.active )
		};
	},

	_tabKeydown: function( event ) {
		var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
			selectedIndex = this.tabs.index( focusedTab ),
			goingForward = true;

		if ( this._handlePageNav( event ) ) {
			return;
		}

		switch ( event.keyCode ) {
			case $.ui.keyCode.RIGHT:
			case $.ui.keyCode.DOWN:
				selectedIndex++;
				break;
			case $.ui.keyCode.UP:
			case $.ui.keyCode.LEFT:
				goingForward = false;
				selectedIndex--;
				break;
			case $.ui.keyCode.END:
				selectedIndex = this.anchors.length - 1;
				break;
			case $.ui.keyCode.HOME:
				selectedIndex = 0;
				break;
			case $.ui.keyCode.SPACE:
				// Activate only, no collapsing
				event.preventDefault();
				clearTimeout( this.activating );
				this._activate( selectedIndex );
				return;
			case $.ui.keyCode.ENTER:
				// Toggle (cancel delayed activation, allow collapsing)
				event.preventDefault();
				clearTimeout( this.activating );
				// Determine if we should collapse or activate
				this._activate( selectedIndex === this.options.active ? false : selectedIndex );
				return;
			default:
				return;
		}

		// Focus the appropriate tab, based on which key was pressed
		event.preventDefault();
		clearTimeout( this.activating );
		selectedIndex = this._focusNextTab( selectedIndex, goingForward );

		// Navigating with control key will prevent automatic activation
		if ( !event.ctrlKey ) {
			// Update aria-selected immediately so that AT think the tab is already selected.
			// Otherwise AT may confuse the user by stating that they need to activate the tab,
			// but the tab will already be activated by the time the announcement finishes.
			focusedTab.attr( "aria-selected", "false" );
			this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );

			this.activating = this._delay(function() {
				this.option( "active", selectedIndex );
			}, this.delay );
		}
	},

	_panelKeydown: function( event ) {
		if ( this._handlePageNav( event ) ) {
			return;
		}

		// Ctrl+up moves focus to the current tab
		if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
			event.preventDefault();
			this.active.focus();
		}
	},

	// Alt+page up/down moves focus to the previous/next tab (and activates)
	_handlePageNav: function( event ) {
		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
			this._activate( this._focusNextTab( this.options.active - 1, false ) );
			return true;
		}
		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
			this._activate( this._focusNextTab( this.options.active + 1, true ) );
			return true;
		}
	},

	_findNextTab: function( index, goingForward ) {
		var lastTabIndex = this.tabs.length - 1;

		function constrain() {
			if ( index > lastTabIndex ) {
				index = 0;
			}
			if ( index < 0 ) {
				index = lastTabIndex;
			}
			return index;
		}

		while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
			index = goingForward ? index + 1 : index - 1;
		}

		return index;
	},

	_focusNextTab: function( index, goingForward ) {
		index = this._findNextTab( index, goingForward );
		this.tabs.eq( index ).focus();
		return index;
	},

	_setOption: function( key, value ) {
		if ( key === "active" ) {
			// _activate() will handle invalid values and update this.options
			this._activate( value );
			return;
		}

		if ( key === "disabled" ) {
			// don't use the widget factory's disabled handling
			this._setupDisabled( value );
			return;
		}

		this._super( key, value);

		if ( key === "collapsible" ) {
			this.element.toggleClass( "ui-tabs-collapsible", value );
			// Setting collapsible: false while collapsed; open first panel
			if ( !value && this.options.active === false ) {
				this._activate( 0 );
			}
		}

		if ( key === "event" ) {
			this._setupEvents( value );
		}

		if ( key === "heightStyle" ) {
			this._setupHeightStyle( value );
		}
	},

	_tabId: function( tab ) {
		return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
	},

	_sanitizeSelector: function( hash ) {
		return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
	},

	refresh: function() {
		var next,
			options = this.options,
			lis = this.tablist.children( ":has(a[href])" );

		// get disabled tabs from class attribute from HTML
		// this will get converted to a boolean if needed in _refresh()
		options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
			return lis.index( tab );
		});

		this._processTabs();

		// was collapsed or no tabs
		if ( options.active === false || !this.anchors.length ) {
			options.active = false;
			this.active = $();
		// was active, but active tab is gone
		} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
			// all remaining tabs are disabled
			if ( this.tabs.length === options.disabled.length ) {
				options.active = false;
				this.active = $();
			// activate previous tab
			} else {
				this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
			}
		// was active, active tab still exists
		} else {
			// make sure active index is correct
			options.active = this.tabs.index( this.active );
		}

		this._refresh();
	},

	_refresh: function() {
		this._setupDisabled( this.options.disabled );
		this._setupEvents( this.options.event );
		this._setupHeightStyle( this.options.heightStyle );

		this.tabs.not( this.active ).attr({
			"aria-selected": "false",
			tabIndex: -1
		});
		this.panels.not( this._getPanelForTab( this.active ) )
			.hide()
			.attr({
				"aria-expanded": "false",
				"aria-hidden": "true"
			});

		// Make sure one tab is in the tab order
		if ( !this.active.length ) {
			this.tabs.eq( 0 ).attr( "tabIndex", 0 );
		} else {
			this.active
				.addClass( "ui-tabs-active ui-state-active" )
				.attr({
					"aria-selected": "true",
					tabIndex: 0
				});
			this._getPanelForTab( this.active )
				.show()
				.attr({
					"aria-expanded": "true",
					"aria-hidden": "false"
				});
		}
	},

	_processTabs: function() {
		var that = this;

		this.tablist = this._getList()
			.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
			.attr( "role", "tablist" );

		this.tabs = this.tablist.find( "> li:has(a[href])" )
			.addClass( "ui-state-default ui-corner-top" )
			.attr({
				role: "tab",
				tabIndex: -1
			});

		this.anchors = this.tabs.map(function() {
				return $( "a", this )[ 0 ];
			})
			.addClass( "ui-tabs-anchor" )
			.attr({
				role: "presentation",
				tabIndex: -1
			});

		this.panels = $();

		this.anchors.each(function( i, anchor ) {
			var selector, panel, panelId,
				anchorId = $( anchor ).uniqueId().attr( "id" ),
				tab = $( anchor ).closest( "li" ),
				originalAriaControls = tab.attr( "aria-controls" );

			// inline tab
			if ( isLocal( anchor ) ) {
				selector = anchor.hash;
				panel = that.element.find( that._sanitizeSelector( selector ) );
			// remote tab
			} else {
				panelId = that._tabId( tab );
				selector = "#" + panelId;
				panel = that.element.find( selector );
				if ( !panel.length ) {
					panel = that._createPanel( panelId );
					panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
				}
				panel.attr( "aria-live", "polite" );
			}

			if ( panel.length) {
				that.panels = that.panels.add( panel );
			}
			if ( originalAriaControls ) {
				tab.data( "ui-tabs-aria-controls", originalAriaControls );
			}
			tab.attr({
				"aria-controls": selector.substring( 1 ),
				"aria-labelledby": anchorId
			});
			panel.attr( "aria-labelledby", anchorId );
		});

		this.panels
			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
			.attr( "role", "tabpanel" );
	},

	// allow overriding how to find the list for rare usage scenarios (#7715)
	_getList: function() {
		return this.element.find( "ol,ul" ).eq( 0 );
	},

	_createPanel: function( id ) {
		return $( "<div>" )
			.attr( "id", id )
			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
			.data( "ui-tabs-destroy", true );
	},

	_setupDisabled: function( disabled ) {
		if ( $.isArray( disabled ) ) {
			if ( !disabled.length ) {
				disabled = false;
			} else if ( disabled.length === this.anchors.length ) {
				disabled = true;
			}
		}

		// disable tabs
		for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
			if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
				$( li )
					.addClass( "ui-state-disabled" )
					.attr( "aria-disabled", "true" );
			} else {
				$( li )
					.removeClass( "ui-state-disabled" )
					.removeAttr( "aria-disabled" );
			}
		}

		this.options.disabled = disabled;
	},

	_setupEvents: function( event ) {
		var events = {
			click: function( event ) {
				event.preventDefault();
			}
		};
		if ( event ) {
			$.each( event.split(" "), function( index, eventName ) {
				events[ eventName ] = "_eventHandler";
			});
		}

		this._off( this.anchors.add( this.tabs ).add( this.panels ) );
		this._on( this.anchors, events );
		this._on( this.tabs, { keydown: "_tabKeydown" } );
		this._on( this.panels, { keydown: "_panelKeydown" } );

		this._focusable( this.tabs );
		this._hoverable( this.tabs );
	},

	_setupHeightStyle: function( heightStyle ) {
		var maxHeight, overflow,
			parent = this.element.parent();

		if ( heightStyle === "fill" ) {
			// IE 6 treats height like minHeight, so we need to turn off overflow
			// in order to get a reliable height
			// we use the minHeight support test because we assume that only
			// browsers that don't support minHeight will treat height as minHeight
			if ( !$.support.minHeight ) {
				overflow = parent.css( "overflow" );
				parent.css( "overflow", "hidden");
			}
			maxHeight = parent.height();
			this.element.siblings( ":visible" ).each(function() {
				var elem = $( this ),
					position = elem.css( "position" );

				if ( position === "absolute" || position === "fixed" ) {
					return;
				}
				maxHeight -= elem.outerHeight( true );
			});
			if ( overflow ) {
				parent.css( "overflow", overflow );
			}

			this.element.children().not( this.panels ).each(function() {
				maxHeight -= $( this ).outerHeight( true );
			});

			this.panels.each(function() {
				$( this ).height( Math.max( 0, maxHeight -
					$( this ).innerHeight() + $( this ).height() ) );
			})
			.css( "overflow", "auto" );
		} else if ( heightStyle === "auto" ) {
			maxHeight = 0;
			this.panels.each(function() {
				maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
			}).height( maxHeight );
		}
	},

	_eventHandler: function( event ) {
		var options = this.options,
			active = this.active,
			anchor = $( event.currentTarget ),
			tab = anchor.closest( "li" ),
			clickedIsActive = tab[ 0 ] === active[ 0 ],
			collapsing = clickedIsActive && options.collapsible,
			toShow = collapsing ? $() : this._getPanelForTab( tab ),
			toHide = !active.length ? $() : this._getPanelForTab( active ),
			eventData = {
				oldTab: active,
				oldPanel: toHide,
				newTab: collapsing ? $() : tab,
				newPanel: toShow
			};

		event.preventDefault();

		if ( tab.hasClass( "ui-state-disabled" ) ||
				// tab is already loading
				tab.hasClass( "ui-tabs-loading" ) ||
				// can't switch durning an animation
				this.running ||
				// click on active header, but not collapsible
				( clickedIsActive && !options.collapsible ) ||
				// allow canceling activation
				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
			return;
		}

		options.active = collapsing ? false : this.tabs.index( tab );

		this.active = clickedIsActive ? $() : tab;
		if ( this.xhr ) {
			this.xhr.abort();
		}

		if ( !toHide.length && !toShow.length ) {
			$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
		}

		if ( toShow.length ) {
			this.load( this.tabs.index( tab ), event );
		}
		this._toggle( event, eventData );
	},

	// handles show/hide for selecting tabs
	_toggle: function( event, eventData ) {
		var that = this,
			toShow = eventData.newPanel,
			toHide = eventData.oldPanel;

		this.running = true;

		function complete() {
			that.running = false;
			that._trigger( "activate", event, eventData );
		}

		function show() {
			eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );

			if ( toShow.length && that.options.show ) {
				that._show( toShow, that.options.show, complete );
			} else {
				toShow.show();
				complete();
			}
		}

		// start out by hiding, then showing, then completing
		if ( toHide.length && this.options.hide ) {
			this._hide( toHide, this.options.hide, function() {
				eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
				show();
			});
		} else {
			eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
			toHide.hide();
			show();
		}

		toHide.attr({
			"aria-expanded": "false",
			"aria-hidden": "true"
		});
		eventData.oldTab.attr( "aria-selected", "false" );
		// If we're switching tabs, remove the old tab from the tab order.
		// If we're opening from collapsed state, remove the previous tab from the tab order.
		// If we're collapsing, then keep the collapsing tab in the tab order.
		if ( toShow.length && toHide.length ) {
			eventData.oldTab.attr( "tabIndex", -1 );
		} else if ( toShow.length ) {
			this.tabs.filter(function() {
				return $( this ).attr( "tabIndex" ) === 0;
			})
			.attr( "tabIndex", -1 );
		}

		toShow.attr({
			"aria-expanded": "true",
			"aria-hidden": "false"
		});
		eventData.newTab.attr({
			"aria-selected": "true",
			tabIndex: 0
		});
	},

	_activate: function( index ) {
		var anchor,
			active = this._findActive( index );

		// trying to activate the already active panel
		if ( active[ 0 ] === this.active[ 0 ] ) {
			return;
		}

		// trying to collapse, simulate a click on the current active header
		if ( !active.length ) {
			active = this.active;
		}

		anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
		this._eventHandler({
			target: anchor,
			currentTarget: anchor,
			preventDefault: $.noop
		});
	},

	_findActive: function( index ) {
		return index === false ? $() : this.tabs.eq( index );
	},

	_getIndex: function( index ) {
		// meta-function to give users option to provide a href string instead of a numerical index.
		if ( typeof index === "string" ) {
			index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
		}

		return index;
	},

	_destroy: function() {
		if ( this.xhr ) {
			this.xhr.abort();
		}

		this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );

		this.tablist
			.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
			.removeAttr( "role" );

		this.anchors
			.removeClass( "ui-tabs-anchor" )
			.removeAttr( "role" )
			.removeAttr( "tabIndex" )
			.removeData( "href.tabs" )
			.removeData( "load.tabs" )
			.removeUniqueId();

		this.tabs.add( this.panels ).each(function() {
			if ( $.data( this, "ui-tabs-destroy" ) ) {
				$( this ).remove();
			} else {
				$( this )
					.removeClass( "ui-state-default ui-state-active ui-state-disabled " +
						"ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
					.removeAttr( "tabIndex" )
					.removeAttr( "aria-live" )
					.removeAttr( "aria-busy" )
					.removeAttr( "aria-selected" )
					.removeAttr( "aria-labelledby" )
					.removeAttr( "aria-hidden" )
					.removeAttr( "aria-expanded" )
					.removeAttr( "role" );
			}
		});

		this.tabs.each(function() {
			var li = $( this ),
				prev = li.data( "ui-tabs-aria-controls" );
			if ( prev ) {
				li.attr( "aria-controls", prev );
			} else {
				li.removeAttr( "aria-controls" );
			}
		});

		if ( this.options.heightStyle !== "content" ) {
			this.panels.css( "height", "" );
		}
	},

	enable: function( index ) {
		var disabled = this.options.disabled;
		if ( disabled === false ) {
			return;
		}

		if ( index === undefined ) {
			disabled = false;
		} else {
			index = this._getIndex( index );
			if ( $.isArray( disabled ) ) {
				disabled = $.map( disabled, function( num ) {
					return num !== index ? num : null;
				});
			} else {
				disabled = $.map( this.tabs, function( li, num ) {
					return num !== index ? num : null;
				});
			}
		}
		this._setupDisabled( disabled );
	},

	disable: function( index ) {
		var disabled = this.options.disabled;
		if ( disabled === true ) {
			return;
		}

		if ( index === undefined ) {
			disabled = true;
		} else {
			index = this._getIndex( index );
			if ( $.inArray( index, disabled ) !== -1 ) {
				return;
			}
			if ( $.isArray( disabled ) ) {
				disabled = $.merge( [ index ], disabled ).sort();
			} else {
				disabled = [ index ];
			}
		}
		this._setupDisabled( disabled );
	},

	load: function( index, event ) {
		index = this._getIndex( index );
		var that = this,
			tab = this.tabs.eq( index ),
			anchor = tab.find( ".ui-tabs-anchor" ),
			panel = this._getPanelForTab( tab ),
			eventData = {
				tab: tab,
				panel: panel
			};

		// not remote
		if ( isLocal( anchor[ 0 ] ) ) {
			return;
		}

		this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );

		// support: jQuery <1.8
		// jQuery <1.8 returns false if the request is canceled in beforeSend,
		// but as of 1.8, $.ajax() always returns a jqXHR object.
		if ( this.xhr && this.xhr.statusText !== "canceled" ) {
			tab.addClass( "ui-tabs-loading" );
			panel.attr( "aria-busy", "true" );

			this.xhr
				.success(function( response ) {
					// support: jQuery <1.8
					// http://bugs.jquery.com/ticket/11778
					setTimeout(function() {
						panel.html( response );
						that._trigger( "load", event, eventData );
					}, 1 );
				})
				.complete(function( jqXHR, status ) {
					// support: jQuery <1.8
					// http://bugs.jquery.com/ticket/11778
					setTimeout(function() {
						if ( status === "abort" ) {
							that.panels.stop( false, true );
						}

						tab.removeClass( "ui-tabs-loading" );
						panel.removeAttr( "aria-busy" );

						if ( jqXHR === that.xhr ) {
							delete that.xhr;
						}
					}, 1 );
				});
		}
	},

	// TODO: Remove this function in 1.10 when ajaxOptions is removed
	_ajaxSettings: function( anchor, event, eventData ) {
		var that = this;
		return {
			url: anchor.attr( "href" ),
			beforeSend: function( jqXHR, settings ) {
				return that._trigger( "beforeLoad", event,
					$.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
			}
		};
	},

	_getPanelForTab: function( tab ) {
		var id = $( tab ).attr( "aria-controls" );
		return this.element.find( this._sanitizeSelector( "#" + id ) );
	}
});

// DEPRECATED
if ( $.uiBackCompat !== false ) {

	// helper method for a lot of the back compat extensions
	$.ui.tabs.prototype._ui = function( tab, panel ) {
		return {
			tab: tab,
			panel: panel,
			index: this.anchors.index( tab )
		};
	};

	// url method
	$.widget( "ui.tabs", $.ui.tabs, {
		url: function( index, url ) {
			this.anchors.eq( index ).attr( "href", url );
		}
	});

	// TODO: Remove _ajaxSettings() method when removing this extension
	// ajaxOptions and cache options
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			ajaxOptions: null,
			cache: false
		},

		_create: function() {
			this._super();

			var that = this;

			this._on({ tabsbeforeload: function( event, ui ) {
				// tab is already cached
				if ( $.data( ui.tab[ 0 ], "cache.tabs" ) ) {
					event.preventDefault();
					return;
				}

				ui.jqXHR.success(function() {
					if ( that.options.cache ) {
						$.data( ui.tab[ 0 ], "cache.tabs", true );
					}
				});
			}});
		},

		_ajaxSettings: function( anchor, event, ui ) {
			var ajaxOptions = this.options.ajaxOptions;
			return $.extend( {}, ajaxOptions, {
				error: function( xhr, s, e ) {
					try {
						// Passing index avoid a race condition when this method is
						// called after the user has selected another tab.
						// Pass the anchor that initiated this request allows
						// loadError to manipulate the tab content panel via $(a.hash)
						ajaxOptions.error(
							xhr, s, ui.tab.closest( "li" ).index(), ui.tab[ 0 ] );
					}
					catch ( e ) {}
				}
			}, this._superApply( arguments ) );
		},

		_setOption: function( key, value ) {
			// reset cache if switching from cached to not cached
			if ( key === "cache" && value === false ) {
				this.anchors.removeData( "cache.tabs" );
			}
			this._super( key, value );
		},

		_destroy: function() {
			this.anchors.removeData( "cache.tabs" );
			this._super();
		},

		url: function( index, url ){
			this.anchors.eq( index ).removeData( "cache.tabs" );
			this._superApply( arguments );
		}
	});

	// abort method
	$.widget( "ui.tabs", $.ui.tabs, {
		abort: function() {
			if ( this.xhr ) {
				this.xhr.abort();
			}
		}
	});

	// spinner
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			spinner: "<em>Loading&#8230;</em>"
		},
		_create: function() {
			this._super();
			this._on({
				tabsbeforeload: function( event, ui ) {
					// Don't react to nested tabs or tabs that don't use a spinner
					if ( event.target !== this.element[ 0 ] ||
							!this.options.spinner ) {
						return;
					}

					var span = ui.tab.find( "span" ),
						html = span.html();
					span.html( this.options.spinner );
					ui.jqXHR.complete(function() {
						span.html( html );
					});
				}
			});
		}
	});

	// enable/disable events
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			enable: null,
			disable: null
		},

		enable: function( index ) {
			var options = this.options,
				trigger;

			if ( index && options.disabled === true ||
					( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) !== -1 ) ) {
				trigger = true;
			}

			this._superApply( arguments );

			if ( trigger ) {
				this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
			}
		},

		disable: function( index ) {
			var options = this.options,
				trigger;

			if ( index && options.disabled === false ||
					( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) === -1 ) ) {
				trigger = true;
			}

			this._superApply( arguments );

			if ( trigger ) {
				this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
			}
		}
	});

	// add/remove methods and events
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			add: null,
			remove: null,
			tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>"
		},

		add: function( url, label, index ) {
			if ( index === undefined ) {
				index = this.anchors.length;
			}

			var doInsertAfter, panel,
				options = this.options,
				li = $( options.tabTemplate
					.replace( /#\{href\}/g, url )
					.replace( /#\{label\}/g, label ) ),
				id = !url.indexOf( "#" ) ?
					url.replace( "#", "" ) :
					this._tabId( li );

			li.addClass( "ui-state-default ui-corner-top" ).data( "ui-tabs-destroy", true );
			li.attr( "aria-controls", id );

			doInsertAfter = index >= this.tabs.length;

			// try to find an existing element before creating a new one
			panel = this.element.find( "#" + id );
			if ( !panel.length ) {
				panel = this._createPanel( id );
				if ( doInsertAfter ) {
					if ( index > 0 ) {
						panel.insertAfter( this.panels.eq( -1 ) );
					} else {
						panel.appendTo( this.element );
					}
				} else {
					panel.insertBefore( this.panels[ index ] );
				}
			}
			panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ).hide();

			if ( doInsertAfter ) {
				li.appendTo( this.tablist );
			} else {
				li.insertBefore( this.tabs[ index ] );
			}

			options.disabled = $.map( options.disabled, function( n ) {
				return n >= index ? ++n : n;
			});

			this.refresh();
			if ( this.tabs.length === 1 && options.active === false ) {
				this.option( "active", 0 );
			}

			this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
			return this;
		},

		remove: function( index ) {
			index = this._getIndex( index );
			var options = this.options,
				tab = this.tabs.eq( index ).remove(),
				panel = this._getPanelForTab( tab ).remove();

			// If selected tab was removed focus tab to the right or
			// in case the last tab was removed the tab to the left.
			// We check for more than 2 tabs, because if there are only 2,
			// then when we remove this tab, there will only be one tab left
			// so we don't need to detect which tab to activate.
			if ( tab.hasClass( "ui-tabs-active" ) && this.anchors.length > 2 ) {
				this._activate( index + ( index + 1 < this.anchors.length ? 1 : -1 ) );
			}

			options.disabled = $.map(
				$.grep( options.disabled, function( n ) {
					return n !== index;
				}),
				function( n ) {
					return n >= index ? --n : n;
				});

			this.refresh();

			this._trigger( "remove", null, this._ui( tab.find( "a" )[ 0 ], panel[ 0 ] ) );
			return this;
		}
	});

	// length method
	$.widget( "ui.tabs", $.ui.tabs, {
		length: function() {
			return this.anchors.length;
		}
	});

	// panel ids (idPrefix option + title attribute)
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			idPrefix: "ui-tabs-"
		},

		_tabId: function( tab ) {
			var a = tab.is( "li" ) ? tab.find( "a[href]" ) : tab;
			a = a[0];
			return $( a ).closest( "li" ).attr( "aria-controls" ) ||
				a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF\-]/g, "" ) ||
				this.options.idPrefix + getNextTabId();
		}
	});

	// _createPanel method
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			panelTemplate: "<div></div>"
		},

		_createPanel: function( id ) {
			return $( this.options.panelTemplate )
				.attr( "id", id )
				.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
				.data( "ui-tabs-destroy", true );
		}
	});

	// selected option
	$.widget( "ui.tabs", $.ui.tabs, {
		_create: function() {
			var options = this.options;
			if ( options.active === null && options.selected !== undefined ) {
				options.active = options.selected === -1 ? false : options.selected;
			}
			this._super();
			options.selected = options.active;
			if ( options.selected === false ) {
				options.selected = -1;
			}
		},

		_setOption: function( key, value ) {
			if ( key !== "selected" ) {
				return this._super( key, value );
			}

			var options = this.options;
			this._super( "active", value === -1 ? false : value );
			options.selected = options.active;
			if ( options.selected === false ) {
				options.selected = -1;
			}
		},

		_eventHandler: function( event ) {
			this._superApply( arguments );
			this.options.selected = this.options.active;
			if ( this.options.selected === false ) {
				this.options.selected = -1;
			}
		}
	});

	// show and select event
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			show: null,
			select: null
		},
		_create: function() {
			this._super();
			if ( this.options.active !== false ) {
				this._trigger( "show", null, this._ui(
					this.active.find( ".ui-tabs-anchor" )[ 0 ],
					this._getPanelForTab( this.active )[ 0 ] ) );
			}
		},
		_trigger: function( type, event, data ) {
			var ret = this._superApply( arguments );
			if ( !ret ) {
				return false;
			}
			if ( type === "beforeActivate" && data.newTab.length ) {
				ret = this._super( "select", event, {
					tab: data.newTab.find( ".ui-tabs-anchor" )[ 0],
					panel: data.newPanel[ 0 ],
					index: data.newTab.closest( "li" ).index()
				});
			} else if ( type === "activate" && data.newTab.length ) {
				ret = this._super( "show", event, {
					tab: data.newTab.find( ".ui-tabs-anchor" )[ 0 ],
					panel: data.newPanel[ 0 ],
					index: data.newTab.closest( "li" ).index()
				});
			}
			return ret;
		}
	});

	// select method
	$.widget( "ui.tabs", $.ui.tabs, {
		select: function( index ) {
			index = this._getIndex( index );
			if ( index === -1 ) {
				if ( this.options.collapsible && this.options.selected !== -1 ) {
					index = this.options.selected;
				} else {
					return;
				}
			}
			this.anchors.eq( index ).trigger( this.options.event + this.eventNamespace );
		}
	});

	// cookie option
	(function() {

	var listId = 0;

	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			cookie: null // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
		},
		_create: function() {
			var options = this.options,
				active;
			if ( options.active == null && options.cookie ) {
				active = parseInt( this._cookie(), 10 );
				if ( active === -1 ) {
					active = false;
				}
				options.active = active;
			}
			this._super();
		},
		_cookie: function( active ) {
			var cookie = [ this.cookie ||
				( this.cookie = this.options.cookie.name || "ui-tabs-" + (++listId) ) ];
			if ( arguments.length ) {
				cookie.push( active === false ? -1 : active );
				cookie.push( this.options.cookie );
			}
			return $.cookie.apply( null, cookie );
		},
		_refresh: function() {
			this._super();
			if ( this.options.cookie ) {
				this._cookie( this.options.active, this.options.cookie );
			}
		},
		_eventHandler: function( event ) {
			this._superApply( arguments );
			if ( this.options.cookie ) {
				this._cookie( this.options.active, this.options.cookie );
			}
		},
		_destroy: function() {
			this._super();
			if ( this.options.cookie ) {
				this._cookie( null, this.options.cookie );
			}
		}
	});

	})();

	// load event
	$.widget( "ui.tabs", $.ui.tabs, {
		_trigger: function( type, event, data ) {
			var _data = $.extend( {}, data );
			if ( type === "load" ) {
				_data.panel = _data.panel[ 0 ];
				_data.tab = _data.tab.find( ".ui-tabs-anchor" )[ 0 ];
			}
			return this._super( type, event, _data );
		}
	});

	// fx option
	// The new animation options (show, hide) conflict with the old show callback.
	// The old fx option wins over show/hide anyway (always favor back-compat).
	// If a user wants to use the new animation API, they must give up the old API.
	$.widget( "ui.tabs", $.ui.tabs, {
		options: {
			fx: null // e.g. { height: "toggle", opacity: "toggle", duration: 200 }
		},

		_getFx: function() {
			var hide, show,
				fx = this.options.fx;

			if ( fx ) {
				if ( $.isArray( fx ) ) {
					hide = fx[ 0 ];
					show = fx[ 1 ];
				} else {
					hide = show = fx;
				}
			}

			return fx ? { show: show, hide: hide } : null;
		},

		_toggle: function( event, eventData ) {
			var that = this,
				toShow = eventData.newPanel,
				toHide = eventData.oldPanel,
				fx = this._getFx();

			if ( !fx ) {
				return this._super( event, eventData );
			}

			that.running = true;

			function complete() {
				that.running = false;
				that._trigger( "activate", event, eventData );
			}

			function show() {
				eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );

				if ( toShow.length && fx.show ) {
					toShow
						.animate( fx.show, fx.show.duration, function() {
							complete();
						});
				} else {
					toShow.show();
					complete();
				}
			}

			// start out by hiding, then showing, then completing
			if ( toHide.length && fx.hide ) {
				toHide.animate( fx.hide, fx.hide.duration, function() {
					eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
					show();
				});
			} else {
				eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
				toHide.hide();
				show();
			}
		}
	});
}

})( jQuery );
(function( $ ) {

var increments = 0;

function addDescribedBy( elem, id ) {
	var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
	describedby.push( id );
	elem
		.data( "ui-tooltip-id", id )
		.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
}

function removeDescribedBy( elem ) {
	var id = elem.data( "ui-tooltip-id" ),
		describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
		index = $.inArray( id, describedby );
	if ( index !== -1 ) {
		describedby.splice( index, 1 );
	}

	elem.removeData( "ui-tooltip-id" );
	describedby = $.trim( describedby.join( " " ) );
	if ( describedby ) {
		elem.attr( "aria-describedby", describedby );
	} else {
		elem.removeAttr( "aria-describedby" );
	}
}

$.widget( "ui.tooltip", {
	version: "1.9.0",
	options: {
		content: function() {
			return $( this ).attr( "title" );
		},
		hide: true,
		items: "[title]",
		position: {
			my: "left+15 center",
			at: "right center",
			collision: "flipfit flipfit"
		},
		show: true,
		tooltipClass: null,
		track: false,

		// callbacks
		close: null,
		open: null
	},

	_create: function() {
		this._on({
			mouseover: "open",
			focusin: "open"
		});

		// IDs of generated tooltips, needed for destroy
		this.tooltips = {};
	},

	_setOption: function( key, value ) {
		var that = this;

		if ( key === "disabled" ) {
			this[ value ? "_disable" : "_enable" ]();
			this.options[ key ] = value;
			// disable element style changes
			return;
		}

		this._super( key, value );

		if ( key === "content" ) {
			$.each( this.tooltips, function( id, element ) {
				that._updateContent( element );
			});
		}
	},

	_disable: function() {
		var that = this;

		// close open tooltips
		$.each( this.tooltips, function( id, element ) {
			var event = $.Event( "blur" );
			event.target = event.currentTarget = element[0];
			that.close( event, true );
		});

		// remove title attributes to prevent native tooltips
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.is( "[title]" ) ) {
				element
					.data( "ui-tooltip-title", element.attr( "title" ) )
					.attr( "title", "" );
			}
		});
	},

	_enable: function() {
		// restore title attributes
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.data( "ui-tooltip-title" ) ) {
				element.attr( "title", element.data( "ui-tooltip-title" ) );
			}
		});
	},

	open: function( event ) {
		var target = $( event ? event.target : this.element )
				.closest( this.options.items );

		// No element to show a tooltip for
		if ( !target.length ) {
			return;
		}

		// If the tooltip is open and we're tracking then reposition the tooltip.
		// This makes sure that a tracking tooltip doesn't obscure a focused element
		// if the user was hovering when the element gained focused.
		if ( this.options.track && target.data( "ui-tooltip-id" ) ) {
			this._find( target ).position( $.extend({
				of: target
			}, this.options.position ) );
			// Stop tracking (#8622)
			this._off( this.document, "mousemove" );
			return;
		}

		if ( target.attr( "title" ) ) {
			target.data( "ui-tooltip-title", target.attr( "title" ) );
		}

		target.data( "tooltip-open", true );

		this._updateContent( target, event );
	},

	_updateContent: function( target, event ) {
		var content,
			contentOption = this.options.content,
			that = this;

		if ( typeof contentOption === "string" ) {
			return this._open( event, target, contentOption );
		}

		content = contentOption.call( target[0], function( response ) {
			// ignore async response if tooltip was closed already
			if ( !target.data( "tooltip-open" ) ) {
				return;
			}
			// IE may instantly serve a cached response for ajax requests
			// delay this call to _open so the other call to _open runs first
			that._delay(function() {
				this._open( event, target, response );
			});
		});
		if ( content ) {
			this._open( event, target, content );
		}
	},

	_open: function( event, target, content ) {
		var tooltip, positionOption;
		if ( !content ) {
			return;
		}

		// Content can be updated multiple times. If the tooltip already
		// exists, then just update the content and bail.
		tooltip = this._find( target );
		if ( tooltip.length ) {
			tooltip.find( ".ui-tooltip-content" ).html( content );
			return;
		}

		// if we have a title, clear it to prevent the native tooltip
		// we have to check first to avoid defining a title if none exists
		// (we don't want to cause an element to start matching [title])
		//
		// We use removeAttr only for key events, to allow IE to export the correct
		// accessible attributes. For mouse events, set to empty string to avoid
		// native tooltip showing up (happens only when removing inside mouseover).
		if ( target.is( "[title]" ) ) {
			if ( event && event.type === "mouseover" ) {
				target.attr( "title", "" );
			} else {
				target.removeAttr( "title" );
			}
		}

		tooltip = this._tooltip( target );
		addDescribedBy( target, tooltip.attr( "id" ) );
		tooltip.find( ".ui-tooltip-content" ).html( content );

		function position( event ) {
			positionOption.of = event;
			tooltip.position( positionOption );
		}
		if ( this.options.track && event && /^mouse/.test( event.originalEvent.type ) ) {
			positionOption = $.extend( {}, this.options.position );
			this._on( this.document, {
				mousemove: position
			});
			// trigger once to override element-relative positioning
			position( event );
		} else {
			tooltip.position( $.extend({
				of: target
			}, this.options.position ) );
		}

		tooltip.hide();

		this._show( tooltip, this.options.show );

		this._trigger( "open", event, { tooltip: tooltip } );

		this._on( target, {
			mouseleave: "close",
			focusout: "close",
			keyup: function( event ) {
				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
					var fakeEvent = $.Event(event);
					fakeEvent.currentTarget = target[0];
					this.close( fakeEvent, true );
				}
			}
		});
	},

	close: function( event, force ) {
		var that = this,
			target = $( event ? event.currentTarget : this.element ),
			tooltip = this._find( target );

		// disabling closes the tooltip, so we need to track when we're closing
		// to avoid an infinite loop in case the tooltip becomes disabled on close
		if ( this.closing ) {
			return;
		}

		// don't close if the element has focus
		// this prevents the tooltip from closing if you hover while focused
		//
		// we have to check the event type because tabbing out of the document
		// may leave the element as the activeElement
		if ( !force && event && event.type !== "focusout" &&
				this.document[0].activeElement === target[0] ) {
			return;
		}

		// only set title if we had one before (see comment in _open())
		if ( target.data( "ui-tooltip-title" ) ) {
			target.attr( "title", target.data( "ui-tooltip-title" ) );
		}

		removeDescribedBy( target );

		tooltip.stop( true );
		this._hide( tooltip, this.options.hide, function() {
			$( this ).remove();
			delete that.tooltips[ this.id ];
		});

		target.removeData( "tooltip-open" );
		this._off( target, "mouseleave focusout keyup" );
		this._off( this.document, "mousemove" );

		this.closing = true;
		this._trigger( "close", event, { tooltip: tooltip } );
		this.closing = false;
	},

	_tooltip: function( element ) {
		var id = "ui-tooltip-" + increments++,
			tooltip = $( "<div>" )
				.attr({
					id: id,
					role: "tooltip"
				})
				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
					( this.options.tooltipClass || "" ) );
		$( "<div>" )
			.addClass( "ui-tooltip-content" )
			.appendTo( tooltip );
		tooltip.appendTo( this.document[0].body );
		if ( $.fn.bgiframe ) {
			tooltip.bgiframe();
		}
		this.tooltips[ id ] = element;
		return tooltip;
	},

	_find: function( target ) {
		var id = target.data( "ui-tooltip-id" );
		return id ? $( "#" + id ) : $();
	},

	_destroy: function() {
		var that = this;

		// close open tooltips
		$.each( this.tooltips, function( id, element ) {
			// Delegate to close method to handle common cleanup
			var event = $.Event( "blur" );
			event.target = event.currentTarget = element[0];
			that.close( event, true );

			// Remove immediately; destroying an open tooltip doesn't use the
			// hide animation
			$( "#" + id ).remove();

			// Restore the title
			if ( element.data( "ui-tooltip-title" ) ) {
				element.attr( "title", element.data( "ui-tooltip-title" ) );
				element.removeData( "ui-tooltip-title" );
			}
		});
	}
});

}( jQuery ) );
;(jQuery.effects || (function($, undefined) {

var backCompat = $.uiBackCompat !== false,
	// prefix used for storing data on .data()
	dataSpace = "ui-effects-";

$.effects = {
	effect: {}
};

/*!
 * jQuery Color Animations v2.0.0
 * http://jquery.com/
 *
 * Copyright 2012 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * Date: Mon Aug 13 13:41:02 2012 -0500
 */
(function( jQuery, undefined ) {

	var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor".split(" "),

	// plusequals test for += 100 -= 100
	rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
	// a set of RE's that can match strings and generate color tuples.
	stringParsers = [{
			re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
			parse: function( execResult ) {
				return [
					execResult[ 1 ],
					execResult[ 2 ],
					execResult[ 3 ],
					execResult[ 4 ]
				];
			}
		}, {
			re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
			parse: function( execResult ) {
				return [
					execResult[ 1 ] * 2.55,
					execResult[ 2 ] * 2.55,
					execResult[ 3 ] * 2.55,
					execResult[ 4 ]
				];
			}
		}, {
			// this regex ignores A-F because it's compared against an already lowercased string
			re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
			parse: function( execResult ) {
				return [
					parseInt( execResult[ 1 ], 16 ),
					parseInt( execResult[ 2 ], 16 ),
					parseInt( execResult[ 3 ], 16 )
				];
			}
		}, {
			// this regex ignores A-F because it's compared against an already lowercased string
			re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
			parse: function( execResult ) {
				return [
					parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
					parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
					parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
				];
			}
		}, {
			re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
			space: "hsla",
			parse: function( execResult ) {
				return [
					execResult[ 1 ],
					execResult[ 2 ] / 100,
					execResult[ 3 ] / 100,
					execResult[ 4 ]
				];
			}
		}],

	// jQuery.Color( )
	color = jQuery.Color = function( color, green, blue, alpha ) {
		return new jQuery.Color.fn.parse( color, green, blue, alpha );
	},
	spaces = {
		rgba: {
			props: {
				red: {
					idx: 0,
					type: "byte"
				},
				green: {
					idx: 1,
					type: "byte"
				},
				blue: {
					idx: 2,
					type: "byte"
				}
			}
		},

		hsla: {
			props: {
				hue: {
					idx: 0,
					type: "degrees"
				},
				saturation: {
					idx: 1,
					type: "percent"
				},
				lightness: {
					idx: 2,
					type: "percent"
				}
			}
		}
	},
	propTypes = {
		"byte": {
			floor: true,
			max: 255
		},
		"percent": {
			max: 1
		},
		"degrees": {
			mod: 360,
			floor: true
		}
	},
	support = color.support = {},

	// element for support tests
	supportElem = jQuery( "<p>" )[ 0 ],

	// colors = jQuery.Color.names
	colors,

	// local aliases of functions called often
	each = jQuery.each;

// determine rgba support immediately
supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;

// define cache name and alpha properties
// for rgba and hsla spaces
each( spaces, function( spaceName, space ) {
	space.cache = "_" + spaceName;
	space.props.alpha = {
		idx: 3,
		type: "percent",
		def: 1
	};
});

function clamp( value, prop, allowEmpty ) {
	var type = propTypes[ prop.type ] || {};

	if ( value == null ) {
		return (allowEmpty || !prop.def) ? null : prop.def;
	}

	// ~~ is an short way of doing floor for positive numbers
	value = type.floor ? ~~value : parseFloat( value );

	// IE will pass in empty strings as value for alpha,
	// which will hit this case
	if ( isNaN( value ) ) {
		return prop.def;
	}

	if ( type.mod ) {
		// we add mod before modding to make sure that negatives values
		// get converted properly: -10 -> 350
		return (value + type.mod) % type.mod;
	}

	// for now all property types without mod have min and max
	return 0 > value ? 0 : type.max < value ? type.max : value;
}

function stringParse( string ) {
	var inst = color(),
		rgba = inst._rgba = [];

	string = string.toLowerCase();

	each( stringParsers, function( i, parser ) {
		var parsed,
			match = parser.re.exec( string ),
			values = match && parser.parse( match ),
			spaceName = parser.space || "rgba";

		if ( values ) {
			parsed = inst[ spaceName ]( values );

			// if this was an rgba parse the assignment might happen twice
			// oh well....
			inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
			rgba = inst._rgba = parsed._rgba;

			// exit each( stringParsers ) here because we matched
			return false;
		}
	});

	// Found a stringParser that handled it
	if ( rgba.length ) {

		// if this came from a parsed string, force "transparent" when alpha is 0
		// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
		if ( rgba.join() === "0,0,0,0" ) {
			jQuery.extend( rgba, colors.transparent );
		}
		return inst;
	}

	// named colors
	return colors[ string ];
}

color.fn = jQuery.extend( color.prototype, {
	parse: function( red, green, blue, alpha ) {
		if ( red === undefined ) {
			this._rgba = [ null, null, null, null ];
			return this;
		}
		if ( red.jquery || red.nodeType ) {
			red = jQuery( red ).css( green );
			green = undefined;
		}

		var inst = this,
			type = jQuery.type( red ),
			rgba = this._rgba = [],
			source;

		// more than 1 argument specified - assume ( red, green, blue, alpha )
		if ( green !== undefined ) {
			red = [ red, green, blue, alpha ];
			type = "array";
		}

		if ( type === "string" ) {
			return this.parse( stringParse( red ) || colors._default );
		}

		if ( type === "array" ) {
			each( spaces.rgba.props, function( key, prop ) {
				rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
			});
			return this;
		}

		if ( type === "object" ) {
			if ( red instanceof color ) {
				each( spaces, function( spaceName, space ) {
					if ( red[ space.cache ] ) {
						inst[ space.cache ] = red[ space.cache ].slice();
					}
				});
			} else {
				each( spaces, function( spaceName, space ) {
					var cache = space.cache;
					each( space.props, function( key, prop ) {

						// if the cache doesn't exist, and we know how to convert
						if ( !inst[ cache ] && space.to ) {

							// if the value was null, we don't need to copy it
							// if the key was alpha, we don't need to copy it either
							if ( key === "alpha" || red[ key ] == null ) {
								return;
							}
							inst[ cache ] = space.to( inst._rgba );
						}

						// this is the only case where we allow nulls for ALL properties.
						// call clamp with alwaysAllowEmpty
						inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
					});

					// everything defined but alpha?
					if ( inst[ cache ] && $.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
						// use the default of 1
						inst[ cache ][ 3 ] = 1;
						if ( space.from ) {
							inst._rgba = space.from( inst[ cache ] );
						}
					}
				});
			}
			return this;
		}
	},
	is: function( compare ) {
		var is = color( compare ),
			same = true,
			inst = this;

		each( spaces, function( _, space ) {
			var localCache,
				isCache = is[ space.cache ];
			if (isCache) {
				localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
				each( space.props, function( _, prop ) {
					if ( isCache[ prop.idx ] != null ) {
						same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
						return same;
					}
				});
			}
			return same;
		});
		return same;
	},
	_space: function() {
		var used = [],
			inst = this;
		each( spaces, function( spaceName, space ) {
			if ( inst[ space.cache ] ) {
				used.push( spaceName );
			}
		});
		return used.pop();
	},
	transition: function( other, distance ) {
		var end = color( other ),
			spaceName = end._space(),
			space = spaces[ spaceName ],
			startColor = this.alpha() === 0 ? color( "transparent" ) : this,
			start = startColor[ space.cache ] || space.to( startColor._rgba ),
			result = start.slice();

		end = end[ space.cache ];
		each( space.props, function( key, prop ) {
			var index = prop.idx,
				startValue = start[ index ],
				endValue = end[ index ],
				type = propTypes[ prop.type ] || {};

			// if null, don't override start value
			if ( endValue === null ) {
				return;
			}
			// if null - use end
			if ( startValue === null ) {
				result[ index ] = endValue;
			} else {
				if ( type.mod ) {
					if ( endValue - startValue > type.mod / 2 ) {
						startValue += type.mod;
					} else if ( startValue - endValue > type.mod / 2 ) {
						startValue -= type.mod;
					}
				}
				result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
			}
		});
		return this[ spaceName ]( result );
	},
	blend: function( opaque ) {
		// if we are already opaque - return ourself
		if ( this._rgba[ 3 ] === 1 ) {
			return this;
		}

		var rgb = this._rgba.slice(),
			a = rgb.pop(),
			blend = color( opaque )._rgba;

		return color( jQuery.map( rgb, function( v, i ) {
			return ( 1 - a ) * blend[ i ] + a * v;
		}));
	},
	toRgbaString: function() {
		var prefix = "rgba(",
			rgba = jQuery.map( this._rgba, function( v, i ) {
				return v == null ? ( i > 2 ? 1 : 0 ) : v;
			});

		if ( rgba[ 3 ] === 1 ) {
			rgba.pop();
			prefix = "rgb(";
		}

		return prefix + rgba.join() + ")";
	},
	toHslaString: function() {
		var prefix = "hsla(",
			hsla = jQuery.map( this.hsla(), function( v, i ) {
				if ( v == null ) {
					v = i > 2 ? 1 : 0;
				}

				// catch 1 and 2
				if ( i && i < 3 ) {
					v = Math.round( v * 100 ) + "%";
				}
				return v;
			});

		if ( hsla[ 3 ] === 1 ) {
			hsla.pop();
			prefix = "hsl(";
		}
		return prefix + hsla.join() + ")";
	},
	toHexString: function( includeAlpha ) {
		var rgba = this._rgba.slice(),
			alpha = rgba.pop();

		if ( includeAlpha ) {
			rgba.push( ~~( alpha * 255 ) );
		}

		return "#" + jQuery.map( rgba, function( v, i ) {

			// default to 0 when nulls exist
			v = ( v || 0 ).toString( 16 );
			return v.length === 1 ? "0" + v : v;
		}).join("");
	},
	toString: function() {
		return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
	}
});
color.fn.parse.prototype = color.fn;

// hsla conversions adapted from:
// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021

function hue2rgb( p, q, h ) {
	h = ( h + 1 ) % 1;
	if ( h * 6 < 1 ) {
		return p + (q - p) * h * 6;
	}
	if ( h * 2 < 1) {
		return q;
	}
	if ( h * 3 < 2 ) {
		return p + (q - p) * ((2/3) - h) * 6;
	}
	return p;
}

spaces.hsla.to = function ( rgba ) {
	if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
		return [ null, null, null, rgba[ 3 ] ];
	}
	var r = rgba[ 0 ] / 255,
		g = rgba[ 1 ] / 255,
		b = rgba[ 2 ] / 255,
		a = rgba[ 3 ],
		max = Math.max( r, g, b ),
		min = Math.min( r, g, b ),
		diff = max - min,
		add = max + min,
		l = add * 0.5,
		h, s;

	if ( min === max ) {
		h = 0;
	} else if ( r === max ) {
		h = ( 60 * ( g - b ) / diff ) + 360;
	} else if ( g === max ) {
		h = ( 60 * ( b - r ) / diff ) + 120;
	} else {
		h = ( 60 * ( r - g ) / diff ) + 240;
	}

	if ( l === 0 || l === 1 ) {
		s = l;
	} else if ( l <= 0.5 ) {
		s = diff / add;
	} else {
		s = diff / ( 2 - add );
	}
	return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
};

spaces.hsla.from = function ( hsla ) {
	if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
		return [ null, null, null, hsla[ 3 ] ];
	}
	var h = hsla[ 0 ] / 360,
		s = hsla[ 1 ],
		l = hsla[ 2 ],
		a = hsla[ 3 ],
		q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
		p = 2 * l - q,
		r, g, b;

	return [
		Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
		Math.round( hue2rgb( p, q, h ) * 255 ),
		Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
		a
	];
};


each( spaces, function( spaceName, space ) {
	var props = space.props,
		cache = space.cache,
		to = space.to,
		from = space.from;

	// makes rgba() and hsla()
	color.fn[ spaceName ] = function( value ) {

		// generate a cache for this space if it doesn't exist
		if ( to && !this[ cache ] ) {
			this[ cache ] = to( this._rgba );
		}
		if ( value === undefined ) {
			return this[ cache ].slice();
		}

		var ret,
			type = jQuery.type( value ),
			arr = ( type === "array" || type === "object" ) ? value : arguments,
			local = this[ cache ].slice();

		each( props, function( key, prop ) {
			var val = arr[ type === "object" ? key : prop.idx ];
			if ( val == null ) {
				val = local[ prop.idx ];
			}
			local[ prop.idx ] = clamp( val, prop );
		});

		if ( from ) {
			ret = color( from( local ) );
			ret[ cache ] = local;
			return ret;
		} else {
			return color( local );
		}
	};

	// makes red() green() blue() alpha() hue() saturation() lightness()
	each( props, function( key, prop ) {
		// alpha is included in more than one space
		if ( color.fn[ key ] ) {
			return;
		}
		color.fn[ key ] = function( value ) {
			var vtype = jQuery.type( value ),
				fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
				local = this[ fn ](),
				cur = local[ prop.idx ],
				match;

			if ( vtype === "undefined" ) {
				return cur;
			}

			if ( vtype === "function" ) {
				value = value.call( this, cur );
				vtype = jQuery.type( value );
			}
			if ( value == null && prop.empty ) {
				return this;
			}
			if ( vtype === "string" ) {
				match = rplusequals.exec( value );
				if ( match ) {
					value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
				}
			}
			local[ prop.idx ] = value;
			return this[ fn ]( local );
		};
	});
});

// add .fx.step functions
each( stepHooks, function( i, hook ) {
	jQuery.cssHooks[ hook ] = {
		set: function( elem, value ) {
			var parsed, curElem,
				backgroundColor = "";

			if ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) {
				value = color( parsed || value );
				if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
					curElem = hook === "backgroundColor" ? elem.parentNode : elem;
					while (
						(backgroundColor === "" || backgroundColor === "transparent") &&
						curElem && curElem.style
					) {
						try {
							backgroundColor = jQuery.css( curElem, "backgroundColor" );
							curElem = curElem.parentNode;
						} catch ( e ) {
						}
					}

					value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
						backgroundColor :
						"_default" );
				}

				value = value.toRgbaString();
			}
			try {
				elem.style[ hook ] = value;
			} catch( value ) {
				// wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit'
			}
		}
	};
	jQuery.fx.step[ hook ] = function( fx ) {
		if ( !fx.colorInit ) {
			fx.start = color( fx.elem, hook );
			fx.end = color( fx.end );
			fx.colorInit = true;
		}
		jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
	};
});

jQuery.cssHooks.borderColor = {
	expand: function( value ) {
		var expanded = {};

		each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
			expanded[ "border" + part + "Color" ] = value;
		});
		return expanded;
	}
};

// Basic color names only.
// Usage of any of the other color names requires adding yourself or including
// jquery.color.svg-names.js.
colors = jQuery.Color.names = {
	// 4.1. Basic color keywords
	aqua: "#00ffff",
	black: "#000000",
	blue: "#0000ff",
	fuchsia: "#ff00ff",
	gray: "#808080",
	green: "#008000",
	lime: "#00ff00",
	maroon: "#800000",
	navy: "#000080",
	olive: "#808000",
	purple: "#800080",
	red: "#ff0000",
	silver: "#c0c0c0",
	teal: "#008080",
	white: "#ffffff",
	yellow: "#ffff00",

	// 4.2.3. "transparent" color keyword
	transparent: [ null, null, null, 0 ],

	_default: "#ffffff"
};

})( jQuery );



/******************************************************************************/
/****************************** CLASS ANIMATIONS ******************************/
/******************************************************************************/
(function() {

var classAnimationActions = [ "add", "remove", "toggle" ],
	shorthandStyles = {
		border: 1,
		borderBottom: 1,
		borderColor: 1,
		borderLeft: 1,
		borderRight: 1,
		borderTop: 1,
		borderWidth: 1,
		margin: 1,
		padding: 1
	};

$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) {
	$.fx.step[ prop ] = function( fx ) {
		if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
			jQuery.style( fx.elem, prop, fx.end );
			fx.setAttr = true;
		}
	};
});

function getElementStyles() {
	var style = this.ownerDocument.defaultView ?
			this.ownerDocument.defaultView.getComputedStyle( this, null ) :
			this.currentStyle,
		newStyle = {},
		key,
		camelCase,
		len;

	// webkit enumerates style porperties
	if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
		len = style.length;
		while ( len-- ) {
			key = style[ len ];
			if ( typeof style[ key ] === "string" ) {
				newStyle[ $.camelCase( key ) ] = style[ key ];
			}
		}
	} else {
		for ( key in style ) {
			if ( typeof style[ key ] === "string" ) {
				newStyle[ key ] = style[ key ];
			}
		}
	}

	return newStyle;
}


function styleDifference( oldStyle, newStyle ) {
	var diff = {},
		name, value;

	for ( name in newStyle ) {
		value = newStyle[ name ];
		if ( oldStyle[ name ] !== value ) {
			if ( !shorthandStyles[ name ] ) {
				if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
					diff[ name ] = value;
				}
			}
		}
	}

	return diff;
}

$.effects.animateClass = function( value, duration, easing, callback ) {
	var o = $.speed( duration, easing, callback );

	return this.queue( function() {
		var animated = $( this ),
			baseClass = animated.attr( "class" ) || "",
			applyClassChange,
			allAnimations = o.children ? animated.find( "*" ).andSelf() : animated;

		// map the animated objects to store the original styles.
		allAnimations = allAnimations.map(function() {
			var el = $( this );
			return {
				el: el,
				start: getElementStyles.call( this )
			};
		});

		// apply class change
		applyClassChange = function() {
			$.each( classAnimationActions, function(i, action) {
				if ( value[ action ] ) {
					animated[ action + "Class" ]( value[ action ] );
				}
			});
		};
		applyClassChange();

		// map all animated objects again - calculate new styles and diff
		allAnimations = allAnimations.map(function() {
			this.end = getElementStyles.call( this.el[ 0 ] );
			this.diff = styleDifference( this.start, this.end );
			return this;
		});

		// apply original class
		animated.attr( "class", baseClass );

		// map all animated objects again - this time collecting a promise
		allAnimations = allAnimations.map(function() {
			var styleInfo = this,
				dfd = $.Deferred(),
				opts = jQuery.extend({}, o, {
					queue: false,
					complete: function() {
						dfd.resolve( styleInfo );
					}
				});

			this.el.animate( this.diff, opts );
			return dfd.promise();
		});

		// once all animations have completed:
		$.when.apply( $, allAnimations.get() ).done(function() {

			// set the final class
			applyClassChange();

			// for each animated element,
			// clear all css properties that were animated
			$.each( arguments, function() {
				var el = this.el;
				$.each( this.diff, function(key) {
					el.css( key, '' );
				});
			});

			// this is guarnteed to be there if you use jQuery.speed()
			// it also handles dequeuing the next anim...
			o.complete.call( animated[ 0 ] );
		});
	});
};

$.fn.extend({
	_addClass: $.fn.addClass,
	addClass: function( classNames, speed, easing, callback ) {
		return speed ?
			$.effects.animateClass.call( this,
				{ add: classNames }, speed, easing, callback ) :
			this._addClass( classNames );
	},

	_removeClass: $.fn.removeClass,
	removeClass: function( classNames, speed, easing, callback ) {
		return speed ?
			$.effects.animateClass.call( this,
				{ remove: classNames }, speed, easing, callback ) :
			this._removeClass( classNames );
	},

	_toggleClass: $.fn.toggleClass,
	toggleClass: function( classNames, force, speed, easing, callback ) {
		if ( typeof force === "boolean" || force === undefined ) {
			if ( !speed ) {
				// without speed parameter
				return this._toggleClass( classNames, force );
			} else {
				return $.effects.animateClass.call( this,
					(force ? { add: classNames } : { remove: classNames }),
					speed, easing, callback );
			}
		} else {
			// without force parameter
			return $.effects.animateClass.call( this,
				{ toggle: classNames }, force, speed, easing );
		}
	},

	switchClass: function( remove, add, speed, easing, callback) {
		return $.effects.animateClass.call( this, {
			add: add,
			remove: remove
		}, speed, easing, callback );
	}
});

})();

/******************************************************************************/
/*********************************** EFFECTS **********************************/
/******************************************************************************/

(function() {

$.extend( $.effects, {
	version: "1.9.0",

	// Saves a set of properties in a data storage
	save: function( element, set ) {
		for( var i=0; i < set.length; i++ ) {
			if ( set[ i ] !== null ) {
				element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
			}
		}
	},

	// Restores a set of previously saved properties from a data storage
	restore: function( element, set ) {
		var val, i;
		for( i=0; i < set.length; i++ ) {
			if ( set[ i ] !== null ) {
				val = element.data( dataSpace + set[ i ] );
				// support: jQuery 1.6.2
				// http://bugs.jquery.com/ticket/9917
				// jQuery 1.6.2 incorrectly returns undefined for any falsy value.
				// We can't differentiate between "" and 0 here, so we just assume
				// empty string since it's likely to be a more common value...
				if ( val === undefined ) {
					val = "";
				}
				element.css( set[ i ], val );
			}
		}
	},

	setMode: function( el, mode ) {
		if (mode === "toggle") {
			mode = el.is( ":hidden" ) ? "show" : "hide";
		}
		return mode;
	},

	// Translates a [top,left] array into a baseline value
	// this should be a little more flexible in the future to handle a string & hash
	getBaseline: function( origin, original ) {
		var y, x;
		switch ( origin[ 0 ] ) {
			case "top": y = 0; break;
			case "middle": y = 0.5; break;
			case "bottom": y = 1; break;
			default: y = origin[ 0 ] / original.height;
		}
		switch ( origin[ 1 ] ) {
			case "left": x = 0; break;
			case "center": x = 0.5; break;
			case "right": x = 1; break;
			default: x = origin[ 1 ] / original.width;
		}
		return {
			x: x,
			y: y
		};
	},

	// Wraps the element around a wrapper that copies position properties
	createWrapper: function( element ) {

		// if the element is already wrapped, return it
		if ( element.parent().is( ".ui-effects-wrapper" )) {
			return element.parent();
		}

		// wrap the element
		var props = {
				width: element.outerWidth(true),
				height: element.outerHeight(true),
				"float": element.css( "float" )
			},
			wrapper = $( "<div></div>" )
				.addClass( "ui-effects-wrapper" )
				.css({
					fontSize: "100%",
					background: "transparent",
					border: "none",
					margin: 0,
					padding: 0
				}),
			// Store the size in case width/height are defined in % - Fixes #5245
			size = {
				width: element.width(),
				height: element.height()
			},
			active = document.activeElement;

		// support: Firefox
		// Firefox incorrectly exposes anonymous content
		// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
		try {
			active.id;
		} catch( e ) {
			active = document.body;
		}

		element.wrap( wrapper );

		// Fixes #7595 - Elements lose focus when wrapped.
		if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
			$( active ).focus();
		}

		wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element

		// transfer positioning properties to the wrapper
		if ( element.css( "position" ) === "static" ) {
			wrapper.css({ position: "relative" });
			element.css({ position: "relative" });
		} else {
			$.extend( props, {
				position: element.css( "position" ),
				zIndex: element.css( "z-index" )
			});
			$.each([ "top", "left", "bottom", "right" ], function(i, pos) {
				props[ pos ] = element.css( pos );
				if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
					props[ pos ] = "auto";
				}
			});
			element.css({
				position: "relative",
				top: 0,
				left: 0,
				right: "auto",
				bottom: "auto"
			});
		}
		element.css(size);

		return wrapper.css( props ).show();
	},

	removeWrapper: function( element ) {
		var active = document.activeElement;

		if ( element.parent().is( ".ui-effects-wrapper" ) ) {
			element.parent().replaceWith( element );

			// Fixes #7595 - Elements lose focus when wrapped.
			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
				$( active ).focus();
			}
		}


		return element;
	},

	setTransition: function( element, list, factor, value ) {
		value = value || {};
		$.each( list, function( i, x ) {
			var unit = element.cssUnit( x );
			if ( unit[ 0 ] > 0 ) {
				value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
			}
		});
		return value;
	}
});

// return an effect options object for the given parameters:
function _normalizeArguments( effect, options, speed, callback ) {

	// allow passing all optinos as the first parameter
	if ( $.isPlainObject( effect ) ) {
		options = effect;
		effect = effect.effect;
	}

	// convert to an object
	effect = { effect: effect };

	// catch (effect)
	if ( options === undefined ) {
		options = {};
	}

	// catch (effect, callback)
	if ( $.isFunction( options ) ) {
		callback = options;
		speed = null;
		options = {};
	}

	// catch (effect, speed, ?)
	if ( typeof options === "number" || $.fx.speeds[ options ] ) {
		callback = speed;
		speed = options;
		options = {};
	}

	// catch (effect, options, callback)
	if ( $.isFunction( speed ) ) {
		callback = speed;
		speed = null;
	}

	// add options to effect
	if ( options ) {
		$.extend( effect, options );
	}

	speed = speed || options.duration;
	effect.duration = $.fx.off ? 0 :
		typeof speed === "number" ? speed :
		speed in $.fx.speeds ? $.fx.speeds[ speed ] :
		$.fx.speeds._default;

	effect.complete = callback || options.complete;

	return effect;
}

function standardSpeed( speed ) {
	// valid standard speeds
	if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
		return true;
	}

	// invalid strings - treat as "normal" speed
	if ( typeof speed === "string" && !$.effects.effect[ speed ] ) {
		// TODO: remove in 2.0 (#7115)
		if ( backCompat && $.effects[ speed ] ) {
			return false;
		}
		return true;
	}

	return false;
}

$.fn.extend({
	effect: function( effect, options, speed, callback ) {
		var args = _normalizeArguments.apply( this, arguments ),
			mode = args.mode,
			queue = args.queue,
			effectMethod = $.effects.effect[ args.effect ],

			// DEPRECATED: remove in 2.0 (#7115)
			oldEffectMethod = !effectMethod && backCompat && $.effects[ args.effect ];

		if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) {
			// delegate to the original method (e.g., .show()) if possible
			if ( mode ) {
				return this[ mode ]( args.duration, args.complete );
			} else {
				return this.each( function() {
					if ( args.complete ) {
						args.complete.call( this );
					}
				});
			}
		}

		function run( next ) {
			var elem = $( this ),
				complete = args.complete,
				mode = args.mode;

			function done() {
				if ( $.isFunction( complete ) ) {
					complete.call( elem[0] );
				}
				if ( $.isFunction( next ) ) {
					next();
				}
			}

			// if the element is hiddden and mode is hide,
			// or element is visible and mode is show
			if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
				done();
			} else {
				effectMethod.call( elem[0], args, done );
			}
		}

		// TODO: remove this check in 2.0, effectMethod will always be true
		if ( effectMethod ) {
			return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
		} else {
			// DEPRECATED: remove in 2.0 (#7115)
			return oldEffectMethod.call(this, {
				options: args,
				duration: args.duration,
				callback: args.complete,
				mode: args.mode
			});
		}
	},

	_show: $.fn.show,
	show: function( speed ) {
		if ( standardSpeed( speed ) ) {
			return this._show.apply( this, arguments );
		} else {
			var args = _normalizeArguments.apply( this, arguments );
			args.mode = "show";
			return this.effect.call( this, args );
		}
	},

	_hide: $.fn.hide,
	hide: function( speed ) {
		if ( standardSpeed( speed ) ) {
			return this._hide.apply( this, arguments );
		} else {
			var args = _normalizeArguments.apply( this, arguments );
			args.mode = "hide";
			return this.effect.call( this, args );
		}
	},

	// jQuery core overloads toggle and creates _toggle
	__toggle: $.fn.toggle,
	toggle: function( speed ) {
		if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) {
			return this.__toggle.apply( this, arguments );
		} else {
			var args = _normalizeArguments.apply( this, arguments );
			args.mode = "toggle";
			return this.effect.call( this, args );
		}
	},

	// helper functions
	cssUnit: function(key) {
		var style = this.css( key ),
			val = [];

		$.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
			if ( style.indexOf( unit ) > 0 ) {
				val = [ parseFloat( style ), unit ];
			}
		});
		return val;
	}
});

})();

/******************************************************************************/
/*********************************** EASING ***********************************/
/******************************************************************************/

(function() {

// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)

var baseEasings = {};

$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
	baseEasings[ name ] = function( p ) {
		return Math.pow( p, i + 2 );
	};
});

$.extend( baseEasings, {
	Sine: function ( p ) {
		return 1 - Math.cos( p * Math.PI / 2 );
	},
	Circ: function ( p ) {
		return 1 - Math.sqrt( 1 - p * p );
	},
	Elastic: function( p ) {
		return p === 0 || p === 1 ? p :
			-Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
	},
	Back: function( p ) {
		return p * p * ( 3 * p - 2 );
	},
	Bounce: function ( p ) {
		var pow2,
			bounce = 4;

		while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
		return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
	}
});

$.each( baseEasings, function( name, easeIn ) {
	$.easing[ "easeIn" + name ] = easeIn;
	$.easing[ "easeOut" + name ] = function( p ) {
		return 1 - easeIn( 1 - p );
	};
	$.easing[ "easeInOut" + name ] = function( p ) {
		return p < 0.5 ?
			easeIn( p * 2 ) / 2 :
			1 - easeIn( p * -2 + 2 ) / 2;
	};
});

})();

})(jQuery));
(function( $, undefined ) {

var rvertical = /up|down|vertical/,
	rpositivemotion = /up|left|vertical|horizontal/;

$.effects.effect.blind = function( o, done ) {
	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		direction = o.direction || "up",
		vertical = rvertical.test( direction ),
		ref = vertical ? "height" : "width",
		ref2 = vertical ? "top" : "left",
		motion = rpositivemotion.test( direction ),
		animation = {},
		show = mode === "show",
		wrapper, distance, margin;

	// if already wrapped, the wrapper's properties are my property. #6245
	if ( el.parent().is( ".ui-effects-wrapper" ) ) {
		$.effects.save( el.parent(), props );
	} else {
		$.effects.save( el, props );
	}
	el.show();
	wrapper = $.effects.createWrapper( el ).css({
		overflow: "hidden"
	});

	distance = wrapper[ ref ]();
	margin = parseFloat( wrapper.css( ref2 ) ) || 0;

	animation[ ref ] = show ? distance : 0;
	if ( !motion ) {
		el
			.css( vertical ? "bottom" : "right", 0 )
			.css( vertical ? "top" : "left", "auto" )
			.css({ position: "absolute" });

		animation[ ref2 ] = show ? margin : distance + margin;
	}

	// start at 0 if we are showing
	if ( show ) {
		wrapper.css( ref, 0 );
		if ( ! motion ) {
			wrapper.css( ref2, margin + distance );
		}
	}

	// Animate
	wrapper.animate( animation, {
		duration: o.duration,
		easing: o.easing,
		queue: false,
		complete: function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});

};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.bounce = function( o, done ) {
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],

		// defaults:
		mode = $.effects.setMode( el, o.mode || "effect" ),
		hide = mode === "hide",
		show = mode === "show",
		direction = o.direction || "up",
		distance = o.distance,
		times = o.times || 5,

		// number of internal animations
		anims = times * 2 + ( show || hide ? 1 : 0 ),
		speed = o.duration / anims,
		easing = o.easing,

		// utility:
		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
		motion = ( direction === "up" || direction === "left" ),
		i,
		upAnim,
		downAnim,

		// we will need to re-assemble the queue to stack our animations in place
		queue = el.queue(),
		queuelen = queue.length;

	// Avoid touching opacity to prevent clearType and PNG issues in IE
	if ( show || hide ) {
		props.push( "opacity" );
	}

	$.effects.save( el, props );
	el.show();
	$.effects.createWrapper( el ); // Create Wrapper

	// default distance for the BIGGEST bounce is the outer Distance / 3
	if ( !distance ) {
		distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
	}

	if ( show ) {
		downAnim = { opacity: 1 };
		downAnim[ ref ] = 0;

		// if we are showing, force opacity 0 and set the initial position
		// then do the "first" animation
		el.css( "opacity", 0 )
			.css( ref, motion ? -distance * 2 : distance * 2 )
			.animate( downAnim, speed, easing );
	}

	// start at the smallest distance if we are hiding
	if ( hide ) {
		distance = distance / Math.pow( 2, times - 1 );
	}

	downAnim = {};
	downAnim[ ref ] = 0;
	// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
	for ( i = 0; i < times; i++ ) {
		upAnim = {};
		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;

		el.animate( upAnim, speed, easing )
			.animate( downAnim, speed, easing );

		distance = hide ? distance * 2 : distance / 2;
	}

	// Last Bounce when Hiding
	if ( hide ) {
		upAnim = { opacity: 0 };
		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;

		el.animate( upAnim, speed, easing );
	}

	el.queue(function() {
		if ( hide ) {
			el.hide();
		}
		$.effects.restore( el, props );
		$.effects.removeWrapper( el );
		done();
	});

	// inject all the animations we just queued to be first in line (after "inprogress")
	if ( queuelen > 1) {
		queue.splice.apply( queue,
			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
	}
	el.dequeue();

};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.clip = function( o, done ) {
	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",
		direction = o.direction || "vertical",
		vert = direction === "vertical",
		size = vert ? "height" : "width",
		position = vert ? "top" : "left",
		animation = {},
		wrapper, animate, distance;

	// Save & Show
	$.effects.save( el, props );
	el.show();

	// Create Wrapper
	wrapper = $.effects.createWrapper( el ).css({
		overflow: "hidden"
	});
	animate = ( el[0].tagName === "IMG" ) ? wrapper : el;
	distance = animate[ size ]();

	// Shift
	if ( show ) {
		animate.css( size, 0 );
		animate.css( position, distance / 2 );
	}

	// Create Animation Object:
	animation[ size ] = show ? distance : 0;
	animation[ position ] = show ? 0 : distance / 2;

	// Animate
	animate.animate( animation, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( !show ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});

};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.drop = function( o, done ) {

	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",
		direction = o.direction || "left",
		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
		motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg",
		animation = {
			opacity: show ? 1 : 0
		},
		distance;

	// Adjust
	$.effects.save( el, props );
	el.show();
	$.effects.createWrapper( el );

	distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2;

	if ( show ) {
		el
			.css( "opacity", 0 )
			.css( ref, motion === "pos" ? -distance : distance );
	}

	// Animation
	animation[ ref ] = ( show ?
		( motion === "pos" ? "+=" : "-=" ) :
		( motion === "pos" ? "-=" : "+=" ) ) +
		distance;

	// Animate
	el.animate( animation, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});
};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.explode = function( o, done ) {

	var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
		cells = rows,
		el = $( this ),
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",

		// show and then visibility:hidden the element before calculating offset
		offset = el.show().css( "visibility", "hidden" ).offset(),

		// width and height of a piece
		width = Math.ceil( el.outerWidth() / cells ),
		height = Math.ceil( el.outerHeight() / rows ),
		pieces = [],

		// loop
		i, j, left, top, mx, my;

	// children animate complete:
	function childComplete() {
		pieces.push( this );
		if ( pieces.length === rows * cells ) {
			animComplete();
		}
	}

	// clone the element for each row and cell.
	for( i = 0; i < rows ; i++ ) { // ===>
		top = offset.top + i * height;
		my = i - ( rows - 1 ) / 2 ;

		for( j = 0; j < cells ; j++ ) { // |||
			left = offset.left + j * width;
			mx = j - ( cells - 1 ) / 2 ;

			// Create a clone of the now hidden main element that will be absolute positioned
			// within a wrapper div off the -left and -top equal to size of our pieces
			el
				.clone()
				.appendTo( "body" )
				.wrap( "<div></div>" )
				.css({
					position: "absolute",
					visibility: "visible",
					left: -j * width,
					top: -i * height
				})

			// select the wrapper - make it overflow: hidden and absolute positioned based on
			// where the original was located +left and +top equal to the size of pieces
				.parent()
				.addClass( "ui-effects-explode" )
				.css({
					position: "absolute",
					overflow: "hidden",
					width: width,
					height: height,
					left: left + ( show ? mx * width : 0 ),
					top: top + ( show ? my * height : 0 ),
					opacity: show ? 0 : 1
				}).animate({
					left: left + ( show ? 0 : mx * width ),
					top: top + ( show ? 0 : my * height ),
					opacity: show ? 1 : 0
				}, o.duration || 500, o.easing, childComplete );
		}
	}

	function animComplete() {
		el.css({
			visibility: "visible"
		});
		$( pieces ).remove();
		if ( !show ) {
			el.hide();
		}
		done();
	}
};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.fade = function( o, done ) {
	var el = $( this ),
		mode = $.effects.setMode( el, o.mode || "toggle" );

	el.animate({
		opacity: mode
	}, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: done
	});
};

})( jQuery );
(function( $, undefined ) {

$.effects.effect.fold = function( o, done ) {

	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",
		hide = mode === "hide",
		size = o.size || 15,
		percent = /([0-9]+)%/.exec( size ),
		horizFirst = !!o.horizFirst,
		widthFirst = show !== horizFirst,
		ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
		duration = o.duration / 2,
		wrapper, distance,
		animation1 = {},
		animation2 = {};

	$.effects.save( el, props );
	el.show();

	// Create Wrapper
	wrapper = $.effects.createWrapper( el ).css({
		overflow: "hidden"
	});
	distance = widthFirst ?
		[ wrapper.width(), wrapper.height() ] :
		[ wrapper.height(), wrapper.width() ];

	if ( percent ) {
		size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
	}
	if ( show ) {
		wrapper.css( horizFirst ? {
			height: 0,
			width: size
		} : {
			height: size,
			width: 0
		});
	}

	// Animation
	animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
	animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;

	// Animate
	wrapper
		.animate( animation1, duration, o.easing )
		.animate( animation2, duration, o.easing, function() {
			if ( hide ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		});

};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.highlight = function( o, done ) {
	var elem = $( this ),
		props = [ "backgroundImage", "backgroundColor", "opacity" ],
		mode = $.effects.setMode( elem, o.mode || "show" ),
		animation = {
			backgroundColor: elem.css( "backgroundColor" )
		};

	if (mode === "hide") {
		animation.opacity = 0;
	}

	$.effects.save( elem, props );
	
	elem
		.show()
		.css({
			backgroundImage: "none",
			backgroundColor: o.color || "#ffff99"
		})
		.animate( animation, {
			queue: false,
			duration: o.duration,
			easing: o.easing,
			complete: function() {
				if ( mode === "hide" ) {
					elem.hide();
				}
				$.effects.restore( elem, props );
				done();
			}
		});
};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.pulsate = function( o, done ) {
	var elem = $( this ),
		mode = $.effects.setMode( elem, o.mode || "show" ),
		show = mode === "show",
		hide = mode === "hide",
		showhide = ( show || mode === "hide" ),

		// showing or hiding leaves of the "last" animation
		anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
		duration = o.duration / anims,
		animateTo = 0,
		queue = elem.queue(),
		queuelen = queue.length,
		i;

	if ( show || !elem.is(":visible")) {
		elem.css( "opacity", 0 ).show();
		animateTo = 1;
	}

	// anims - 1 opacity "toggles"
	for ( i = 1; i < anims; i++ ) {
		elem.animate({
			opacity: animateTo
		}, duration, o.easing );
		animateTo = 1 - animateTo;
	}

	elem.animate({
		opacity: animateTo
	}, duration, o.easing);

	elem.queue(function() {
		if ( hide ) {
			elem.hide();
		}
		done();
	});

	// We just queued up "anims" animations, we need to put them next in the queue
	if ( queuelen > 1 ) {
		queue.splice.apply( queue,
			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
	}
	elem.dequeue();
};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.puff = function( o, done ) {
	var elem = $( this ),
		mode = $.effects.setMode( elem, o.mode || "hide" ),
		hide = mode === "hide",
		percent = parseInt( o.percent, 10 ) || 150,
		factor = percent / 100,
		original = {
			height: elem.height(),
			width: elem.width()
		};

	$.extend( o, {
		effect: "scale",
		queue: false,
		fade: true,
		mode: mode,
		complete: done,
		percent: hide ? percent : 100,
		from: hide ?
			original :
			{
				height: original.height * factor,
				width: original.width * factor
			}
	});

	elem.effect( o );
};

$.effects.effect.scale = function( o, done ) {

	// Create element
	var el = $( this ),
		options = $.extend( true, {}, o ),
		mode = $.effects.setMode( el, o.mode || "effect" ),
		percent = parseInt( o.percent, 10 ) ||
			( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ),
		direction = o.direction || "both",
		origin = o.origin,
		original = {
			height: el.height(),
			width: el.width(),
			outerHeight: el.outerHeight(),
			outerWidth: el.outerWidth()
		},
		factor = {
			y: direction !== "horizontal" ? (percent / 100) : 1,
			x: direction !== "vertical" ? (percent / 100) : 1
		};

	// We are going to pass this effect to the size effect:
	options.effect = "size";
	options.queue = false;
	options.complete = done;

	// Set default origin and restore for show/hide
	if ( mode !== "effect" ) {
		options.origin = origin || ["middle","center"];
		options.restore = true;
	}

	options.from = o.from || ( mode === "show" ? { height: 0, width: 0 } : original );
	options.to = {
		height: original.height * factor.y,
		width: original.width * factor.x,
		outerHeight: original.outerHeight * factor.y,
		outerWidth: original.outerWidth * factor.x
	};

	// Fade option to support puff
	if ( options.fade ) {
		if ( mode === "show" ) {
			options.from.opacity = 0;
			options.to.opacity = 1;
		}
		if ( mode === "hide" ) {
			options.from.opacity = 1;
			options.to.opacity = 0;
		}
	}

	// Animate
	el.effect( options );

};

$.effects.effect.size = function( o, done ) {

	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ],

		// Always restore
		props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ],

		// Copy for children
		props2 = [ "width", "height", "overflow" ],
		cProps = [ "fontSize" ],
		vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
		hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],

		// Set options
		mode = $.effects.setMode( el, o.mode || "effect" ),
		restore = o.restore || mode !== "effect",
		scale = o.scale || "both",
		origin = o.origin || [ "middle", "center" ],
		original, baseline, factor,
		position = el.css( "position" );

	if ( mode === "show" ) {
		el.show();
	}
	original = {
		height: el.height(),
		width: el.width(),
		outerHeight: el.outerHeight(),
		outerWidth: el.outerWidth()
	};

	el.from = o.from || original;
	el.to = o.to || original;

	// Set scaling factor
	factor = {
		from: {
			y: el.from.height / original.height,
			x: el.from.width / original.width
		},
		to: {
			y: el.to.height / original.height,
			x: el.to.width / original.width
		}
	};

	// Scale the css box
	if ( scale === "box" || scale === "both" ) {

		// Vertical props scaling
		if ( factor.from.y !== factor.to.y ) {
			props = props.concat( vProps );
			el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from );
			el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to );
		}

		// Horizontal props scaling
		if ( factor.from.x !== factor.to.x ) {
			props = props.concat( hProps );
			el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from );
			el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to );
		}
	}

	// Scale the content
	if ( scale === "content" || scale === "both" ) {

		// Vertical props scaling
		if ( factor.from.y !== factor.to.y ) {
			props = props.concat( cProps );
			el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from );
			el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to );
		}
	}

	$.effects.save( el, restore ? props : props1 );
	el.show();
	$.effects.createWrapper( el );
	el.css( "overflow", "hidden" ).css( el.from );

	// Adjust
	if (origin) { // Calculate baseline shifts
		baseline = $.effects.getBaseline( origin, original );
		el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y;
		el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x;
		el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y;
		el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x;
	}
	el.css( el.from ); // set top & left

	// Animate
	if ( scale === "content" || scale === "both" ) { // Scale the children

		// Add margins/font-size
		vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps);
		hProps = hProps.concat([ "marginLeft", "marginRight" ]);
		props2 = props.concat(vProps).concat(hProps);

		el.find( "*[width]" ).each( function(){
			var child = $( this ),
				c_original = {
					height: child.height(),
					width: child.width()
				};
			if (restore) {
				$.effects.save(child, props2);
			}

			child.from = {
				height: c_original.height * factor.from.y,
				width: c_original.width * factor.from.x
			};
			child.to = {
				height: c_original.height * factor.to.y,
				width: c_original.width * factor.to.x
			};

			// Vertical props scaling
			if ( factor.from.y !== factor.to.y ) {
				child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from );
				child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to );
			}

			// Horizontal props scaling
			if ( factor.from.x !== factor.to.x ) {
				child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from );
				child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to );
			}

			// Animate children
			child.css( child.from );
			child.animate( child.to, o.duration, o.easing, function() {

				// Restore children
				if ( restore ) {
					$.effects.restore( child, props2 );
				}
			});
		});
	}

	// Animate
	el.animate( el.to, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( el.to.opacity === 0 ) {
				el.css( "opacity", el.from.opacity );
			}
			if( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, restore ? props : props1 );
			if ( !restore ) {

				// we need to calculate our new positioning based on the scaling
				if ( position === "static" ) {
					el.css({
						position: "relative",
						top: el.to.top,
						left: el.to.left
					});
				} else {
					$.each([ "top", "left" ], function( idx, pos ) {
						el.css( pos, function( _, str ) {
							var val = parseInt( str, 10 ),
								toRef = idx ? el.to.left : el.to.top;

							// if original was "auto", recalculate the new value from wrapper
							if ( str === "auto" ) {
								return toRef + "px";
							}

							return val + toRef + "px";
						});
					});
				}
			}

			$.effects.removeWrapper( el );
			done();
		}
	});

};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.shake = function( o, done ) {

	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "effect" ),
		direction = o.direction || "left",
		distance = o.distance || 20,
		times = o.times || 3,
		anims = times * 2 + 1,
		speed = Math.round(o.duration/anims),
		ref = (direction === "up" || direction === "down") ? "top" : "left",
		positiveMotion = (direction === "up" || direction === "left"),
		animation = {},
		animation1 = {},
		animation2 = {},
		i,

		// we will need to re-assemble the queue to stack our animations in place
		queue = el.queue(),
		queuelen = queue.length;

	$.effects.save( el, props );
	el.show();
	$.effects.createWrapper( el );

	// Animation
	animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
	animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
	animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;

	// Animate
	el.animate( animation, speed, o.easing );

	// Shakes
	for ( i = 1; i < times; i++ ) {
		el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing );
	}
	el
		.animate( animation1, speed, o.easing )
		.animate( animation, speed / 2, o.easing )
		.queue(function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		});

	// inject all the animations we just queued to be first in line (after "inprogress")
	if ( queuelen > 1) {
		queue.splice.apply( queue,
			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
	}
	el.dequeue();

};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.slide = function( o, done ) {

	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
		mode = $.effects.setMode( el, o.mode || "show" ),
		show = mode === "show",
		direction = o.direction || "left",
		ref = (direction === "up" || direction === "down") ? "top" : "left",
		positiveMotion = (direction === "up" || direction === "left"),
		distance,
		animation = {};

	// Adjust
	$.effects.save( el, props );
	el.show();
	distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );

	$.effects.createWrapper( el ).css({
		overflow: "hidden"
	});

	if ( show ) {
		el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
	}

	// Animation
	animation[ ref ] = ( show ?
		( positiveMotion ? "+=" : "-=") :
		( positiveMotion ? "-=" : "+=")) +
		distance;

	// Animate
	el.animate( animation, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});
};

})(jQuery);
(function( $, undefined ) {

$.effects.effect.transfer = function( o, done ) {
	var elem = $( this ),
		target = $( o.to ),
		targetFixed = target.css( "position" ) === "fixed",
		body = $("body"),
		fixTop = targetFixed ? body.scrollTop() : 0,
		fixLeft = targetFixed ? body.scrollLeft() : 0,
		endPosition = target.offset(),
		animation = {
			top: endPosition.top - fixTop ,
			left: endPosition.left - fixLeft ,
			height: target.innerHeight(),
			width: target.innerWidth()
		},
		startPosition = elem.offset(),
		transfer = $( '<div class="ui-effects-transfer"></div>' )
			.appendTo( document.body )
			.addClass( o.className )
			.css({
				top: startPosition.top - fixTop ,
				left: startPosition.left - fixLeft ,
				height: elem.innerHeight(),
				width: elem.innerWidth(),
				position: targetFixed ? "fixed" : "absolute"
			})
			.animate( animation, o.duration, o.easing, function() {
				transfer.remove();
				done();
			});
};

})(jQuery);
});

define('ui/tab',[
	'aloha/core',
	'jquery',
	'ui/container',
	'ui/component',
	'PubSub',
	'jqueryui'
], function (
	Aloha,
	$,
	Container,
	Component,
	PubSub
) {
	

	var idCounter = 0;
	var slottedComponents = {};

	function hasVisibleComponents(tab){
		var i, slot, component, hasVisible = false;

		// the problem is, a container component had a button to expand the options
		if( $(
				'button:not(.aloha-multisplit-toggle)',
				tab.panel
			).length !== 0
		){
			hasVisible = true;
		}

		/*
		// @todo this algorithm must be enhanced, asking each component if is visible
		// this is the longest aproach, I iterate the components registered in
		// this tab
		for(i = 0; i < tab._slotsList.length; i++){
			if(undefined !== slottedComponents[slotName]){
				if($(
						'button:not(.aloha-multisplit-toggle)',
						slottedComponents[slotName].element
					).length !== 0
				){
					hasVisible = true;
					break;
				}
			}
		}
		*/
		return hasVisible;
	}

	/**
	 * Defines a Container object that represents a collection of related
	 * component groups to be rendered together on the toolbar.  Tabs are
	 * organized by feature and functionality so that related controls can be
	 * brought in and out of view depending on whether they are
	 * appropriate for a given user context.
	 *
	 * Tabs can be defined declaritively in the Aloha configuration in the
	 * following manner:
	 *
	 *    Aloha.settings.toolbar: [
	 *      {
	 *         label: 'Lists',
	 *         showOn: 'ul,ol,*.parent(.aloha-editable ul,.aloha-editable ol)',
	 *         components: [ [ 'orderedList', 'unorderedList' ] ]
	 *      }
	 *    ]
	 *
	 * Alternatively, tabs can also be created imperatively in this way:
	 * new Tab( options, components ).
	 *
	 * @class
	 * @extends {Container}
	 */
	var Tab = Container.extend({

		_elemBySlot: null,
		_groupBySlot: null,
		_groupByComponent: null,

		/**
		 * All that this constructor does is save the components array into a
		 * local variable, to be used during instantialization.
		 *
		 * @param {object} settings
		 * @param {Array.<Array<string>>} components
		 * @constructor
		 */
		_constructor: function (context, settings, components) {
			var thisTab = this,
			    i, j,
			    elem,
			    groupedComponents,
			    group,
			    groupProps,
			    componentName;

			this._elemBySlot = {};
			this._groupBySlot = {};
			this._groupByComponent = {};
			this._slotsList = [];
			this._super(context, settings);

			this.container = settings.container;
			this.list = this.container.data('list');
			this.panels = this.container.data('panels');
			this.id = 'tab-ui-container-' + (++idCounter);
			this.panel = $('<div>', {id : this.id, 'unselectable': 'on'});
			this.handle = $('<li><a href="' + location.href.replace(/#.*$/, '') + '#' + this.id + '">' +
				settings.label + '</a></li>');

			for (i = 0; i < components.length; i++) {
				if (typeof components[i] === 'string') {
					if (1 === components[i].length && components[i].charCodeAt(0) === 10) {
						this.panel.append('<div>', {'unselectable': 'on'});
					} else {
						elem = $('<span>', {'unselectable': 'on'});
						this._elemBySlot[components[i]] = elem;
						this.panel.append(elem);
					}
				} else {
					// Hide the group until the first button is adopted into it.
					group = $('<div>', {
						'class': 'aloha-ui-component-group aloha-ui-hidden',
						'unselectable': 'on'
					}).appendTo(this.panel);
					groupProps = {element: group, visibleCounter: 0};
					groupedComponents = components[i];
					for (j = 0; j < groupedComponents.length; j++) {
						this._groupBySlot[groupedComponents[j]] = groupProps;
						if (groupedComponents[j] &&
							1 === groupedComponents[j].length &&
						    groupedComponents[j].charCodeAt(0) === 10) {
							group.append($('<div>', {'unselectable': 'on'}));
						} else {
							componentName = groupedComponents[j];
							elem = $('<span>', {'unselectable': 'on'});
							this._elemBySlot[groupedComponents[j]] = elem;
							group.append(elem);
						}
					}
				}
			}

			this.panel.append($('<div>', {'class': 'aloha-ui-clear', 'unselectable': 'on'}));
			this.handle.appendTo(this.list);
			this.panel.appendTo(this.panels);
			this.container.tabs('refresh');

			var alohaTabs = settings.container.data('aloha-tabs');
			this.index = alohaTabs.length;
			alohaTabs.push(this);
		},

		adoptInto: function(slot, component) {
			var elem = this._elemBySlot[slot],
			    group;
			if (!elem) {
				return false;
			}
			slottedComponents[slot] = component;
			component.adoptParent(this);
			elem.append(component.element);
			group = this._groupBySlot[slot];
			this._slotsList.push(slot);
			if (group) {
				this._groupByComponent[component.id] = group;
				if (component.isVisible()) {
					if (!group.visibleCounter) {
						group.element.removeClass('aloha-ui-hidden');
					}
					group.visibleCounter += 1;
				}
			}
			return true;
		},

		foreground: function() {
			this.container.tabs('select', this.index);
		},

		childForeground: function(childComponent) {
			this.foreground();
		},

		hasVisibleComponents: function () {
			var siblings = this._elemBySlot;
			var slot;
			for (slot in siblings) {
				if (siblings.hasOwnProperty(slot) && slottedComponents[slot]) {
					if (slottedComponents[slot].visible) {
						return true;
					}
				}
			}
			return false;
		},

		childVisible: function(childComponent, visible) {
			if (visible) {
				childComponent.container.show();
			} else if (!childComponent.container.hasVisibleComponents()) {
				childComponent.container.hide();
			}
			var group = this._groupByComponent[childComponent.id];
			if (!group) {
				return;
			}
			if (visible) {
				if (0 === group.visibleCounter) {
					group.element.removeClass('aloha-ui-hidden');
				}
				group.visibleCounter += 1;
			} else {
				group.visibleCounter -= 1;
				if (0 === group.visibleCounter) {
					group.element.addClass('aloha-ui-hidden');
				}
			}
		},

		/**
		 * @override
		 */
		show: function() {
			if (!this.list.children().length || !hasVisibleComponents(this)) {
				return;
			}

			this.handle.show();
			this.visible = true;

			// Hiding all tabs may hide the toolbar, so showing the
			// first tab again must also show the toolbar.
			this.container.show();

			// If no tabs are selected, then select the tab which was just shown.
			if (   !this.container.find('.ui-tabs-active').length
			    ||  this.container.tabs('option', 'selected') === this.index) {
				this.foreground();
			}
		},

		/**
		 * @override
		 */
		hide: function() {
			var tabs = this.list.children();
			if ( 0 === tabs.length ) {
				return;
			}
			this.handle.hide();
			this.visible = false;

			// If the tab we just hid was the selected tab, then we need to
			// select another tab in its stead.  We will select the first
			// visible tab we find, or else we deselect all tabs.
			if ( this.index === this.container.tabs( 'option', 'selected' ) ) {
				tabs = this.container.data( 'aloha-tabs' );

				var i;
				for ( i = 0; i < tabs.length; ++i ) {
					if ( tabs[ i ].visible ) {
						this.container.tabs( 'select', i );
						return;
					}
				}

				// This does not work...
				// this.container.tabs( 'select', -1 );

				// Why do we remove this class?
				this.handle.removeClass( 'ui-tabs-active' );

				// It doesn't make any sense to leave the toolbar
				// visible after all tabs have been hidden.
				this.container.hide();
			}
		}

	});

	$.extend(Tab, {

		/**
		 * Creates holding elements for jQuery UI Tabs for a surface.
		 *
		 * @static
		 * @return {jQuery.<HTMLElement>} The holder container on which we
		 *                                invoke jQuery UI Tabs once it is
		 *                                populated with tab containers.
		 */
		createContainer: function () {
			var $container = $('<div>', {'unselectable': 'on'});
			var $list = $('<ul>', {'unselectable': 'on'}).appendTo($container);
			var $panels = $('<div>', {'unselectable': 'on'}).appendTo($container);

			$container
				.data('list', $list)
				.data('panels', $panels)
				.data('aloha-tabs', [])
				.tabs({
					select: function (event, ui) {
						var tabs = $container.data('aloha-tabs');
						$container.data('aloha-active-container', tabs[ui.index]);
						PubSub.pub('aloha.ui.container.selected', {data: tabs[ui.index]});
					}
				});

			return $container;
		}
	});

	return Tab;
});

define('ui/subguarded',[
	'aloha/core',
	'jquery',
	'PubSub'
], function (
	Aloha,
	jQuery,
	PubSub
) {
	

	/**
	 * The last uid that was was used to uniquely identify a function.
	 * NB: Make sure to increment this counter before assigning it to a new
	 *     function.
	 *
	 * @type {numder}
	 * @private
	 */
	var alohaUid = 0;

	/**
	 * A spares array, where arguments lists are indexed against the alohaUid of
	 * the guarded dispatch function with which they were registered with.
	 *
	 * @type {object.<number, array>}
	 * @private
	 */
	var registeredArguments = {};

	/**
	 * A spares array of guarded dispatch functions indexed against their
	 * alohaUid.  Each entry in this array will correspond with an entry in the
	 * `registeredArguments' map which is exists on the same index.
	 *
	 * @type {object.<number, array>}
	 * @private
	 */
	var registeredGuards = {};

	/**
	 * For a given function, will derive its unique identifing number.  Be
	 * aware that although this is a "getter" function, it will mutate the
	 * given function, and the closure variable `alohaUid' if the given
	 * function has not had a unique id set to it.
	 *
	 * @param {function} func Function whose uid is to be retreived.
	 * @return {number} The alohaUid property that has been assigned to the
	 *                  given function.
	 */
	function getUid(func) {
		if (!func.alohaUid) {
			func.alohaUid = ++alohaUid;
		}
		return func.alohaUid;
	}

	/**
	 * Retrieves a list of all guard functions that are registered for the
	 * given event.
	 *
	 * @param {string} event The name of the event whose guard functions are to
	 *                       be to retreive.
	 * @return {array.<function>} A list of guarded dispatch functions that
	 *                            were registered to handle the given event.
	 */
	function getRegisteredGuards(event) {
		return registeredGuards[event] || [];
	}

	/**
	 * Retrieves a list of argument lists that were registerd with the given
	 * guard function.
	 *
	 * @paran {function} guard A function that is to filter and dispatch.
	 * @return {array.<*>} A list of arguments list.
	 */
	function getArguments(guard) {
		return registeredArguments[guard.alohaUid] || [];
	}

	/**
	 * Registers the given arguments list against the provided guard function.
	 *
	 * @paran {function} guard A function that is to filter and dispatch a
	 *                         variable number of callbacks.
	 * @return {array.<*>} The list of all registed arguments lists that
	 *                     correspond with the given guard.
	 */
	function registerArguments(guard, args) {
		if (!registeredArguments[guard.alohaUid]) {
			registeredArguments[guard.alohaUid] = [];
		}
		registeredArguments[guard.alohaUid].push(args);
		return registeredArguments[guard.alohaUid];
	}

	/**
	 * Registers the given guard function to the given event.
	 *
	 * @paran {string} event The event on which to invoke the guard.
	 * @paran {function} guard A function that is to filter and dispatch a
	 *                         variable number of callbacks.
	 * @return {array.<*>} The list of all registed guard functions that
	 *                     correspond with the given guard.
	 */
	function registerGuard(event, guard) {
		if (!registeredGuards[event]) {
			registeredGuards[event] = [];
		}
		registeredGuards[event].push(guard);
		return registeredGuards[event];
	}

	/**
	 * Process a guard functions that have been registered on the given event
	 * when the event is triggered.
	 *
	 * @param {Event} event Name of the event.
	 * @param ... any other arguments passed on to the guard function
	 */
	function trigger(event) {
		var guards = getRegisteredGuards(event);
		var i;
		for (i = 0; i < guards.length; i++) {
			guards[i].apply(null, [getArguments(guards[i])].concat(arguments));
		}
	}

	/**
	 * Provides a mechanism to register event handlers that are filtered and
	 * dispatched through a guard function.
	 *
	 * All arguments following the guard parameter will be passed to the guard
	 * function in a list containing a tuple of arguments.  It is expected that
	 * one of the arguments will be a callback function that will be call if
	 * the other arguments pass the condition implemented in the guarded
	 * dispatch function.
	 *
	 * USAGE:
	 *    sub(event, dispatch [, ... ])
	 *
	 * @param {string} event
	 * @param {function(array.<array.<*...>>)} guard A function that will be
	 *                                               invoked when the specified
	 *                                               event is fired.  This
	 *                                               function will receive a
	 *                                               array consisting of
	 *                                               arguments tuples, followed
	 *                                               by the the arguments that
	 *                                               are received from the
	 *                                               event.
	 * @param {*...} args A variable number of arguments which will be passed
	 *                    as a list in a list to the dispatch function.
	 */
	function sub() {
		var args = Array.prototype.slice.call(arguments);
		var events = args.shift();
		if (typeof events === 'string') {
			events = [events];
		}
		var guard = args.shift();
		getUid(guard);
		registerArguments(guard, args);
		var i;
		var event;
		var bindHandler = function ($event, range, nativeEvent) {
			trigger(event, $event, range, nativeEvent);
		};
		var pubsubHandler = function () {
			trigger(event);
		};
		for (i = 0; i < events.length; i++) {
			event = events[i];
			registerGuard(event, guard);
			Aloha.bind(event, bindHandler);
			PubSub.sub(event, pubsubHandler);
		}
	}

	return sub;

});

define('vendor/amplify.store',['util/json2'],function(){
var amplify = {};

/*!
 * Amplify Store - Persistent Client-Side Storage 1.1.0
 * 
 * Copyright 2011 appendTo LLC. (http://appendto.com/team)
 * Dual licensed under the MIT or GPL licenses.
 * http://appendto.com/open-source-licenses
 * 
 * http://amplifyjs.com
 */
(function( amplify, undefined ) {

var store = amplify.store = function( key, value, options, type ) {
	var type = store.type;
	if ( options && options.type && options.type in store.types ) {
		type = options.type;
	}
	return store.types[ type ]( key, value, options || {} );
};

store.types = {};
store.type = null;
store.addType = function( type, storage ) {
	if ( !store.type ) {
		store.type = type;
	}

	store.types[ type ] = storage;
	store[ type ] = function( key, value, options ) {
		options = options || {};
		options.type = type;
		return store( key, value, options );
	};
}
store.error = function() {
	return "amplify.store quota exceeded"; 
};

var rprefix = /^__amplify__/;
function createFromStorageInterface( storageType, storage ) {
	store.addType( storageType, function( key, value, options ) {
		var storedValue, parsed, i, remove,
			ret = value,
			now = (new Date()).getTime();

		if ( !key ) {
			ret = {};
			remove = [];
			i = 0;
			try {
				// accessing the length property works around a localStorage bug
				// in Firefox 4.0 where the keys don't update cross-page
				// we assign to key just to avoid Closure Compiler from removing
				// the access as "useless code"
				// https://bugzilla.mozilla.org/show_bug.cgi?id=662511
				key = storage.length;

				while ( key = storage.key( i++ ) ) {
					if ( rprefix.test( key ) ) {
						parsed = JSON.parse( storage.getItem( key ) );
						if ( parsed.expires && parsed.expires <= now ) {
							remove.push( key );
						} else {
							ret[ key.replace( rprefix, "" ) ] = parsed.data;
						}
					}
				}
				while ( key = remove.pop() ) {
					storage.removeItem( key );
				}
			} catch ( error ) {}
			return ret;
		}

		// protect against name collisions with direct storage
		key = "__amplify__" + key;

		if ( value === undefined ) {
			storedValue = storage.getItem( key );
			parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
			if ( parsed.expires && parsed.expires <= now ) {
				storage.removeItem( key );
			} else {
				return parsed.data;
			}
		} else {
			if ( value === null ) {
				storage.removeItem( key );
			} else {
				parsed = JSON.stringify({
					data: value,
					expires: options.expires ? now + options.expires : null
				});
				try {
					storage.setItem( key, parsed );
				// quota exceeded
				} catch( error ) {
					// expire old data and try again
					store[ storageType ]();
					try {
						storage.setItem( key, parsed );
					} catch( error ) {
						throw store.error();
					}
				}
			}
		}

		return ret;
	});
}

// localStorage + sessionStorage
// IE 8+, Firefox 3.5+, Safari 4+, Chrome 4+, Opera 10.5+, iPhone 2+, Android 2+
for ( var webStorageType in { localStorage: 1, sessionStorage: 1 } ) {
	// try/catch for file protocol in Firefox
	try {
		if ( window[ webStorageType ].getItem ) {
			createFromStorageInterface( webStorageType, window[ webStorageType ] );
		}
	} catch( e ) {}
}

// globalStorage
// non-standard: Firefox 2+
// https://developer.mozilla.org/en/dom/storage#globalStorage
if ( window.globalStorage ) {
	// try/catch for file protocol in Firefox
	try {
		createFromStorageInterface( "globalStorage",
			window.globalStorage[ window.location.hostname ] );
		// Firefox 2.0 and 3.0 have sessionStorage and globalStorage
		// make sure we default to globalStorage
		// but don't default to globalStorage in 3.5+ which also has localStorage
		if ( store.type === "sessionStorage" ) {
			store.type = "globalStorage";
		}
	} catch( e ) {}
}

// userData
// non-standard: IE 5+
// http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx
(function() {
	// IE 9 has quirks in userData that are a huge pain
	// rather than finding a way to detect these quirks
	// we just don't register userData if we have localStorage
	if ( store.types.localStorage ) {
		return;
	}

	// append to html instead of body so we can do this from the head
	var div = document.createElement( "div" ),
		attrKey = "amplify";
	div.style.display = "none";
	document.getElementsByTagName( "head" )[ 0 ].appendChild( div );

	// we can't feature detect userData support
	// so just try and see if it fails
	// surprisingly, even just adding the behavior isn't enough for a failure
	// so we need to load the data as well
	try {
		div.addBehavior( "#default#userdata" );
		div.load( attrKey );
	} catch( e ) {
		div.parentNode.removeChild( div );
		return;
	}

	store.addType( "userData", function( key, value, options ) {
		div.load( attrKey );
		var attr, parsed, prevValue, i, remove,
			ret = value,
			now = (new Date()).getTime();

		if ( !key ) {
			ret = {};
			remove = [];
			i = 0;
			while ( attr = div.XMLDocument.documentElement.attributes[ i++ ] ) {
				parsed = JSON.parse( attr.value );
				if ( parsed.expires && parsed.expires <= now ) {
					remove.push( attr.name );
				} else {
					ret[ attr.name ] = parsed.data;
				}
			}
			while ( key = remove.pop() ) {
				div.removeAttribute( key );
			}
			div.save( attrKey );
			return ret;
		}

		// convert invalid characters to dashes
		// http://www.w3.org/TR/REC-xml/#NT-Name
		// simplified to assume the starting character is valid
		// also removed colon as it is invalid in HTML attribute names
		key = key.replace( /[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g, "-" );

		if ( value === undefined ) {
			attr = div.getAttribute( key );
			parsed = attr ? JSON.parse( attr ) : { expires: -1 };
			if ( parsed.expires && parsed.expires <= now ) {
				div.removeAttribute( key );
			} else {
				return parsed.data;
			}
		} else {
			if ( value === null ) {
				div.removeAttribute( key );
			} else {
				// we need to get the previous value in case we need to rollback
				prevValue = div.getAttribute( key );
				parsed = JSON.stringify({
					data: value,
					expires: (options.expires ? (now + options.expires) : null)
				});
				div.setAttribute( key, parsed );
			}
		}

		try {
			div.save( attrKey );
		// quota exceeded
		} catch ( error ) {
			// roll the value back to the previous value
			if ( prevValue === null ) {
				div.removeAttribute( key );
			} else {
				div.setAttribute( key, prevValue );
			}

			// expire old data and try again
			store.userData();
			try {
				div.setAttribute( key, parsed );
				div.save( attrKey );
			} catch ( error ) {
				// roll the value back to the previous value
				if ( prevValue === null ) {
					div.removeAttribute( key );
				} else {
					div.setAttribute( key, prevValue );
				}
				throw store.error();
			}
		}
		return ret;
	});
}() );

// in-memory storage
// fallback for all browsers to enable the API even if we can't persist data
(function() {
	var memory = {},
		timeout = {};

	function copy( obj ) {
		return obj === undefined ? undefined : JSON.parse( JSON.stringify( obj ) );
	}

	store.addType( "memory", function( key, value, options ) {
		if ( !key ) {
			return copy( memory );
		}

		if ( value === undefined ) {
			return copy( memory[ key ] );
		}

		if ( timeout[ key ] ) {
			clearTimeout( timeout[ key ] );
			delete timeout[ key ];
		}

		if ( value === null ) {
			delete memory[ key ];
			return null;
		}

		memory[ key ] = value;
		if ( options.expires ) {
			timeout[ key ] = setTimeout(function() {
				delete memory[ key ];
				delete timeout[ key ];
			}, options.expires );
		}

		return value;
	});
}() );

}( amplify ) );
return amplify;
});

/* floating.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 *
 * @overview
 * Implements position and floating animation effect for UI surfaces.
 */
define('ui/floating',[
	'jquery',
	'aloha/core',
	'ui/surface',
	'ui/subguarded',
	'PubSub',
	'vendor/amplify.store',
	'util/browser'
], function (
	$,
	Aloha,
	Surface,
	subguarded,
	PubSub,
	amplifyStore,
	Browser
) {
	

	/**
	 * The distance that the floating surface should maintain from the editable
	 * it is floating to.
	 *
	 * @type {string}
	 * @const
	 */
	var DISTANCE = 10;

	/**
	 * The duration of the floating animation in milliseconds.
	 *
	 * @type {number}
	 * @const
	 */
	var DURATION = 500;

	/**
	 * jQuery unit set containing a feference to the global window.
	 *
	 * @type {jQuery.<window>}
	 * @const
	 */
	var $WINDOW = $(window);

	/**
	 * The "position" style value.
	 *
	 * IE 7 does not support "fixed" position styling.  Since "fixed" position
	 * results in smoother animation the use of "absolute" is made as a special
	 * accomodation for IE 7.
	 *
	 * @type {string}
	 * @const
	 */
	var POSITION_STYLE = Browser.ie7 ? 'absolute' : 'fixed';

	/**
	 * The position of the floating menu.
	 *
	 * Used to float dialoges (eg special character-picker) with the floating
	 * menu.
	 *
	 * @type {object<string,*>}
	 */
	var POSITION = {
		style: POSITION_STYLE,
		offset: {
			top: 0,
			left: 0
		}
	};

	/**
	 * Animates a surface element to the given position.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set of the DOM
	 *                                        element to move.
	 * @param {object} position The x and y position to which the element
	 *                          should end up.
	 * @param {number} duration The length of time (in milliseconds) that the
	 *                          animation should run for.
	 * @param {function} callback Function to be invoked when animation
	 *                            completes.
	 */
	function floatTo($element, position, duration, callback) {
		if ('absolute' === POSITION_STYLE) {
			position.top += $WINDOW.scrollTop();
			position.left += $WINDOW.scrollLeft();
		}

		POSITION.offset = position;

		$element.stop().animate(position, duration, function () {
			callback(position);
			PubSub.pub('aloha.floating.changed', {
				position: $.extend({}, POSITION)
			});
		});
	}

	/**
	 * Moves an element above the given coordinates.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set of the DOM
	 *                                        element to move.
	 * @param {object} position The x and y position to which the element
	 *                          should end up.
	 * @param {number} duration The length of time (in milliseconds) that the
	 *                          animation should run for.
	 * @param {function} callback Function to be invoked when animation
	 *                            completes.
	 */
	function floatAbove($element, position, duration, callback) {
		position.top -= $element.height() + DISTANCE;
		floatTo($element, position, duration, callback);
	}

	/**
	 * Moves the element below the given coordinates.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set of the DOM
	 *                                        element to move.
	 * @param {object} position The x and y position to which the element
	 *                          should end up.
	 * @param {number} duration The length of time (in milliseconds) that the
	 *                          animation should run for.
	 * @param {function} callback Function to be invoked when animation
	 *                            completes.
	 */
	function floatBelow($element, position, duration, callback) {
		position.top += DISTANCE;
		floatTo($element, position, duration, callback);
	}

	/**
	 * Persist the "top" and "left" positions of the FloatingMenu surface.
	 */
	function storePinPosition(offset) {
		amplifyStore.store('Aloha.FloatingMenu.pinned', 'true');
		amplifyStore.store('Aloha.FloatingMenu.top', offset.top);
		amplifyStore.store('Aloha.FloatingMenu.left', offset.left);
	}

	/**
	 * Clears persisted state of the FloatingMenu surface.
	 */
	function unstorePinPosition() {
		amplifyStore.store('Aloha.FloatingMenu.pinned', null);
		amplifyStore.store('Aloha.FloatingMenu.top', null);
		amplifyStore.store('Aloha.FloatingMenu.left', null);
	}

	/**
	 * Retreive the persisted pinned position of the FloatingMenu surface.
	 *
	 * @return {object}
	 */
	function getPinState() {
		if (amplifyStore.store('Aloha.FloatingMenu.pinned') === 'true') {
			return {
				top: parseInt(amplifyStore.store('Aloha.FloatingMenu.top'), 10),
				left: parseInt(amplifyStore.store('Aloha.FloatingMenu.left'), 10),
				isPinned: true
			};
		}
		return {
			top: null,
			left: null,
			isPinned: false
		};
	}

	/**
	 * Constrains the given position coordinates to be within the viewport.
	 *
	 * @param {object} position "Top" and "left" coordinates.
	 * @param {object} Constrained "top" and "left" coordinates.
	 */
	function forcePositionIntoWindow(position) {
		var left = position.left;
		var top = position.top;

		if (top < 0) {
			top = 0;
		} else if (top > $WINDOW.height()) {
			top = $WINDOW.height() / 2;
		}

		if (left < 0) {
			left = 0;
		} else if (left > $WINDOW.width()) {
			left = $WINDOW.width() / 2;
		}

		return {
			top: top,
			left: left
		};
	}

	/**
	 * Floats a surface to the appropriate position around the given editable.
	 *
	 * @param {Surface} surface The surface to be positioned.
	 * @param {Aloha.Editable} editable The editable around which the surface
	 *                                  should be positioned.
	 * @param {number} duration The length of time (in milliseconds) for the
	 *                          animation should run.
	 * @param {function} callback Function to be invoked after the animation
	 *                            is completed.
	 */
	function floatSurface(surface, editable, duration, callback) {
		if (typeof duration !== 'number') {
			duration = DURATION;
		}

		var topGutter = (parseInt($('body').css('marginTop'), 10) || 0)
		              + (parseInt($('body').css('paddingTop'), 10) || 0);
		var $surface = surface.$element;
		var offset = editable.obj.offset();
		var top = offset.top;
		var left = offset.left;
		var scrollTop = $WINDOW.scrollTop();
		var availableSpace = top - scrollTop - topGutter;
		var horizontalOverflow = left + $surface.width() - $WINDOW.width();

		if (horizontalOverflow > 0) {
			left = Math.max(0, left - horizontalOverflow);
		}

		if (availableSpace >= $surface.height()) {
			floatAbove($surface, {
				top: top - scrollTop,
				left: left
			}, duration, callback);
		} else if (availableSpace + $surface.height() >
		           availableSpace + editable.obj.height()) {
			floatBelow($surface, {
				top: top + editable.obj.height(),
				left: left
			}, duration, callback);
		} else {
			floatBelow($surface, {
				top: topGutter,
				left: left
			}, duration, callback);
		}
	}

	/**
	 * Pins a surface at the speficied position on the viewport.
	 *
	 * @param {Surface} surfaces The surfaces that are to be pinned.
	 * @param {object} position The "top" and "left" position of where the
	 *                          surface is to be pinned.
	 * @param {boolean} isFloating Whether or not the surface type is in
	 *                             "floating" mode or not.
	 */
	function togglePinSurface(surface, position, isFloating) {
		var $surface = surface.$element;
		if (isFloating) {
			unstorePinPosition();
			$surface.find('.aloha-ui-pin').removeClass('aloha-ui-pin-down');
		} else {
			storePinPosition(position);
			$surface.find('.aloha-ui-pin').addClass('aloha-ui-pin-down');
		}
		$surface.css({
			position: 'fixed',
			top: position.top
		});
	}

	/**
	 * Filters surface activation events.
	 */
	function onActivatedSurface(tuples, eventName, $event, range, nativeEvent) {
		var i;
		for (i = 0; i < tuples.length; i++) {
			if (tuples[i][0].isActive()) {
				tuples[i][1]($event, range, nativeEvent);
			}
		}
	}

	/**
	 * Sets the surface's DOM element's "position" property to "fixed."
	 *
	 * IE7 will not properly set the position property to "fixed" if our
	 * element is not rendered.  We therefore have to do a rigmarole to
	 * temorarily render the element in order to set the position correctly.
	 *
	 * @param {Surface} surface
	 */
	function setPositionStyleToFixed(surface) {
		if ($.browser.msie) {
			var $parent = surface.$element.parent();
			surface.$element.appendTo('body');
			surface.$element.css('position', POSITION_STYLE);
			if ($parent.length) {
				surface.$element.appendTo($parent);
			} else {
				surface.$element.detach();
			}
		} else {
			surface.$element.css('position', POSITION_STYLE);
		}
	}

	/**
	 * Binds floating facilities on a surface.
	 *
	 * @TODO:
	 * Resizable toolbars are possible, and would be a nice feature:
	 * surface.$element.resizable();
	 *
	 * @param {Surface} surface A UI Surface instance.
	 * @param {object} SurfaceTypeManager
	 */
	function makeFloating(surface, SurfaceTypeManager) {
		subguarded([
			'aloha-selection-changed',
			'aloha.ui.container.selected'
		], onActivatedSurface, surface, function () {
			surface._move();
		});

		var updateSurfacePosition = function () {
			var position = forcePositionIntoWindow({
				top: SurfaceTypeManager.pinTop,
				left: SurfaceTypeManager.pinLeft
			});
			SurfaceTypeManager.setFloatingPosition(position);
			surface.$element.css({
				top: position.top,
				left: position.left
			});
		};

		$WINDOW.scroll(function () {
			// TODO: only do this for active surfaces.
			surface._move(0);
		});

		$WINDOW.resize(function () {
			if (!SurfaceTypeManager.isFloatingMode) {
				updateSurfacePosition();
			}
		});

		surface.addPin();
		setPositionStyleToFixed(surface);

		if (!SurfaceTypeManager.isFloatingMode) {
			updateSurfacePosition();
		}

		surface.$element.css('z-index', 10100).draggable({
			distance: 20,
			stop: function (event, ui) {
				SurfaceTypeManager.setFloatingPosition(ui.position);
				if (!SurfaceTypeManager.isFloatingMode) {
					storePinPosition(ui.position);
				}
			}
		});
	}

	return {
		getPinState: getPinState,
		makeFloating: makeFloating,
		floatSurface: floatSurface,
		togglePinSurface: togglePinSurface,
		POSITION_STYLE: POSITION_STYLE
	};
});

/**
 * @license RequireJS i18n 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/requirejs/i18n for details
 */
/*jslint regexp: true */
/*global require: false, navigator: false, define: false */

/**
 * This plugin handles i18n! prefixed modules. It does the following:
 *
 * 1) A regular module can have a dependency on an i18n bundle, but the regular
 * module does not want to specify what locale to load. So it just specifies
 * the top-level bundle, like "i18n!nls/colors".
 *
 * This plugin will load the i18n bundle at nls/colors, see that it is a root/master
 * bundle since it does not have a locale in its name. It will then try to find
 * the best match locale available in that master bundle, then request all the
 * locale pieces for that best match locale. For instance, if the locale is "en-us",
 * then the plugin will ask for the "en-us", "en" and "root" bundles to be loaded
 * (but only if they are specified on the master bundle).
 *
 * Once all the bundles for the locale pieces load, then it mixes in all those
 * locale pieces into each other, then finally sets the context.defined value
 * for the nls/colors bundle to be that mixed in locale.
 *
 * 2) A regular module specifies a specific locale to load. For instance,
 * i18n!nls/fr-fr/colors. In this case, the plugin needs to load the master bundle
 * first, at nls/colors, then figure out what the best match locale is for fr-fr,
 * since maybe only fr or just root is defined for that locale. Once that best
 * fit is found, all of its locale pieces need to have their bundles loaded.
 *
 * Once all the bundles for the locale pieces load, then it mixes in all those
 * locale pieces into each other, then finally sets the context.defined value
 * for the nls/fr-fr/colors bundle to be that mixed in locale.
 */
(function () {
    

    //regexp for reconstructing the master bundle name from parts of the regexp match
    //nlsRegExp.exec("foo/bar/baz/nls/en-ca/foo") gives:
    //["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"]
    //nlsRegExp.exec("foo/bar/baz/nls/foo") gives:
    //["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""]
    //so, if match[5] is blank, it means this is the top bundle definition.
    var nlsRegExp = /(^.*(^|\/)nls(\/|$))([^\/]*)\/?([^\/]*)/;

    //Helper function to avoid repeating code. Lots of arguments in the
    //desire to stay functional and support RequireJS contexts without having
    //to know about the RequireJS contexts.
    function addPart(locale, master, needed, toLoad, prefix, suffix) {
        if (master[locale]) {
            needed.push(locale);
            if (master[locale] === true || master[locale] === 1) {
                toLoad.push(prefix + locale + '/' + suffix);
            }
        }
    }

    function addIfExists(req, locale, toLoad, prefix, suffix) {
        var fullName = prefix + locale + '/' + suffix;
        if (require._fileExists(req.toUrl(fullName))) {
            toLoad.push(fullName);
        }
    }

    /**
     * Simple function to mix in properties from source into target,
     * but only if target does not already have a property of the same name.
     * This is not robust in IE for transferring methods that match
     * Object.prototype names, but the uses of mixin here seem unlikely to
     * trigger a problem related to that.
     */
    function mixin(target, source, force) {
        var prop;
        for (prop in source) {
            if (source.hasOwnProperty(prop) && (!target.hasOwnProperty(prop) || force)) {
                target[prop] = source[prop];
            } else if (typeof source[prop] === 'object') {
                mixin(target[prop], source[prop], force);
            }
        }
    }

    define('i18n',['module'], function (module) {
        var masterConfig = module.config();

        return {
            version: '2.0.1',
            /**
             * Called when a dependency needs to be loaded.
             */
            load: function (name, req, onLoad, config) {
                config = config || {};

                if (config.locale) {
                    masterConfig.locale = config.locale;
                }

                var masterName,
                    match = nlsRegExp.exec(name),
                    prefix = match[1],
                    locale = match[4],
                    suffix = match[5],
                    parts = locale.split("-"),
                    toLoad = [],
                    value = {},
                    i, part, current = "";

                //If match[5] is blank, it means this is the top bundle definition,
                //so it does not have to be handled. Locale-specific requests
                //will have a match[4] value but no match[5]
                if (match[5]) {
                    //locale-specific bundle
                    prefix = match[1];
                    masterName = prefix + suffix;
                } else {
                    //Top-level bundle.
                    masterName = name;
                    suffix = match[4];
                    locale = masterConfig.locale;
                    if (!locale) {
                        locale = masterConfig.locale =
                            typeof navigator === "undefined" ? "root" :
                            (navigator.language ||
                             navigator.userLanguage || "root").toLowerCase();
                    }
                    parts = locale.split("-");
                }

                if (config.isBuild) {
                    //Check for existence of all locale possible files and
                    //require them if exist.
                    toLoad.push(masterName);
                    addIfExists(req, "root", toLoad, prefix, suffix);
                    for (i = 0; i < parts.length; i++) {
                        part = parts[i];
                        current += (current ? "-" : "") + part;
                        addIfExists(req, current, toLoad, prefix, suffix);
                    }

                    req(toLoad, function () {
                        onLoad();
                    });
                } else {
                    //First, fetch the master bundle, it knows what locales are available.
                    req([masterName], function (master) {
                        //Figure out the best fit
                        var needed = [],
                            part;

                        //Always allow for root, then do the rest of the locale parts.
                        addPart("root", master, needed, toLoad, prefix, suffix);
                        for (i = 0; i < parts.length; i++) {
                            part = parts[i];
                            current += (current ? "-" : "") + part;
                            addPart(current, master, needed, toLoad, prefix, suffix);
                        }

                        //Load all the parts missing.
                        req(toLoad, function () {
                            var i, partBundle, part;
                            for (i = needed.length - 1; i > -1 && needed[i]; i--) {
                                part = needed[i];
                                partBundle = master[part];
                                if (partBundle === true || partBundle === 1) {
                                    partBundle = req(prefix + part + '/' + suffix);
                                }
                                mixin(value, partBundle);
                            }

							// MODIFICATION FROM ALOHA START: add a t() function
							value.t = function( key, defaultValue ) {
								if ( this[key] ) {
									return this[key];
								} else if ( defaultValue ) {
									return defaultValue;
								} else {
									return key;
								}
							}
							// END OF ALOHA MODIFICATION

                            //All done, notify the loader.
                            onLoad(value);
                        });
                    });
                }
            }
        };
    });
}());

define('ui/nls/i18n',{
	"root":  {
		"button.dismiss.label": "Dismiss",
		"button.bold.label": "Bold",
		"button.italic.label": "Italic",
		"button.strikethrough.label": "Strikethrough",
		"button.subscript.label": "Subscript",
		"button.superscript.label": "Superscript",
		"button.underline.label": "Underline",
		"button.yes.label": "Yes",
		"button.no.label": "No",

		// paragraph
		"button.p.label": "Paragraph",
		"button.h1.label": "Heading 1",
		"button.h2.label": "Heading 2",
		"button.h3.label": "Heading 3",
		"button.h4.label": "Heading 4",
		"button.h5.label": "Heading 5",
		"button.h6.label": "Heading 6",
		"button.pre.label": "Preformatted text",
		"button.removeFormatting.label": "Remove formatting",

		// list
		"button.ol.label": "Insert ordered list",
		"button.ul.label": "Insert unordered list",
		"button.indent.label": "Indent list",
		"button.outdent.label": "Outdent list",
		"button.createLink.label": "Insert link",
		"button.removeLink.label": "Remove link",
		"button.createAbbr.label": "Insert abbreviation",
		"button.characterPicker.label": "Pick special characters",

		// link
		"button.createLink.label": "Insert link",
		"button.removeLink.label": "Remove link",

		// abbr
		"button.createAbbr.label": "Insert abbreviation",

		// character picker
		"button.characterPicker.label": "Pick special characters",

		// justify
		"button.justifyLeft.label": "Align to the left",
		"button.justifyRight.label": "Align to the right",
		"button.justifyCenter.label": "Center",
		"button.justifyFull.label": "Justify",

		// horizontal rule
		"button.horizontalRule.label": "Insert horizontal rule",
		"button.createLanguageAnnotation.label": "Insert language annotation",
		"button.metaview.label": "Switch between meta and normal view",
		"button.quote.label": "Format selection as quote",
		"button.blockquote.label": "Format selection as blockquote",
		"tab.format.label": "Format",
		"tab.insert.label": "Insert",
		"tab.abbr.label": "Abbreviation",
		"tab.img.label": "Image",
		"tab.link.label": "Link",
		"tab.list.label": "List",
		"tab.table.label": "Table",
		"tab.col.label": "Table Column",
		"tab.row.label": "Table Row",
		"tab.cell.label": "Table Cell",
		"tab.wai-lang.label": "Language annotation"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

define('ui/toolbar',[
	'jquery',
	'aloha/core',
	'ui/surface',
	'ui/tab',
	'ui/floating',
	'ui/context',
	'i18n!ui/nls/i18n',
	'jqueryui'
], function (
	$,
	Aloha,
	Surface,
	Tab,
	floating,
	Context,
	i18n
) {
	

	function isFloatingEnabled() {
		return !Aloha.settings
			|| !Aloha.settings.toolbar
			|| Aloha.settings.toolbar.floating !== false;
	}

	/**
	 * The toolbar is configured via `settings.toolbar` and is defined as an
	 * array of tabs with component groups, where the groups are arrays of
	 * controls.
	 *
	 * There are separate components for each context, but only the components
	 * for the active context are shown.
	 *
	 * As a container for tabs, the toolbar serves to group together groups of
	 * control components so that they can be shown and hidden together in their
	 * feature/functional set.  For example groups of table controls would be
	 * placed in a table tab, groups of list controls in an image tab, and so
	 * forth.
	 *
	 * Toolbar class and manager
	 *
	 * @class
	 * @extends {Surface}
	 */
	var Toolbar = Surface.extend({
		_moveTimeout: null,
		$_container: null,
		_tabBySlot: null,
		_tabs: [],

		/**
		 * Toolbar constructor.
		 *
		 * @param {!Array.<(Object|Array|string)>} tabs
		 * @constructor
		 * @override
		 */
		_constructor: function (context, tabs) {
			var tabSettings,
			    tabInstance,
			    i,
			    key;
			this._super(context);
			this.$element = $('<div>', {'class': 'aloha-ui aloha-ui-toolbar', 'unselectable': 'on'});
			this.$_container = Tab.createContainer().appendTo(this.$element);
			this._tabBySlot = {};

			for (i = 0; i < tabs.length; i++) {
				tabSettings = tabs[i];
				tabInstance = new Tab(context, {
					label: i18n.t(tabSettings.label, tabSettings.label),
					showOn: tabSettings.showOn,
					container: this.$_container
				}, tabSettings.components);
				for (key in tabInstance._elemBySlot) {
					if (tabInstance._elemBySlot.hasOwnProperty(key)) {
						this._tabBySlot[key] = tabInstance;
					}
				}
				this._tabs.push({tab: tabInstance, settings: tabSettings});
			}

			// Pinning behaviour is global in that if one toolbar is pinned,
			// then all other toolbars will be pinned to that position.
			if (isFloatingEnabled()) {
				floating.makeFloating(this, Toolbar);
			}
		},

		adoptInto: function (slot, component) {
			var tab = this._tabBySlot[slot];
			return tab && tab.adoptInto(slot, component);
		},

		getActiveContainer: function () {
			return this.$_container.data('aloha-active-container');
		},

		getContainers: function () {
			return this.$_container.data('aloha-tabs');
		},

		/**
		 * Moves the toolbar into the optimal position near the active editable.
		 *
		 * @param {number} duration The length of time the moving animation
		 *                          should run.
		 */
		_move: function (duration) {
			// We need to order the invocation of the floating animation to
			// occur after the the height of the toolbar's DOM has been
			// caluclated.
			var toolbar = this;
			if (toolbar._moveTimeout) {
				clearTimeout(toolbar._moveTimeout);
			}
			toolbar._moveTimeout = setTimeout(function () {
				toolbar._moveTimeout = null;
				if (Aloha.activeEditable && Toolbar.isFloatingMode) {
					floating.floatSurface(
						toolbar,
						Aloha.activeEditable,
						duration,
						Toolbar.setFloatingPosition
					);
				}
				// 20ms should be small enough to appear instantaneous to the
				// user but large enough to avoid doing unnecessary work when
				// the selection changes multiple times within a short time
				// frame.
			}, 20);
		},

		addPin: function () {
			var $pin = $('<div class="aloha-ui-pin">');
			var $element = this.$element;
			$element.find('.ui-tabs').append($pin);
			$element.find('.ui-tabs').hover(function () {
				$element.addClass('aloha-ui-hover');
			}, function () {
				$element.removeClass('aloha-ui-hover');
			});

			if (!Toolbar.isFloatingMode) {
				$pin.addClass('aloha-ui-pin-down');
			}

			var surface = this;

			$pin.click(function () {
				Toolbar.isFloatingMode = !Toolbar.isFloatingMode;
				var position;

				if (Toolbar.isFloatingMode) {
					position = {
						top: Toolbar.pinTop,
						left: Toolbar.pinLeft
					};
				} else {
					position = surface.$element.offset();
					position.top -= $(window).scrollTop();
				}

				Toolbar.setFloatingPosition(position);
				floating.togglePinSurface(surface, position, Toolbar.isFloatingMode);
			});
		},

		/**
		 * Shows the toolbar.
		 */
		show: function () {
			Toolbar.$surfaceContainer.children().detach();
			Toolbar.$surfaceContainer.append(this.$element);
			Toolbar.$surfaceContainer.stop().fadeTo(200, 1);
			if (isFloatingEnabled()) {
				var position = Toolbar.getFloatingPosition();
				this.$element.stop().css({
					top: position.top,
					left: position.left
				});
				this._move();
			}
		},

		/**
		 * Hides the toolbar.
		 */
		hide: function () {
			Toolbar.$surfaceContainer.stop().fadeOut(200, function () {
				Toolbar.$surfaceContainer.children().detach();
			});
		}
	});

	$.extend(Toolbar, {

		/**
		 * An element on which all toolbar surfaces are to be rendered on the
		 * page.
		 * @type {jQuery.<HTMLElement>}
		 */
		$surfaceContainer: null,

		/**
		 * Whether or not floating toolbar surfaces should be pinned.
		 * @type {boolean}
		 */
		isFloatingMode: true,

		/**
		 * Left position of pinned toolbars.
		 * @type {number}
		 */
		pinLeft: 0,

		/**
		 * Top position of pinned toolbars.
		 * @type {number}
		 */
		pinTop: 0,

		/**
		 * Initializes the toolbar manager.  Adds the surface container
		 * element, and sets up floating behaviour settings.
		 */
		init: function () {
			// TODO should use context.js to get the context element
			Toolbar.$surfaceContainer = $('<div>', {
				'class': 'aloha aloha-surface aloha-toolbar',
				'unselectable': 'on'
			}).hide();

			// In the built aloha.js, init will happend before the body has
			// finished loading, so we have to defer appending the element.
			$(function () { Toolbar.$surfaceContainer.appendTo('body'); });
			Surface.trackRange(Toolbar.$surfaceContainer);
			var pinState = floating.getPinState();
			Toolbar.pinTop = pinState.top;
			Toolbar.pinLeft = pinState.left;
			Toolbar.isFloatingMode = !pinState.isPinned;
		},

		setFloatingPosition: function (position) {
			Toolbar.pinTop = position.top;
			Toolbar.pinLeft = position.left;
		},

		getFloatingPosition: function () {
			return {
				top: Toolbar.pinTop,
				left: Toolbar.pinLeft
			};
		}
	});

	Toolbar.init();

	return Toolbar;
});

define('ui/settings',['jquery', 'util/arrays', 'util/maps', 'util/trees'], function($, Arrays, Maps, Trees){
	var defaultToolbarSettings = {
		tabs: [
			// Format Tab
			{
				label: 'tab.format.label',
				showOn: { scope: 'Aloha.continuoustext' },
				components: [
					[
						'bold', 'strong', 'italic', 'emphasis', 'underline', '\n',
						'subscript', 'superscript', 'strikethrough', 'quote'
					], [
						'formatLink', 'formatAbbr', 'formatNumeratedHeaders', 'toggleDragDrop', '\n',
						'toggleMetaView', 'wailang', 'toggleFormatlessPaste'
					], [
						'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '\n',
						'orderedList', 'unorderedList', 'indentList', 'outdentList', 'colorPicker'
					], [
						'formatBlock'
					]
				]
			},
			// Insert Tab
			{
				label: "tab.insert.label",
				showOn: { scope: 'Aloha.continuoustext' },
				components: [
					[ "createTable", "characterPicker", "insertLink",
					  "insertImage", "insertAbbr", "insertToc",
					  "insertHorizontalRule", "insertTag"]
				]
			},
			// Link Tab
			{
				label: 'tab.link.label',
				showOn: { scope: 'link' },
				components: [ 'editLink', 'removeLink', 'linkBrowser' ]
			},
            // Image Tab
            {
                label: "tab.img.label",
                showOn: {scope: 'image'},
                components: [
					[ "imageSource", "\n",
					  "imageTitle" ],
					[ "imageResizeWidth", "\n",
					  "imageResizeHeight" ],
					[ "imageAlignLeft", "imageAlignRight", "imageAlignNone", "imageIncPadding", "\n",
					  "imageCropButton", "imageCnrReset", "imageCnrRatio", "imageDecPadding" ],
					[ "imageBrowser" ]
                ]
            },
            // Abbr Tab
            {   label: "tab.abbr.label",
                showOn: { scope: 'abbr' },
                components: [
                    [ "abbrText", "removeAbbr" ]
                ]
            },
            // Wailang Tab
            {   label: "tab.wai-lang.label",
				showOn: { scope: 'wai-lang' },
                components: [ [ "wailangfield", "removewailang" ] ]
            },
			// Table Tabs
			{
				label: "tab.table.label",
				showOn: { scope: 'table.cell' },
				components: [
					[ "mergecells", "splitcells", "tableCaption",
					  "naturalFit", "tableSummary" ],
					[ "formatTable" ]
				]
			},
			{
				label: "tab.col.label",
				showOn: { scope: 'table.column' },
				components: [
					[ "addcolumnleft", "addcolumnright", "deletecolumns",
					  "columnheader", "mergecellsColumn", "splitcellsColumn",
					  "formatColumn" ]
				]
			},
			{
				label: "tab.row.label",
				showOn: { scope: 'table.row' },
				components: [
					[ "addrowbefore", "addrowafter", "deleterows", "rowheader",
					  "mergecellsRow", "splitcellsRow", "formatRow" ]
				]
			},
			{
				label: "tab.cell.label",
				showOn: { scope: 'table.cell' },
				components: [
					[ "alignTop", "alignMiddle", "alignBottom", "formatCell" ]
				]
			}

		]
	};

	/**
	 * Combines two toolbar configurations.
	 *
	 * The rules for combining configurations are as follows
	 *
	 * * remove all components and tabs from the default toolbar configuration
	 *   that are listed in the given exclude array,
	 * * add all remaining tabs from the default configuration to the user
	 *   configuration,
	 * * and merge tabs with the same name such that a tab property that is
	 *   omitted in the user configuration will be taken from the default
	 *   configuration,
	 * * and, if both the default tab and the user's tab configuration contain
	 *   a components property, and unless the exclusive property on a tab is
	 *   true, append all remaining components from the default tab to the
	 *   user's tab configuration.
	 *
	 * @param userTabs
	 *        a list of tab configurations
	 * @param defaultTabs
	 *        a list of tab configurations
	 * @param exclude
	 *        a list of component names and tab labels to ignore
	 *        in the given defaultTabs configuration.
	 * @return
	 *
	 */
	function combineToolbarSettings(userTabs, defaultTabs, exclude) {
		var defaultTabsByLabel = Maps.fillTuples({}, Arrays.map(defaultTabs, function(tab) {
			return [tab.label, tab];
		}));
		var exclusionLookup = makeExclusionMap(userTabs, exclude);
		function pruneDefaultComponents(form) {
			return 'array' === $.type(form) ? !form.length : exclusionLookup[form];
		};
		userTabs = mergeDefaultComponents(userTabs, defaultTabsByLabel, pruneDefaultComponents);
		defaultTabs = remainingDefaultTabs(defaultTabs, exclusionLookup, pruneDefaultComponents);
		return userTabs.concat(defaultTabs);
	}

	function remainingDefaultTabs(defaultTabs, exclusionLookup, pruneDefaultComponents) {
		var i,
		    tab,
		    tabs = [],
		    defaultTab,
		    components;
		for (i = 0; i < defaultTabs.length; i++) {
			defaultTab = defaultTabs[i];
			if (!exclusionLookup[defaultTab.label]) {
				components = Trees.postprune(defaultTab.components, pruneDefaultComponents);
				if (components) {
					tab = $.extend({}, defaultTab);
					tab.components = components;
					tabs.push(tab);
				}
			}
		}
		return tabs;
	}

	function mergeDefaultComponents(userTabs, defaultTabsByLabel, pruneDefaultComponents) {
		var i,
            tab,
		    tabs = [],
		    userTab,
		    components,
		    defaultTab,
		    defaultComponents;
		for (i = 0; i < userTabs.length; i++) {
			userTab = userTabs[i];
			components = userTab.components || [];
			defaultTab = defaultTabsByLabel[userTab.label];
			if (!userTab.exclusive && defaultTab) {
				defaultComponents = Trees.postprune(defaultTab.components, pruneDefaultComponents);
				if (defaultComponents) {
					components = components.concat(defaultComponents);
				}
			}
			tab = $.extend({}, defaultTab || {}, userTab);
			tab.components = components;
			tabs.push(tab);
		}
		return tabs;
	}

	function makeExclusionMap(userTabs, exclude) {
		var i,
		    map = Maps.fillKeys({}, exclude, true);
		for (i = 0; i < userTabs.length; i++) {
			map[userTabs[i].label] = true;
			Maps.fillKeys(map, Trees.flatten(userTabs[i].components), true);
		}
		return map;
	}

	return {
		defaultToolbarSettings: defaultToolbarSettings,
		combineToolbarSettings: combineToolbarSettings
	};
});

/* ui-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * The ui/ui-plugin module controls the creation and display of the UI.
 */
define('ui/ui-plugin', [
	'jquery',
	'aloha',
	'ui/context',
	'ui/container',
	'ui/surface',
	'ui/toolbar',
	'ui/scopes',
	'ui/settings',
	'PubSub',
	// Most modules of the ui plugin depend on jquery-ui, but its easy
	// to forget to add the dependency so we do it here.
	'jqueryui'
], function (
	$,
	Aloha,
	Context,
	Container,
	Surface,
	Toolbar,
	Scopes,
	Settings,
	PubSub
) {
	

	var context = new Context(),
        toolbar = new Toolbar(context, getToolbarSettings());

	Aloha.bind('aloha-editable-activated', function(event, alohaEvent) {
		Surface.show(context);
		Container.showContainersForContext(context, event);
	});

	Aloha.bind('aloha-editable-deactivated', function (event, alohaEvent) {
		if (!Surface.suppressHide) {
			Surface.hide(context);
		}
	});

	PubSub.sub('aloha.ui.scope.change', function () {
		Container.showContainersForContext(context);
		primaryScopeForegroundTab(Scopes.getPrimaryScope());
	});

	function getToolbarSettings() {
		var userSettings = Aloha.settings.toolbar,
		    defaultSettings = Settings.defaultToolbarSettings;
		if (!userSettings) {
			return defaultSettings.tabs;
		}
		return Settings.combineToolbarSettings(
			userSettings.tabs || [],
			defaultSettings.tabs,
			userSettings.exclude || []
		);
	}

	function primaryScopeForegroundTab() {
		var tabs = toolbar._tabs,
		    primaryScope = Scopes.getPrimaryScope(),
		    settings,
		    i;
		for (i = 0; i < tabs.length; i++) {
			settings = tabs[i].settings;
			if ('object' === $.type(settings.showOn) && settings.showOn.scope === primaryScope) {
				tabs[i].tab.foreground();
				break;
			}
		}
	}

	/**
	 * Adopts a component instance into the UI.
	 *
	 * Usually, the implementation of this method will display the
	 * component, at a position in the UI given by the slot
	 * argument.
	 *
	 * @param slot
	 *        A position argument that is interpreted by the UI however it likes.
	 * @param component
	 *        An instance of a component to adopt into the given slot.
	 * @api
	 */
	function adoptInto(slot, component) {
		return toolbar.adoptInto(slot, component);
	}

	/**
	 * Shows the toolbar.
	 *
	 * By default, the toolbar will be hidden when no editable is
	 * activated, and shown when an editable is activated. Calling
	 * this function will show the toolbar regardless of whether an
	 * editable is activated.
	 *
	 * The toolbar will only become visible if tabs are visible as well.
	 * To make tabs visible, set a scope. For example
	 * Scopes.setScope('Aloha.continuoustext');		
	 *
	 * Please note that the toolbar will not remain visible if an
	 * editable is subsequently deactivated.
	 *
	 * @param {?Object} event
	 *        An optional event argument that caused the toolbar to be show.
	 *        Will be passed on to Aloha.settings.toolbar.tabs[i].showOn functions.
	 * @api
	 */
	function showToolbar(event) {
		Surface.show(context);
		Container.showContainersForContext(context, event);
	}

	/**
	 * This module is part of the Aloha API.
	 * It is valid to override this module via requirejs to provide a
	 * custom behaviour. An overriding module must implement all API
	 * methods. Every member must have an api annotation. No non-api
	 * members are allowed.
	 * @api
	 */
	return {
		/**
		 * Adopts a component instance into the UI.
		 *
		 * Usually, the implementation of this method will display the
		 * component, at a position in the UI given by the slot
		 * argument.
		 *
		 * @param slot
		 *        A position argument that is interpreted by the UI however it likes.
		 * @param component
		 *        An instance of a component to adopt into the given slot.
		 * @api
		 */
		adoptInto: adoptInto,
		showToolbar: showToolbar
	};
});

/**
 * Aloha Editor User Interface API Semantics
 * =========================================
 *
 * Note
 * ===
 *    * This documentation is "thinking out loud," and very much "work in work
 *      in progress--as is the Aloha UI API itself.
 *
 *    * For flexibility and ease, it seems that it would be best that the Aloha
 *      UI API will not constrain the developer to these semantics, but will
 *      naively assume that these semantics are observed.
 *
 * Components (Buttons, Labels, Icons)
 * ---
 * Aloha Editor represents its user interface using objects called
 * `components`.  A uniform interface for these components allows them to be
 * agnostic to what container they are rendered on.
 *
 * Controls (Buttons)
 * ---
 * Interactive components like buttons, are called `controls`, to distinguish
 * them from non-interactive components like labels, and icons.
 *
 * Containers (Tabs, Panels)
 * ---
 * In rendering the UI, components are organized in visual groups, and these
 * groups are in turn bundled onto `containers`.  Containers can be tabs, as in
 * the case of the floating menu, or panels like in the sidebar.  Containers
 * allow a collection of controls that represent a feature set to be rendered
 * as a group and to be brought in and out of view together.
 *
 * Surfaces (Toolbar, Sidebar, Ribbon)
 * ---
 * `Surfaces` are areas on a web page in which containers can be placed.  The
 * sidebar, and the toolbar are examples of such surfaces.  The possibility
 * exists for other surfaces to be defined--such as a ribbon, or a footer menu.
 *
 *
 * Class structure
 * ===
 *
 *         Surface
 *	          |
 *	     ,----+----.
 *       |         |
 *	  Toolbar   Sidebar
 *
 *
 *        Container
 *	          |
 *	     ,----+----.
 *       |         |
 *	   Panel      Tab
 *
 *
 *        Component
 *            |
 *	     ,----+----.
 *       |         |
 *	   Label    Control
 *
 *
 * Enforcing good dependencies
 * ===
 * In order to void subtle errors that arise from erroneous dependency
 * declarations, we will have ill-defined or missing dependencies fail early
 * and noisily.  We achieve this by never exposing individual modules through
 * the Aloha.ui hash unless absolutely necessary. Modules are accessibly only
 * through require.
 *
 * How this works:
 * For example, a ui module "uiModule" would not be exposed through
 * `Aloha.ui.uiModule` but from require's `define` call:
 *
 *		define([ 'ui/uiModule' ], function( uiModule ) {});
 *
 * or
 *
 *		var uiModule = Aloha.require( 'ui/uiModule' );
 * 
 * This will force more deliberate and precise usage of dependencies.  The
 * developer will have to know exactly which direct dependencies they are using;
 * any missing or unsuccessfully require dependency will not inadvertantly be
 * provided from another require somewhere else in Aloha thereby silencing an
 * error that will emerge unexpectedly if that require is ever removed.
 */

define('ui/ui', [
	'jquery',
	'ui/ui-plugin'
],
function(
	$,
	UiPlugin
) {
	

	/**
	 * A hash map of components mapped against the slots into which they have
	 * been adopted.
	 *
	 * @type {Object<string, Component>}
	 */
	var components = {};

	/**
	 * Adopts a component into the UI.
	 *
	 * Only adopted components will become part of the UI.
	 *
	 * Where the component is placed is decided by looking the name up
	 * in the configuration.
	 * 
	 * If adoption is successful, the component will have its
	 * adoptParent() method invoked by the container the component ends
	 * up in.
	 *
	 * @param {string?} name
	 *        Names the component to be adopted.
	 *        The name is used by the UI to identify the component,
	 *        and place the component.
	 *        Making multiple calles with different components but
	 *        the same name is valid.
	 *        If not given, settings must be given and contain a
	 *        name property.
	 * @param {Object?} SuperTypeOrInstance
	 *        Either a component type that will be instantiated, or an
	 *        already instantiated component instance.
	 * @param {Object?} settings
	 *        An optional map of component settings which will be used
	 *        to extend the SuperType.
	 *        Ignored if an component instance is given.
	 *        Must contain a name property if no name is given.
	 * @return
	 *        If a SuperTypeOrInstance is a SuperType, the new component
	 *        instantiated from SuperType, or the given component
	 *        instance.
	 * @api
	 */
	function adopt(name, SuperTypeOrInstance, settings) {
		var Type,
		    component;

		if ('string' !== $.type(name)) {
			settings = SuperTypeOrInstance;
			SuperTypeOrInstance = name;
			name = settings.name;
		} else {
			settings = $.extend({name: name}, settings);
		}

		if (!SuperTypeOrInstance.isInstance) {
			Type = settings ? SuperTypeOrInstance.extend(settings) : SuperTypeOrInstance;
			component = new Type();
		} else {
			component = SuperTypeOrInstance;
		}

		components[name] = component;
		UiPlugin.adoptInto(name, component);

		return component;
	}

	/**
	 * Retreives the component that was adopted at the given UI slot.
	 *
	 * @param {string} slot The name of the slot.
	 * @return {Component?} A component, or null if no slot was adopted
	 *                      into the slot.
	 */
	function getComponentAtSlot(slot) {
		return components[slot] || null;
	}

	return {
		adopt: adopt,
		getAdoptedComponent: getComponentAtSlot
	};
});

/* utils.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('ui/utils',['jquery', 'jqueryui'], function ($) {
	

	/**
	 * Wraps an element such that a label is displayed alongside it.
	 *
	 * Contrary to tooltips, a label is always visible and takes up
	 * place in the toolbar.
	 *
	 * The label will wrap the given element to make an implicit
	 * association between label and element (click on the label will
	 * give focus to a wrapped input element for example).
	 *
	 * @param {string} labelText
	 *       The already internationalized text the label should contain.
	 * @param {!jQuery} element
	 *       Any element to wrap.
	 * @return {!jQuery}
	 *       A new label element that wraps the given element.
	 */
	function wrapWithLabel(labelText, element) {
		return $('<label>', {'class': 'aloha-ui-label'})
			.append($('<span>', {'class': 'aloha-ui-label-text', 'text': labelText}))
			.append(element);
	}

	function makeButton(button, props, hasMenu) {
		button.button({
			label: makeButtonLabel(props),
			text: !!(props.text || props.html),
			icons: {
				primary: props.icon || (props.iconUrl && 'aloha-ui-inline-icon-container') || null,
				secondary: (hasMenu && 'aloha-jqueryui-icon ui-icon-triangle-1-s') || null
			}
		});
		if (props.iconUrl) {
			button.button('widget')
				.children('.ui-button-icon-primary')
				.append(makeButtonIconFromUrl(props.iconUrl));
		}
		return button;
	}

	function makeButtonLabel(props) {
		// TODO text should be escaped
		return props.html || props.text || props.tooltip;
	}

	function makeButtonLabelWithIcon(props) {
		var label = makeButtonLabel(props);
		if (props.iconUrl) {
			label = makeButtonIconFromUrl(props.iconUrl) + label;
		}
		return label;
	}

	function makeButtonIconFromUrl(iconUrl) {
		return '<img class="aloha-ui-inline-icon" src="' + iconUrl + '">';
	}

	function makeButtonElement(attr) {
		// Set type to button to avoid problems with IE which
		// considers buttons to be of type submit by default. One
		// problem that occurd was that hitting enter inside a
		// text-input caused a click event in the button right next
		// to it.
		return $('<button>', attr).attr('type', 'button');
	}

	return {
		wrapWithLabel: wrapWithLabel,
		makeButton: makeButton,
		makeButtonElement: makeButtonElement,
		makeButtonLabel: makeButtonLabel,
		makeButtonLabelWithIcon: makeButtonLabelWithIcon,
		makeButtonIconFromUrl: makeButtonIconFromUrl
	};
});

define('ui/vendor/jquery-ui-autocomplete-html',["jquery", "jqueryui"], function(jQuery){
/*
 * jQuery UI Autocomplete HTML Extension
 *
 * Copyright 2010, Scott González (http://scottgonzalez.com)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * http://github.com/scottgonzalez/jquery-ui-extensions
 */
(function( $ ) {

var proto = $.ui.autocomplete.prototype,
	initSource = proto._initSource;

function filter( array, term ) {
	var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
	return $.grep( array, function(value) {
		return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
	});
}

$.extend( proto, {
	_initSource: function() {
		if ( this.options.html && $.isArray(this.options.source) ) {
			this.source = function( request, response ) {
				response( filter( this.options.source, request.term ) );
			};
		} else {
			initSource.call( this );
		}
	},

	_renderItem: function( ul, item) {
		return $( "<li></li>" )
			.data( "item.autocomplete", item )
			.append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
			.appendTo( ul );
	}
});

})( jQuery );
});

/**
 * This is a helper module for porting plugins from the old
 * ui-attributefield.js in the aloha core to the new ui-plugin.
 * This interface is obsolete and must not be used for new implementations.
 */
define('ui/port-helper-attribute-field',[
	'jquery',
	'ui/ui',
	'ui/component',
	'ui/scopes',
	'ui/context',
	'ui/utils',
	'aloha/repositorymanager',
	'aloha/selection',
	'aloha/console',
	'ui/vendor/jquery-ui-autocomplete-html'
], function (
	$,
	Ui,
	Component,
	Scopes,
	Context,
	Utils,
	RepositoryManager,
	Selection,
	console
) {
	

	// Main responsibilities implemented by the attribute-field are
	//
	// * setting a target object and attribute and the subsequent change of
	//   that target attribute (example link plugin, table plugin)
	// * background color highlighting of the target object (example link plugin)
	// * a placeholder in the attribute field with a grey foreground color
	//   (example link plugin)
	// * maintain a current repository item to distinguish link plugin
	//   repository items from literal values typed in the attribute field
	//   (example link plugin)
	// * repository manager markObject on the target object if a repository
	//   item was selected (example link plugin)

	/**
	 * Creates a new attribute field.
	 *
	 * @param {!Object} props
	 *        A map containing the following properties
	 *        name         -
	 *        label        - some text that will be displayed alongside
	 *                       the attribute field,
	 *        labelClass   - a class to identify the label element,
	 *        valueField   -
	 *        displayField -
	 *        objectTypeFilter -
	 *        placeholder  -
	 *        noTargetHighlight -
	 *        targetHighlightCass - a class to be identify focused element
	 *        cls          -
	 *        width        -
	 *        scope        -
	 *        element      - the <input> element to use.
	 *                       If not supplied, a new one will be created.
	 */
	var AttributeField = function (props) {
		var valueField = props.valueField || 'id',
			displayField = props.displayField || 'name',
			objectTypeFilter = props.objectTypeFilter || ['all'],
			placeholder = props.placeholder,
			noTargetHighlight = !!props.noTargetHighlight,
			targetHighlightClass = props.targetHighlightClass,
			element = props.element ? $(props.element) : $('<input id="aloha-attribute-field-' + props.name + '">'),
			component,
			template,
			resourceItem,
			resourceValue,
			targetObject,
			targetAttribute,
			lastAttributeValue,
			additionalTargetObjects = [];

		if (props.cls) {
			element.addClass(props.cls);
		}
		if (props.width) {
			element.width(props.width);
		}

		component = Ui.adopt(props.name, Component, {
			scope: props.scope,
			init: function(){

				if (props.element) {
					this.element = element;
				} else {
					if (props.label) {
						this.element = Utils.wrapWithLabel(props.label, element);
						if (props.labelClass) {
							this.element.addClass(props.labelClass);
						}
					} else {
						// Why do we have to wrap the element in a span? It
						// doesn't seem to work otherwise.
						this.element = $('<span>').append(element);
					}
				}

				element.autocomplete({
					'html': true,
					'appendTo': Context.selector,
					'source': function( req, res ) {
						RepositoryManager.query({
							queryString: req.term,
							objectTypeFilter: objectTypeFilter
						}, function( data ) {
							res($.map(data.items, function(item) {
								return {
									label: parse(template, item),
									value: item.name,
									obj: item
								};
							}));
						});
					},
					"select": onSelect
				});
			}
		});

		element
			.bind("focus", onFocus)
			.bind("blur", onBlur)
			.bind("keydown", onKeyDown)
			.bind("keyup", onKeyup);

		setPlaceholder();

		// Because IE7 doesn't give us the blur event when the editable
		// is deactivated and the toolbar disappears (other browsers do).
		// TODO unbind, otherwise mermory leak
		Aloha.bind('aloha-editable-deactivated', onBlur);

		function onSelect(event, ui) {
			if (ui.item) {
				setItem(ui.item.obj);
			}
			finishEditing();
		}

		function onBlur() {
			finishEditing();
		}

		function onFocus(event, ui) {
			if ( ! $(event.target).is(':visible') ) {
				// The check for visible fixes the bug that the background
				// color of the target element is not restored.
				// Rationale: it's possible for the input to receive the focus event,
				// for example if it was triggered programatically, even if
				// it isn't visible. Problem is, if it's not visible, then
				// it will not really get focused and consequently, there
				// will be no blur event either. However, we must be able to
				// assume that the blur event will be fired so that we can
				// clean up the background color.
				return;
			}
			changeTargetBackground();

			// Remove placeholder
			if (getValue() === placeholder) {
				setValue('');
			}
		}

		function onKeyDown(event){
			// on ENTER or ESC leave the editing
			if ( event.keyCode == 13 || event.keyCode == 27 ) {
				event.preventDefault();
			}
		}

		function onKeyup(event) {
			// If this attribute field currently refers to a repository
			// item, and the user edits the contents of the input field,
			// this attribute field seizes to refer to the repository item.
			if (resourceItem && resourceValue !== getValue()) {
				resourceItem = null;
				resourceValue = null;
			}

			// This handles attribute updates for non-repository, literal urls typed into the input field.
			// Input values that refer to a repository item are handled via setItem().
			if ( ! resourceItem ) {
				setAttribute(targetAttribute, getValue());
			}

			if ( ( event.keyCode == 13 || event.keyCode == 27 ) ) {
				// Set focus to link element and select the object
				Selection.getRangeObject().select();
				finishEditing();
			}
		}

		function finishEditing() {
			restoreTargetBackground();

			if ( ! targetObject || lastAttributeValue === $(targetObject).attr(targetAttribute)) {
				return;
			}

			// when no resource item was selected, remove any marking of the target object
			if ( ! resourceItem ) {
				RepositoryManager.markObject( targetObject );
			}

			if (getValue() === '') {
				setPlaceholder();
			}
		}

		/**
		 * Execute a function for every targets this attribute
		 *
		 * fields is pointing to.
		 * @param {Function} fn Function to execute for each target
		 */
		function executeForTargets(fn) {
			var target = $(targetObject);
			fn(target);
			for (var i = 0, len = additionalTargetObjects.length; i < len ; i++) {
				fn($(additionalTargetObjects[i]));
			}
		}

		/**
		 * Change target background so the targets are
		 * highlighted
		 */
		function changeTargetBackground() {
			var target = $(targetObject);
			if (targetHighlightClass) {
				executeForTargets(function (target) {
					target.addClass(targetHighlightClass)
				});
			}

			if (noTargetHighlight) {
				return;
			}

			// Make sure that multiple invokations of
			// changeTargetBackground don't set an incorrect
			// data-original-background-color.
			restoreTargetBackground();

			// set background color to give visual feedback which link is modified
			if (target.context && target.context.style &&
				target.context.style['background-color']) {
				executeForTargets(function (target) {
					target.attr('data-original-background-color',
					            target.context.style['background-color']);
				});
			}
			executeForTargets(function (target) {
				target.css('background-color', '#80B5F2');
			});
		}

		function restoreTargetBackground() {
			var target = $(targetObject);
			if (targetHighlightClass) {
				executeForTargets(function (target) {
					target.removeClass(targetHighlightClass);
				});
			}
			if (noTargetHighlight) {
				return;
			}
			// Remove the highlighting and restore original color if was set before
			var color = target.attr('data-original-background-color');
			executeForTargets(function (target) {
				target.css('background-color', color || '');
			});
			if (!target.attr('style')) {
				executeForTargets(function (target) {
					target.removeAttr('style');
				});
			}
			executeForTargets(function (target) {
				target.removeAttr('data-original-background-color');
			});
		}

		function parse(template, item) {
			return template.replace( /\{([^}]+)\}/g, function(_, name) {
				return name in item ? item[ name ] : "";
			});
		}

		function setPlaceholder() {
			if (null === placeholder) {
				return;
			}
			element.css('color', '#AAA');
			element.val(placeholder);
		}

		function setTemplate(tmpl){
			template = tmpl;
		}

		/**
		 * When at least on objectType is set the value in the Attribute field does a query to all registered repositories.
		 * @param {Array} objectTypeFilter The array of objectTypeFilter to be searched for.
		 * @void
		 */
		function setObjectTypeFilter(objTypeFilter) {
			objectTypeFilter = objTypeFilter;
		}

		/**
		 * Adding a listener to the field
		 * @param {String} eventname The name of the event. Ex. 'keyup'
		 * @param {function} handler The function that should be called when the event happens.
		 */
		function addListener(eventName, handler) {
			element.bind(eventName, $.proxy(handler, attrField));
		}

		function getValue() {
			return element.val();
		}

		function setValue(value) {
			element.val(value);
			element.css('color', 'black');
		}

		function setItem(item) {
			resourceItem = item;

			if (item) {
				// TODO split display field by '.' and get corresponding attribute, because it could be a properties attribute.
				var v = item[displayField];
				// set the value into the field
				setValue(v);
				// store the value to be the "reference" value for the currently selected resource item
				resourceValue = v;
				setAttribute(targetAttribute, item[valueField]);
				RepositoryManager.markObject(targetObject, item);
			} else {
				resourceValue = null;
			}
		}

		function getItem() {
			return resourceItem;
		}

		/**
		 * Sets an attribute optionally based on a regex on reference
		 * @param {String} attr The Attribute name which should be set. Ex. "lang"
		 * @param {String} value The value to set. Ex. "de-AT"
		 * @param {String} regex The regex when the attribute should be set. The regex is applied to the value of refernece.
		 * @param {String} reference The value for the regex.
		 */
		function setAttribute(attr, value, regex, reference) {
			if (targetObject) {
				// check if a reference value is submitted to check against with a regex
				if (typeof reference !== 'undefined' && !reference.match(new RegExp(regex))) {
					$(targetObject).removeAttr(attr);
				} else {
					executeForTargets(function (target) {
						target.attr(attr, value);
					});
				}
			}
		}

		/**
		 * Sets the target Object of which the Attribute should be modified
		 * @param {jQuery} obj the target object
		 * @param {String} attr Attribute to be modified ex. "href" of a link
		 * @void
		 */
		function setTargetObject(obj, attr) {
			targetObject = obj;
			targetAttribute = attr;
			additionalTargetObjects = [];

			setItem(null);
			
			if (obj && attr) {
				lastAttributeValue = $(obj).attr(attr);
				setValue($(targetObject).attr(targetAttribute));
			} else {
				setValue('');
				return;
			}

			// check whether a repository item is linked to the object
			RepositoryManager.getObject( obj, function ( items ) {
				if (items && items.length > 0) {
					setItem(items[0]);
				}
			} );
		}

		function addAdditionalTargetObject(targetObj) {
			additionalTargetObjects.push(targetObj);
		}

		function getTargetObject() {
			return targetObject;
		}

		function focus() {
			component.focus();
			element.focus();
		}

		function foreground() {
			component.foreground();
		}

		function show() {
			element.show();
		}

		function hide() {
			element.hide();
		}

		function getInputId(){
			return element.attr("id");
		}

		function hasInputElem() {
			return true;
		}

		function getInputElem() {
			return element[0];
		}

		function getInputJQuery() {
			return element;
		}

		var attrField = {
			getInputElem: getInputElem,
			hasInputElem: hasInputElem,
			getInputId: getInputId,
			hide: hide,
			show: show,
			foreground: foreground,
			focus: focus,
			getTargetObject: getTargetObject,
			setTargetObject: setTargetObject,
			addAdditionalTargetObject: addAdditionalTargetObject,
			setAttribute: setAttribute,
			getItem: getItem,
			setItem: setItem,
			setValue: setValue,
			getValue: getValue,
			addListener: addListener,
			setObjectTypeFilter: setObjectTypeFilter,
			setTemplate: setTemplate,
			setPlaceholder: setPlaceholder,
			getInputJQuery: getInputJQuery
		};

		return attrField;
	};

	return AttributeField;
});

/**
 * NOTES:
 *    - Why do we maintain 2 references to the DOM element for a button
 *    component (this.element = this.buttonElement)?
 */

define('ui/button',[
	'jquery',
	'ui/component',
	'ui/utils',
	'jqueryui'
],
function (jQuery, Component, Utils) {
	

	/**
	 * Implements a simple button component.  All interactive UI controls (that
	 * is--anything that is not a label) will most probably extend the Button
	 * component.
	 *
	 * An extending class should optionally define the following properties
	 * tooltip - the internationalized tooltip text,
	 * icon    - the icon class,
	 * class   - an additional class to add to the button element,
	 * click   - the click handler for the button.
	 *
	 * @class
	 * @name Button
	 * @extends {Component}
	 */
	var Button = Component.extend({

		/**
		 * Initializes this button instance.
		 * The initialization is done when the component is rendered, not when
		 * it is created.  This is necessary to allow multiple renderings of
		 * the same component.  For example, you may want a component to be in
		 * the toolbar and in the sidebar.
		 *
		 * @override
		 */
		init: function () {
			this._super();
			this.createButtonElement();
			Utils.makeButton(this.buttonElement, this)
				.button('widget')
				.tooltip({
					tooltipClass: 'aloha aloha-ui-tooltip',
					position: {
						my: 'left top',
						at: 'right bottom'
					}
				})
				.zIndex(0)    // fix for IE7,8 so the icon shown disabled
				.click(jQuery.proxy(function () {

					// Ensure tooltips are always hidden after a button
					// is clicked because sometimes the tooltip doesn't
					// get closed automatically, for example after table
					// cells are merged or split.
					// IE needs the force argument to be true, Chrome doesn't.
					// The event argument can be ignored.
					this.buttonElement.tooltip('close', null/*event*/, true/*force*/);

					this._onClick();
				}, this));
		},

		/**
		 * May be overridden by component subclasses to implement
		 * component-specific behaviour.  The default implementation just calls
		 * the public click method.
		 *
		 * @protected
		 */
		_onClick: function () {
			this.click();
		},

		/**
		 * Handles mouse clicks on this button's rendered elements.
		 */
		click: function () {},

		/**
		 * Creates the DOM element to be rendered for user interaction.
		 *
		 * @return {jQuery<HTMLElement>}
		 */
		createButtonElement: function () {
			var button = Utils.makeButtonElement();
			if (this['class']) {
				button.addClass(this['class']);
			}
			this.element = this.buttonElement = button;
			return button;
		},

		/**
		 * Shows the button in a greyed-out inactive (unclickable) state.
		 */
		disable: function() {
			this.element.button('option', 'disabled', false);
		},

		/**
		 * Enables the button again after it has previously been disabled.
		 */
		enable: function(enable_opt) {
			this.element.button('option', 'disabled', enable_opt === false);
		}
	});

	return Button;
});

define('ui/toggleButton',[
	'jquery',
	'ui/button',
	'jqueryui'
],
function( jQuery, Button ) {
	

	var idCounter = 0;

	/**
	 * ToggleButton control. Extends the Button component type to provide an
	 * easy way to create buttons that can transition between "checked" and
	 * "unchecked" states.
	 *
	 * @class
	 * @name ToggleButton
	 * @extends {Button}
	 */
	var ToggleButton = Button.extend({

		_checked: false,

		/**
		 * Sets the state of the toggleButton and updates its visual display
		 * accordingly.
		 *
		 * @param {boolean} toggled Whether the button is to be set to the
		 *                          "toggled/checked" state.
		 */
		setState: function( toggled ) {
			// It is very common to set the button state on every
			// selection change even if the state hasn't changed.
			// Profiling showed that this is very inefficient.
			if (this._checked === toggled) {
				return;
			}
			this._checked = toggled;
			if (toggled) {
				this.element.addClass("aloha-button-active");
			} else {
				this.element.removeClass("aloha-button-active");
			}
		},

		getState: function() {
			return this._checked;
		},

		_onClick: function() {
			this.setState( ! this._checked );
			this.click();
		}
	});

	return ToggleButton;
});

define('link/nls/i18n',{
	"root":  {
		"button.addlink.tooltip": "Insert Link",
		"button.removelink.tooltip": "Remove Link",
		"newlink.defaulttext": "New Link",
		"floatingmenu.tab.link": "Link",
		"link.target.self": "Self",
		"link.target.blank": "Blank",
		"link.target.parent": "Parent",
		"link.target.top": "Top",
		"link.target.framename": "Framename",
		"link.target.legend": "Target",
		"link.title.legend": "Title",
		"insertLink": "ctrl+k"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

define('aloha/nls/i18n',{
	"root":  {
		"plugin.abbr.floatingmenu.tab.abbr": "Abbreviation",
		"floatingmenu.tab.format": "Format",
		"floatingmenu.tab.insert": "Insert",
		"yes": "Yes",
		"no": "No",
		"cancel": "Cancel",
		"repository.no_item_found": "No item found.",
		"repository.loading": "Loading",
		"repository.no_items_found_yet": "No items found yet..."
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* keys.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('util/keys',['jquery'], function ($) {
	

	var KEYCODE_TOKEN_MAP = (function () {
		var map = {};
		$.each({
			8  : 'backspace',
			9  : 'tab',
			13 : 'enter',
			16 : 'shift control alt',
			20 : 'capslock',
			27 : 'escape',
			32 : 'space',
			33 : 'pageup',
			34 : 'pagedown',
			37 : 'left up right down',
			45 : 'insert',
			46 : 'delete',
			48 : '0 1 2 3 4 5 6 7 8 9',
			65 : 'a b c d e f g h i j k l m n o p q r s t u v w x y z',
			112: 'f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15'
		}, function (key, tokens) {
			var start = parseInt(key, 10);
			$.each(tokens.split(' '), function (i, token) {
				map[start + i] = token;
			});
		});
		return map;
	}());

	function getTokenByKeyCode(keyCode) {
		return KEYCODE_TOKEN_MAP[keyCode];
	}

	return {
		getToken: getTokenByKeyCode,
		keyCodeTokenMap: $.extend({}, KEYCODE_TOKEN_MAP)
	};
});

/* link-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/* Aloha Link Plugin
 * -----------------
 * This plugin provides an interface to allow the user to insert, edit and
 * remove links within an active editable.
 * It presents its user interface in the Toolbar, in a Sidebar panel.
 * Clicking on any links inside the editable activates the this plugin's
 * floating menu scope.
 */
define('link/link-plugin', [
	'aloha',
	'aloha/plugin',
	'aloha/ephemera',
	'jquery',
	'ui/port-helper-attribute-field',
	'ui/ui',
	'ui/scopes',
	'ui/surface',
	'ui/button',
	'ui/toggleButton',
	'i18n!link/nls/i18n',
	'i18n!aloha/nls/i18n',
	'aloha/console',
	'PubSub',
	'util/keys'
], function (
	Aloha,
	Plugin,
	Ephemera,
	jQuery,
	AttributeField,
	Ui,
	Scopes,
	Surface,
	Button,
	ToggleButton,
	i18n,
	i18nCore,
	console,
	PubSub,
	Keys
) {
	
	
	var GENTICS = window.GENTICS,
	    pluginNamespace = 'aloha-link',
	    oldValue = '',
	    newValue;

	/**
	 * Properties for cleaning up markup immediately after inserting new link
	 * markup.
	 *
	 * Successive anchor elements are generally not merged, but an exception
	 * needs to be made in the process of creating links: adjacent fragments of
	 * new links are coalesced whenever possible.
	 *
	 * @type {object}
	 */
	var insertLinkPostCleanup = {
		merge: true,
		mergeable: function (node) {
			return ('aloha-new-link' === node.className && node.nextSibling
			     && 'aloha-new-link' === node.nextSibling.className);
		}
	};
	
	Ephemera.classes('aloha-link-pointer', 'aloha-link-text');

	function setupMousePointerFix() {
		jQuery(document)
			.bind('keydown.aloha-link.pointer-fix', function (e) {
				// metaKey for OSX, 17 for PC (we can't check
				// e.ctrlKey because it's only set on keyup or
				// keypress, not on keydown).
				if (e.metaKey || Keys.getToken(e.keyCode) === 'control') {
					jQuery('body').addClass('aloha-link-pointer');
				}
			})
			.bind('keyup.aloha-link.pointer-fix', function (e) {
				if (e.metaKey || Keys.getToken(e.keyCode) === 'control') {
					jQuery('body').removeClass('aloha-link-pointer');
				}
			});
	}

	function teardownMousePointerFix() {
		jQuery(document).unbind('.aloha-link.pointer-fix');
	}

	function setupMetaClickLink(editable) {
		editable.obj.delegate('a', 'click.aloha-link.meta-click-link', function (e) {
			// Use metaKey for OSX and ctrlKey for PC
			if (e.metaKey || e.ctrlKey) {
				// blur current editable. user is waiting for the link to load
				Aloha.activeEditable.blur();
				// hack to guarantee a browser history entry
				window.setTimeout(function () {
					location.href = e.target;
				}, 0);
				e.stopPropagation();
				return false;
			}
		});
	}

	function teardownMetaClickLink(editable) {
		editable.obj.unbind('.aloha-link.meta-click-link');
	}

	return Plugin.create('link', {
		/**
		 * Default configuration allows links everywhere
		 */
		config: [ 'a' ],

		/**
		 * The value that will automatically be set to an anchor tag's title
		 * attribute if its href field matches the titleregex, and the editor
		 * has not manually defined the title attribute.
		 *
		 * @type {string}
		 */
		title: null,

		/**
		 * Regular Expression string which the field's href value will be tested
		 * against in order to determine whether or not to set the configured
		 * title attribute value.
		 *
		 * @type {string}
		 */
		titleregex: null,

		/**
		 * all links that match the targetregex will get set the target
		 * e.g. ^(?!.*aloha-editor.com).* matches all href except aloha-editor.com
		 */
		targetregex: '',
		
		/**
		  * this target is set when either targetregex matches or not set
		  * e.g. _blank opens all links in new window
		  */
		target: '',
		
		/**
		 * all links that match the cssclassregex will get set the css class
		 * e.g. ^(?!.*aloha-editor.com).* matches all href except aloha-editor.com
		 */
		cssclassregex: null,
		
		/**
		  * this target is set when either cssclassregex matches or not set
		  */
		cssclass: '',
		
		/**
		 * the defined object types to be used for this instance
		 */
		objectTypeFilter: [],
		
		/**
		 * handle change on href change
		 * called function ( obj, href, item );
		 */
		onHrefChange: null,
		
		/**
		 * This variable is used to ignore one selection changed event. We need
		 * to ignore one selectionchanged event when we set our own selection.
		 */
		ignoreNextSelectionChangedEvent: false,
		
		/**
		 * Internal update interval reference to work around an ExtJS bug
		 */
		hrefUpdateInt: null,
		
		/**
		 * HotKeys used for special actions
		*/
		hotKey: {
			insertLink: i18n.t('insertLink', 'ctrl+k')
		},
		
		/**
		 * Default input value for a new link
		*/
		hrefValue: 'http://',
		
		/**
		 * Initialize the plugin
		 */
		init: function () {
			var plugin = this;

			if ('undefined' !== typeof this.settings.title) {
				this.title = this.settings.title;
			}
			if ('undefined' !== typeof this.settings.titleregex) {
				this.titleregex = this.settings.titleregex;
			}
			if ( typeof this.settings.targetregex != 'undefined' ) {
				this.targetregex = this.settings.targetregex;
			}
			if ( typeof this.settings.target != 'undefined' ) {
				this.target = this.settings.target;
			}
			if ( typeof this.settings.cssclassregex != 'undefined' ) {
				this.cssclassregex = this.settings.cssclassregex;
			}
			if ( typeof this.settings.cssclass != 'undefined' ) {
				this.cssclass = this.settings.cssclass;
			}
			if ( typeof this.settings.objectTypeFilter != 'undefined' ) {
				this.objectTypeFilter = this.settings.objectTypeFilter;
			}
			if ( typeof this.settings.onHrefChange != 'undefined' ) {
				this.onHrefChange = this.settings.onHrefChange;
			}
			if ( typeof this.settings.hotKey != 'undefined' ) {
				jQuery.extend(true, this.hotKey, this.settings.hotKey);
			}
			if ( typeof this.settings.hrefValue != 'undefined' ) {
				this.hrefValue = this.settings.hrefValue;
			}
			
			this.createButtons();
			this.subscribeEvents();
			this.bindInteractions();

			Aloha.bind('aloha-plugins-loaded', function () {
				plugin.initSidebar(Aloha.Sidebar.right);
				PubSub.pub('aloha.link.ready', {
					plugin: plugin
				})
			});
		},

		nsSel: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each( arguments, function () {
				stringBuilder.push( '.' + ( this == '' ? prefix : prefix + '-' + this ) );
			} );
			return jQuery.trim(stringBuilder.join(' '));
		},

		//Creates string with this component's namepsace prefixed the each classname
		nsClass: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each( arguments, function () {
				stringBuilder.push( this == '' ? prefix : prefix + '-' + this );
			} );
			return jQuery.trim(stringBuilder.join(' '));
		},

		initSidebar: function ( sidebar ) {
			var pl = this;
			pl.sidebar = sidebar;
			sidebar.addPanel( {
				
				id       : pl.nsClass( 'sidebar-panel-target' ),
				title    : i18n.t( 'floatingmenu.tab.link' ),
				content  : '',
				expanded : true,
				activeOn : 'a, link',
				
				onInit: function () {
					 var that = this,
						 content = this.setContent(
							'<div class="' + pl.nsClass( 'target-container' ) + '"><fieldset><legend>' + i18n.t( 'link.target.legend' ) + '</legend><ul><li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_self" /><span>' + i18n.t( 'link.target.self' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_blank" /><span>' + i18n.t( 'link.target.blank' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_parent" /><span>' + i18n.t( 'link.target.parent' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="_top" /><span>' + i18n.t( 'link.target.top' ) + '</span></li>' + 
							'<li><input type="radio" name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '" value="framename" /><span>' + i18n.t( 'link.target.framename' ) + '</span></li>' + 
							'<li><input type="text" class="' + pl.nsClass( 'framename' ) + '" /></li></ul></fieldset></div>' + 
							'<div class="' + pl.nsClass( 'title-container' ) + '" ><fieldset><legend>' + i18n.t( 'link.title.legend' ) + '</legend><input type="text" class="' + pl.nsClass( 'linkTitle' ) + '" /></fieldset></div>'
						).content; 
					 
					 jQuery( pl.nsSel( 'framename' ) ).live( 'keyup', function () {
						jQuery( that.effective ).attr( 'target', jQuery( this ).val().replace( '\"', '&quot;' ).replace( "'", "&#39;" ) );
					 } );
					 
					 jQuery( pl.nsSel( 'radioTarget' ) ).live( 'change', function () {
						if ( jQuery( this ).val() == 'framename' ) {
							jQuery( pl.nsSel( 'framename' ) ).slideDown();
						} else {
							jQuery( pl.nsSel( 'framename' ) ).slideUp().val( '' );
							jQuery( that.effective ).attr( 'target', jQuery( this ).val() );
						}
					 } );
					 
					 jQuery( pl.nsSel( 'linkTitle' ) ).live( 'keyup', function () {
						jQuery( that.effective ).attr( 'title', jQuery( this ).val().replace( '\"', '&quot;' ).replace( "'", "&#39;" ) );
					 } );
				},
				
				onActivate: function ( effective ) {
					var that = this;
					that.effective = effective;
					if ( jQuery( that.effective ).attr( 'target' ) != null ) {
						var isFramename = true;
						jQuery( pl.nsSel( 'framename' ) ).hide().val( '' );
						jQuery( pl.nsSel( 'radioTarget' ) ).each( function () {
							jQuery( this ).removeAttr('checked');
							if ( jQuery( this ).val() === jQuery( that.effective ).attr( 'target' ) ) {
								isFramename = false;
								jQuery( this ).attr( 'checked', 'checked' );
							}
						} );
						if ( isFramename ) {
							jQuery( pl.nsSel( 'radioTarget[value="framename"]' ) ).attr( 'checked', 'checked' );
							jQuery( pl.nsSel( 'framename' ) )
								.val( jQuery( that.effective ).attr( 'target' ) )
								.show();
						}
					} else {
						jQuery( pl.nsSel( 'radioTarget' ) ).first().attr( 'checked', 'checked' );
						jQuery( that.effective ).attr( 'target', jQuery( pl.nsSel( 'radioTarget' ) ).first().val() );
					}
					
					var that = this;
					that.effective = effective;
					jQuery( pl.nsSel( 'linkTitle' ) ).val( jQuery( that.effective ).attr( 'title' ) );
				}
				
			} );
			
			sidebar.show();
		},
		
		/**
		 * Subscribe for events
		 */
		subscribeEvents: function () {
			var that = this,
			    isEnabled = {};

			var editablesCreated = 0;

			// add the event handler for creation of editables
			Aloha.bind('aloha-editable-created', function (event, editable) {
				var config = that.getEditableConfig(editable.obj),
				    enabled = (jQuery.inArray('a', config) !== -1);

				isEnabled[editable.getId()] = enabled;

				if (!enabled) {
					return;
				}

				// enable hotkey for inserting links
				editable.obj.bind('keydown.aloha-link', that.hotKey.insertLink, function() {
					if ( that.findLinkMarkup() ) {
						// open the tab containing the href
						that.hrefField.foreground();
						that.hrefField.focus();
					} else {
						that.insertLink(true);
					}
					return false;
				} );

				editable.obj.find('a').each(function() {
					that.addLinkEventHandlers(this);
				});

				if (0 === editablesCreated++) {
					setupMousePointerFix();
				}
			});

			Aloha.bind('aloha-editable-destroyed', function (event, editable) {
				editable.obj.unbind('.aloha-link');
				if (0 === --editablesCreated) {
					teardownMousePointerFix();
				}
			});

			Aloha.bind('aloha-editable-activated', function(event, props) {
				if (isEnabled[Aloha.activeEditable.getId()]) {
					that._formatLinkButton.show();
					that._insertLinkButton.show();
				} else {
					that._formatLinkButton.hide();
					that._insertLinkButton.hide();
				}
				setupMetaClickLink(props.editable);
			});

			var insideLinkScope = false;

			Aloha.bind('aloha-selection-changed', function(event, rangeObject){
				var enteredLinkScope = false;
				if (Aloha.activeEditable && isEnabled[Aloha.activeEditable.getId()]) {
					enteredLinkScope = selectionChangeHandler(that, rangeObject);
					// Only foreground the tab containing the href field
					// the first time the user enters the link scope to
					// avoid intefering with the user's manual tab
					// selection.
					if (enteredLinkScope && insideLinkScope !== enteredLinkScope) {
						that.hrefField.foreground();
					}
				}
				insideLinkScope = enteredLinkScope;
			});

			// Fixes problem: if one clicks from inside an aloha link
			// outside the editable and thereby deactivates the
			// editable, the link scope will remain active.
			var linkPlugin = this;
			Aloha.bind('aloha-editable-deactivated', function (event, props) {
				if (insideLinkScope) {
					// Leave the link scope lazily to avoid flickering
					// when switching between anchor element editables.
					setTimeout(function () {
						if (!insideLinkScope) {
							linkPlugin.toggleLinkScope(false);
						}
					}, 100);
					insideLinkScope = false;
				}
				teardownMetaClickLink(props.editable);
			});
		},

		/**
		 * lets you toggle the link scope to true or false
		 * @param show bool
		 */
		toggleLinkScope: function ( show ) {
			// Check before doing anything as a performance improvement.
			// The _isScopeActive_editableId check ensures that when
			// changing from a normal link in an editable to an editable
			// that is a link itself, the removeLinkButton will be
			// hidden.
			if (this._isScopeActive === show && Aloha.activeEditable && this._isScopeActive_editableId === Aloha.activeEditable.getId()) {
				return;
			}
			this._isScopeActive = show;
			this._isScopeActive_editableId = Aloha.activeEditable && Aloha.activeEditable.getId();
			if ( show ) {
				this.hrefField.show();
				this._insertLinkButton.hide();
				// Never show the removeLinkButton when the link itself
				// is the editable.
				if (Aloha.activeEditable && Aloha.activeEditable.obj[0].nodeName === 'A') {
					this._removeLinkButton.hide();
				} else {
					this._removeLinkButton.show();
				}
				this._formatLinkButton.setState(true);
				Scopes.enterScope(this.name, 'link');
			} else {
				this.hrefField.hide();
				this._insertLinkButton.show();
				this._removeLinkButton.hide();
				this._formatLinkButton.setState(false);
				// The calls to enterScope and leaveScope by the link
				// plugin are not balanced.
				// When the selection is changed from one link to
				// another, the link scope is incremented more than
				// decremented, which necessitates the force=true
				// argument to leaveScope.
				Scopes.leaveScope(this.name, 'link', true);
			}
		},

		/**
		 * Add event handlers to the given link object
		 * @param link object
		 */
		addLinkEventHandlers: function ( link ) {
			var that = this;

			// show pointer on mouse over
			jQuery( link ).mouseenter( function ( e ) {
				Aloha.Log.debug( that, 'mouse over link.' );
				that.mouseOverLink = link;
				that.updateMousePointer();
			} );

			// in any case on leave show text cursor
			jQuery( link ).mouseleave( function ( e ) {
				Aloha.Log.debug( that, 'mouse left link.' );
				that.mouseOverLink = null;
				that.updateMousePointer();
			} );

			// follow link on ctrl or meta + click
			jQuery( link ).click( function ( e ) {
				if ( e.metaKey ) {
					// blur current editable. user is waiting for the link to load
					Aloha.activeEditable.blur();
					// hack to guarantee a browser history entry
					window.setTimeout( function () {
						location.href = e.target;
					}, 0 );
					e.stopPropagation();

					return false;
				}
			} );
		},

		/**
		 * Initialize the buttons
		 */
		createButtons: function () {
			var that = this;

			this._formatLinkButton = Ui.adopt("formatLink", ToggleButton, {
				tooltip: i18n.t("button.addlink.tooltip"),
				icon: "aloha-icon aloha-icon-link",
				scope: 'Aloha.continuoustext',
				click: function() {
					that.formatLink();
				}
			});

			this._insertLinkButton = Ui.adopt("insertLink", Button, {
				tooltip: i18n.t("button.addlink.tooltip"),
				icon: "aloha-icon aloha-icon-link",
				scope: 'Aloha.continuoustext',
				click: function() {
					that.insertLink(false);
				}
			});
			
			this.hrefField = AttributeField({
				name: 'editLink',
				width: 320,
				valueField: 'url',
				cls: 'aloha-link-href-field',
				scope: 'Aloha.continuoustext',
				noTargetHighlight: false,
				targetHighlightClass: 'aloha-focus'
			});
			this.hrefField.setTemplate('<span><b>{name}</b><br/>{url}</span>');
			this.hrefField.setObjectTypeFilter( this.objectTypeFilter );

			this._removeLinkButton = Ui.adopt("removeLink", Button, {
				tooltip: i18n.t("button.removelink.tooltip"),
				icon: "aloha-icon aloha-icon-unlink",
				scope: 'Aloha.continuoustext',
				click: function() {
					that.removeLink();
				}
			});
		},

		/**
		 * Parse a all editables for links and bind an onclick event
		 * Add the link short cut to all edtiables
		 */
		bindInteractions: function () {
			var that = this;

			// update link object when src changes
			this.hrefField.addListener( 'keyup', function ( event ) {
				if (Keys.getToken(event.keyCode) === 'escape') {
					var curval = that.hrefField.getValue();
					if ( curval[ 0 ] == '/' || // local link
						 curval[ 0 ] == '#' || // inner document link
						 curval.match( /^.*\.([a-z]){2,4}$/i ) || // local file with extension
						 curval.match( /^([a-z]){3,10}:\/\/.+/i ) || // external link (http(s), ftp(s), ssh, file, skype, ... )
						 curval.match( /^(mailto|tel):.+/i ) // mailto / tel link
					) {
						// could be a link better leave it as it is
					} else {
						// the user searched for something and aborted
						var hrefValue = that.hrefField.getValue();
						
						// restore original value and hide combo list
						that.hrefField.setValue( hrefValue );
						
						if ( hrefValue == that.hrefValue || hrefValue == '' ) {
							that.removeLink( false );
						}
						
					}
				}
				
				that.hrefChange();
				
				// Terminate the link scope and show the final link.
				if (Keys.getToken(event.keyCode) === 'enter') {
					// Update the selection and place the cursor at the end of the link.
					var	range = Aloha.Selection.getRangeObject();
					
					// workaround to keep the found markup otherwise removelink won't work
//					var foundMarkup = that.findLinkMarkup( range );
//					console.dir(foundMarkup);
//					that.hrefField.setTargetObject(foundMarkup, 'href');
					
					// We have to ignore the next 2 onselectionchange events.
					// The first one we need to ignore is the one trigger when
					// we reposition the selection to right at the end of the
					// link.
					// Not sure what the next event is yet but we need to
					// ignore it as well, ignoring it prevents the value of
					// hrefField from being set to the old value.
					that.ignoreNextSelectionChangedEvent = true;
					range.startContainer = range.endContainer;
					range.startOffset = range.endOffset;
					range.select();
					that.ignoreNextSelectionChangedEvent = true;
					
					var hrefValue = jQuery( that.hrefField.getInputElem() ).attr( 'value' );
					
					if ( hrefValue == that.hrefValue || hrefValue == '' ) {
						that.removeLink( false );
					}
					
					window.setTimeout( function () {
						Scopes.setScope('Aloha.continuoustext');
					}, 100 );
				} else {
					// Check whether the value in the input field has changed
					// because if it has, then the ui-attribute object's store
					// needs to be cleared. The reason we need to do this
					// clearing is because once the auto-suggeset combo box is
					// shown and/or populated, the next enter keypress event
					// would be handled as if the user is selecting one of the
					// elements in the down down list.
					newValue = jQuery( that.hrefField.getInputElem() ).attr( 'value' );
					if ( oldValue != newValue ) {
						oldValue = newValue;
					}
				}
			});

			jQuery( document )
				.keydown( function ( e ) {
					Aloha.Log.debug( that, 'Meta key down.' );
					that.metaKey = e.metaKey;
					that.updateMousePointer();
				} ).keyup( function ( e ) {
					Aloha.Log.debug( that, 'Meta key up.' );
					that.metaKey = e.metaKey;
					that.updateMousePointer();
				} );
		},
		
		/**
		 * Updates the mouse pointer
		 */
		updateMousePointer: function () {
			if ( this.metaKey && this.mouseOverLink ) {
				Aloha.Log.debug( this, 'set pointer' );
				jQuery( this.mouseOverLink ).removeClass( 'aloha-link-text' );
				jQuery( this.mouseOverLink ).addClass( 'aloha-link-pointer' );
			} else {
				jQuery( this.mouseOverLink ).removeClass( 'aloha-link-pointer' );
				jQuery( this.mouseOverLink ).addClass( 'aloha-link-text' );
			}
		},

		/**
		 * Check whether inside a link tag
		 * @param {GENTICS.Utils.RangeObject} range range where to insert the
		 *			object (at start or end)
		 * @return markup
		 * @hide
		 */
		findLinkMarkup: function ( range ) {
			if ( typeof range == 'undefined' ) {
				range = Aloha.Selection.getRangeObject();
			}
			if ( Aloha.activeEditable ) {
				// If the anchor element itself is the editable, we
				// still want to show the link tab.
				var limit = Aloha.activeEditable.obj;
				if (limit[0] && limit[0].nodeName === 'A') {
					limit = limit.parent();
				}
				return range.findMarkup(function () {
					return this.nodeName == 'A';
				}, limit);
			} else {
				return null;
			}
		},

		/**
		 * Format the current selection or if collapsed the current word as
		 * link. If inside a link tag the link is removed.
		 */
		formatLink: function () {
			if ( Aloha.activeEditable ) {
				if ( this.findLinkMarkup( Aloha.Selection.getRangeObject() ) ) {
					this.removeLink();
				} else {
					this.insertLink();
				}
			}
		},

		/**
		 * Insert a new link at the current selection. When the selection is
		 * collapsed, the link will have a default link text, otherwise the
		 * selected text will be the link text.
		 */
		insertLink: function ( extendToWord ) {
			var that = this,
			    range = Aloha.Selection.getRangeObject(),
			    linkText,
			    newLink;
			
			// There are occasions where we do not get a valid range, in such
			// cases we should not try and add a link
			if ( !( range.startContainer && range.endContainer ) ) {
				return;
			}
			
			// do not nest a link inside a link
			if ( this.findLinkMarkup( range ) ) {
				return;
			}
			
			// activate floating menu tab
			this.hrefField.foreground();
			
			// if selection is collapsed then extend to the word.
			if ( range.isCollapsed() && extendToWord !== false ) {
				GENTICS.Utils.Dom.extendToWord( range );
			}
			if ( range.isCollapsed() ) {
				// insert a link with text here
				linkText = i18n.t( 'newlink.defaulttext' );
				newLink = jQuery( '<a href="' + that.hrefValue + '" class="aloha-new-link">' + linkText + '</a>' );
				GENTICS.Utils.Dom.insertIntoDOM( newLink, range, jQuery( Aloha.activeEditable.obj ) );
				range.startContainer = range.endContainer = newLink.contents().get( 0 );
				range.startOffset = 0;
				range.endOffset = linkText.length;
			} else {
				newLink = jQuery( '<a href="' + that.hrefValue + '" class="aloha-new-link"></a>' );
				GENTICS.Utils.Dom.addMarkup( range, newLink, false );
				GENTICS.Utils.Dom.doCleanup(insertLinkPostCleanup, range);
			}

			Aloha.activeEditable.obj.find( 'a.aloha-new-link' ).each( function ( i ) {
				that.addLinkEventHandlers( this );
				jQuery(this).removeClass( 'aloha-new-link' );
			} );

			range.select();


			// focus has to become before prefilling the attribute, otherwise
			// Chrome and Firefox will not focus the element correctly.
			this.hrefField.focus();
			
			// prefill and select the new href
			// We need this guard because sometimes the element has not yet been initialized
			if ( this.hrefField.hasInputElem() ) {
				jQuery( this.hrefField.getInputElem() ).attr( 'value', that.hrefValue ).select();
			}
			
			// because the Aloha Selection is deprecated I need to convert it to a ragne
			var apiRange = Aloha.createRange();
			apiRange.setStart(range.startContainer, range.startOffset);
			apiRange.setEnd(range.endContainer, range.endOffset);

			PubSub.pub('aloha.link.insert', {range: apiRange});
			this.hrefChange();
		},

		/**
		 * Remove an a tag and clear the current item from the hrefField
		 */
		removeLink: function ( terminateLinkScope ) {
			var	range = Aloha.Selection.getRangeObject(),
				foundMarkup = this.findLinkMarkup();
			
			// clear the current item from the href field
			this.hrefField.setItem(null);
			if ( foundMarkup ) {
				// remove the link
				GENTICS.Utils.Dom.removeFromDOM( foundMarkup, range, true );

				range.startContainer = range.endContainer;
				range.startOffset = range.endOffset;

				// select the (possibly modified) range
				range.select();
				
				if ( typeof terminateLinkScope == 'undefined' ||
						terminateLinkScope === true ) {
					Scopes.setScope('Aloha.continuoustext');
				}
			}
		},

		/**
		 * Automatically sets (or unsets) the title attribute value of the given
		 * AttributeField's target anchor element based on the link's href
		 * value.
		 *
		 * @param {AttributeField} field The AttributeField that is to be used.
		 * @param {string} value The value to which the title attribute is to be
		 *                       set to.
		 * @param {string} regex A string representing a regular expression
		 *                       against which to test the href value of the
		 *                       AttributeField `field`, to predicate whether
		 *                       the title field should be update or not.
		 */
		automaticallySetTitle: function (field, value, regex) {
			var currentValue = jQuery(field.getTargetObject()).attr('title');
			var canOverwriteTitle = !currentValue || value === currentValue;
			if (value && canOverwriteTitle) {
				field.setAttribute('title', value, regex, field.getValue());
			}
		},

		/**
		 * Updates the link object depending on the src field
		 */
		hrefChange: function () {
			var that = this;

			this.automaticallySetTitle(
				this.hrefField,
				this.title,
				this.titleregex
			);

			// For now hard coded attribute handling with regex.
			// Avoid creating the target attribute, if it's unnecessary, so
			// that XSS scanners (AntiSamy) don't complain.
			if ( this.target != '' ) {
				this.hrefField.setAttribute(
					'target',
					this.target,
					this.targetregex,
					this.hrefField.getValue()
				);
			}
			
			if (null != this.cssclassregex) {
				this.hrefField.setAttribute(
					'class',
					this.cssclass,
					this.cssclassregex,
					this.hrefField.getValue()
				);
			}

			var href = that.hrefField.getValue();
			var element = that.hrefField.getTargetObject();
			
			Aloha.trigger('aloha-link-href-change', {
				 href: href,
				 obj: element,
				 item: that.hrefField.getItem()
			});

			PubSub.pub('aloha.link.changed', {
				href: href,
				element: element,
				input: that.hrefField.getInputElem()
			});
			
			if ( typeof this.onHrefChange == 'function' ) {
				this.onHrefChange.call(
					this,
					this.hrefField.getTargetObject(),
					this.hrefField.getValue(),
					this.hrefField.getItem()
				);
			}
		}
	});

	/**
	 * Add additional target objects, in case the selection includes
	 * several links tag
	 *
	 * @param {RangeObject} rangeObject Selection Range
	 * @param {LinkPlugin} that Link Plugin object
	 */
	function addAdditionalTargetObject(rangeObject, field) {
		var links = rangeObject.findAllMarkupByTagName('A', rangeObject);
		for (var i = 0, len = links.length; i < len; i++) {
			field.addAdditionalTargetObject(links[i]);
		}
	}

	/**
	 * Selection change handler.
	 *
	 * @param {LinkPlugin} that This Link Plugin object
	 * @param {RangeObject} rangeObject Selection Range
	 * @returns {boolean} True if the link Scope was activated,
	 *                    False otherwise
	 */
	function selectionChangeHandler(that, rangeObject) {
		var foundMarkup,
		    enteredLinkScope = false;

		// Check if we need to ignore this selection changed event for
		// now and check whether the selection was placed within a
		// editable area.
		if (!that.ignoreNextSelectionChangedEvent &&
			Aloha.Selection.isSelectionEditable() &&
			Aloha.activeEditable != null ) {
			
			foundMarkup = that.findLinkMarkup(rangeObject);

			if (foundMarkup) {
				that.toggleLinkScope(true);

				// now we are ready to set the target object
				that.hrefField.setTargetObject(foundMarkup, 'href');
				addAdditionalTargetObject(rangeObject, that.hrefField);
				// if the selection-changed event was raised by the first click interaction on this page
				// the hrefField component might not be initialized. When the user switches to the link
				// tab to edit the link the field would be empty. We check for that situation and add a
				// special interval check to set the value once again
				if (jQuery('#' + that.hrefField.getInputId()).length == 0) {
					// there must only be one update interval running at the same time
					if (that.hrefUpdateInt !== null) {
						clearInterval(that.hrefUpdateInt);
					}
					
					// register a timeout that will set the value as soon as the href field was initialized
					that.hrefUpdateInt = setInterval( function () {
						if (jQuery( '#' + that.hrefField.getInputId()).length > 0) { // the object was finally created
							that.hrefField.setTargetObject(foundMarkup, 'href');
							clearInterval(that.hrefUpdateInt);
						}
					}, 200);
				}
				Aloha.trigger('aloha-link-selected');
				enteredLinkScope = true;

				PubSub.pub('aloha.link.selected', {
					input: that.hrefField.getInputElem(),
					href: that.hrefField.getValue(),
					element: that.hrefField.getTargetObject()
				});
			} else {
				that.toggleLinkScope(false);
				that.hrefField.setTargetObject(null);
				Aloha.trigger('aloha-link-unselected');
			}
		} else {
			that.toggleLinkScope(false);
		}
		
		that.ignoreNextSelectionChangedEvent = false;
		return enteredLinkScope;
	}
} );

define('ui/dialog',[
	'jquery',
	'ui/component',
	'i18n!ui/nls/i18n'
], function (
	$,
	Component,
	i18n
) {
	

	function makeDialogDiv(props) {
		var textOrHtml = {};
		if (props.text) {
			textOrHtml.text = props.text;
		}
		if (props.html) {
			textOrHtml.html = props.html;
		}
		return $('<div>', textOrHtml);
	}

	/**
	 * Wraps the callback function so to destory the dialog when the callback is
	 * invoked.
	 *
	 * @param {function} callback
	 * @return {function} Wrapped callback.
	 */
	function callbackAndDestroy(callback) {
		return function () {
			callback.apply(this);
			$(this).dialog('destroy').remove();
		};
	}

	function wrapDialogButtons(buttons) {
		// Buttons automatically close the dialog for convenience
		var title;
		for (title in buttons) {
			if (buttons.hasOwnProperty(title)) {
				buttons[title] = callbackAndDestroy(buttons[title]);
			}
		}
		return buttons;
	}

	function makeDialogProps(props, defaultTitle) {
		// All root elements of widgets added to the page by aloha should have the class 'aloha'.
		// aloha-dialog is used for a hack to prevent a click in the
		// dialog from bluggin the editable search for aloha-dialog in
		// the aloha core for more information.
		var cls = 'aloha aloha-dialog';
		if (props.cls) {
			cls += ' ' + props.cls;
		}
		return {
			'resizable': false,
			'modal': true,
			'title': props.title || defaultTitle,
			'dialogClass': cls,
			'zIndex': 10200
		};
	}

	return {
		/**
		 * Shows a confirm dialog.
		 *
		 * A confirm dialog has a confirm icon and style and yes and no buttons.
		 *
		 * @param props is an object with the following properties (all optional):
		 *          title - the title of the dialog
		 *           text - either the text inside the dialog
		 *           html - or the html inside the dialog
		 *            yes - makes a "Yes" button in the dialog and invokes the given callback if it is pressed.
		 *             no - makes a "No" button in the dialog and invokes the given callback if it is pressed.
		 *         answer - makes a "Yes" and "No" button in the dialog and pressing either will invoke the
		 *                  callback with the answer as a boolean argument. Does not interfere with yes and
		 *                  no properties.
		 *            cls - the root element of the dialog will receive this class
		 *        buttons - an object where the properties are button titles and the values are callbacks
		 *        Button callbacks will receive the dialog element as context.
		 *        Pressing any buttons in the dialog will automatically close the dialog.
		 * @return
		 *        A function that can be called to close the dialog.
		 */
		'confirm': function (props) {
			var buttons = props.buttons || {};

			var yesLabel = i18n.t('button.yes.label');
			var noLabel = i18n.t('button.no.label');

			// block adds backwards compatibility to still be able to use
			// 'buttons.Yes/No' for setting functionality of basic buttons
			if (buttons.Yes !== null && yesLabel !== 'Yes') {
				buttons[yesLabel] = buttons.Yes;
				delete buttons.Yes;
			}
			if (buttons.No !== null && noLabel !== 'No') {
				buttons[noLabel] = buttons.No;
				delete buttons.No;
			}

			buttons[yesLabel] = buttons[yesLabel] || props.yes || $.noop;
			buttons[noLabel]  = buttons[noLabel]  || props.no  || $.noop;

			if (props.answer) {
				var yes = buttons[yesLabel];
				var no  = buttons[noLabel];
				buttons[yesLabel] = function () {
					yes();
					props.answer(true);
				};
				buttons[noLabel] = function () {
					no();
					props.answer(false);
				};
			}
			var dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, 'Confirm'), {
					'buttons': wrapDialogButtons(buttons)
				})
			);
			return function () {
				dialog.dialog('destroy').remove();
			};
		},

		/**
		 * Shows an alert dialog.
		 *
		 * An alert dialog has an alert icon and style and a dismiss button.
		 *
		 * @param props is an object with the following properties (all optional)
		 *        title - the title of the dialog
		 *        text - either the text inside the dialog
		 *        html - or the html inside the dialog
		 *        cls - the root element of the dialog will receive this class
		 * @return
		 *        A function that can be called to close the dialog.
		 */
		'alert': function (props) {
			var propsExtended = {};
			propsExtended[i18n.t('button.dismiss.label')] = $.noop;
			var dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, 'Alert'), {
					'buttons': wrapDialogButtons(propsExtended)
				})
			);
			return function () {
				dialog.dialog('destroy').remove();
			};
		},

		/**
		 * Shows a stripped down modal dialog that can be customized.
		 *
		 * @param {object} props Properties with which to customize the
		 *                       modal.  All properties of
		 *                       api.jqueryui.com/dialog apply, in addition to
		 *                       the following optional properties:
		 *                       html - HTML contents to be placed inside the
		 *                              modal.
		 *                        cls - Custom class to be given to the modal's
		 *                              root element.
		 * @return {jQuery.<HTMLElement>} jQuery object containing the dialog
		 *                                DOM element.
		 */
		'modal': function (props) {
			var $dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, ''), props)
			);
			$dialog.parent().find('.ui-dialog-titlebar').remove();
			return $dialog;
		},

		/**
		 * Shows a progress dialog.
		 *
		 * A progress dialog shows a progressbar and a message to
		 * indicate that some process is in progress.
		 *
		 * @param props is an object with the following properties (all optional)
		 *        title - the title of the dialog
		 *         text - either the text inside the dialog
		 *         html - or the html inside the dialog
		 *          cls - the root element of the dialog will receive this class
		 *        value - the intial value of the progressbar from 0 to 100
		 * @return
		 *        A function that can be called to update the progress bar with a value from 0 to 100.
		 *        If null or undefined is passed, the dialog will be closed.
		 */
		'progress': function (props) {
			var progressbar = $("<div>").progressbar({
				// TODO if no initial value is specific, show a full but an animated progress bar instead
				value: null != props.value ? props.value : 100
			});
			var dialog = makeDialogDiv(props).dialog(
				$.extend(makeDialogProps(props, 'Progress'), {
					open: function () {
						$(this).append(progressbar);
					}
				})
			);
			return function (value) {
				if (null != value) {
					progressbar.progressbar({ value: value });
				} else {
					dialog.dialog('destroy').remove();
				}
			};
		}
	};
});

/*global define: true */
/**
 * The multiSplit component groups multiple buttons and other
 * button-like items into an expandable menu.
 */
define('ui/multiSplit',[
	'jquery',
	'ui/component',
	'ui/button',
	'ui/utils'
], function (
	$,
	Component,
	Button,
	Utils
) {
	

	/**
	 * MultiSplit component type.
	 * @class
	 * @api
	 * @extends {Component}
	 */
	var MultiSplit = Component.extend({

		_activeButton: null,
		_isOpen: false,

		/**
		 * @override
		 */
		init: function () {
			this._super();
			var multiSplit = this;
			var element = this.element = $('<div>', {
				'class': 'aloha-multisplit'
			});
			var content = this.contentElement = $('<div>', {
				'class': 'aloha-multisplit-content'
			}).appendTo(element);
			var toggle = this.toggleButton = Utils.makeButtonElement({
				'class': 'aloha-multisplit-toggle',
				click: function () {
					multiSplit.toggle();
				}
			}).button().appendTo(element);

			this.buttons = [];

			var buttons = this.getButtons();

			if (0 === buttons.length) {
				element.hide();
			}

			for (var i = 0; i < buttons.length; i++) {
				this.addButton(buttons[i]);
			}

			$('body').click(function (event) {
				if (multiSplit._isOpen &&
			        !multiSplit.element.is(event.target) &&
			        0 === multiSplit.element.find(event.target).length) {
					multiSplit.close();
				}
			});
		},

		/**
		 * Adds a button to this multisplit component.
		 *
		 * @param button
		 *        - name 
		 *          The name of the button.
		 *          Used by setActiveButton(name), show(name),
		 *          hide(name) to identify the correct button.
		 *
		 *        - tooltip
		 *        - icon
		 *          The tooltip and icon. 
		 *
		 *        - wide
		 *          A boolean indicating whether the button should
		 *          extend the entire width of the multi split button.
		 *
		 *        - init
		 *          An optional init function. Will be invoked with the
		 *          button component instance as context.
		 *
		 *        - click
		 *          A click handler for the button.
		 * @api
		 */
		addButton: function (props) {
			var multiSplit = this;

			var component = new (Button.extend({
				tooltip: props.tooltip,
				icon: props.wide ? props.icon : 'aloha-large-icon ' + props.icon,
				iconOnly: props.wide ? false : true,
				init: function () {
					this._super();
					if (props.init) {
						props.init.call(this);
					}
				},
				click: function () {
					props.click.apply(multiSplit, arguments);
					multiSplit.close();
				}
			}))();
			if (!props.wide) {
				component.element.addClass('aloha-large-button');
			}

			multiSplit.buttons[props.name] = {
				settings: props,
				component: component,
				element: component.element,
				visible: true
			};

			// Ensure non-wide buttons always come before wide buttons
			var fullwidthButtons = this.contentElement.children('.aloha-ui-multisplit-fullwidth');
			if (!props.wide && fullwidthButtons.length) {
				fullwidthButtons.before(component.element[0]);
			} else {
				this.contentElement.append(component.element[0]);
			}
		},

		/**
		 * @api
		 */
		setActiveButton: function (name) {
			if (!name) {
				name = null;
			}
			if (null !== this._activeButton) {
				this.buttons[this._activeButton]
				    .element.removeClass('aloha-multisplit-active');
			}
			this._activeButton = name;
			if (null !== name) {
				this.buttons[name]
				    .element.addClass('aloha-multisplit-active');
			}
		},

		/**
		 * Toggles the multisplit menu
		 * @api
		 */
		toggle: function () {
			this.element.toggleClass('aloha-multisplit-open');
			this._isOpen = !this._isOpen;
		},

		/**
		 * Opens the multisplit menu
		 * @api
		 */
		open: function () {
			this.element.addClass('aloha-multisplit-open');
			this._isOpen = true;
		},

		/**
		 * Closes the multisplit menu
		 * @api
		 */
		close: function () {
			this.element.removeClass('aloha-multisplit-open');
			this._isOpen = false;
		},

		/**
		 * Show the button with given index
		 * @api
		 * @param {Number} index button index
		 */
		show: function (name) {
			if (!name) {
				name = null;
			}
			if (null !== name && this.buttons[name] !== undefined) {
				this.buttons[name].element.show();
				this.buttons[name].visible = true;
				// since we show at least one button now, we need to show the multisplit button
				this.element.show();
			}
		},

		/**
		 * Hide the button with given index
		 * @api
		 * @param {Number} index button index
		 */
		hide: function (name) {
			var button, visible = false;

			if (!name) {
				name = null;
			}
			if (null !== name && this.buttons[name] !== undefined) {
				this.buttons[name].element.hide();
				this.buttons[name].visible = false;

				// now check, if there is a visible button
				for (button in this.buttons) {
					if (this.buttons.hasOwnProperty(button)) {
						if (this.buttons[button].visible) {
							this.element.show();
							visible = true;
							break;
						}
					}
				}

				if (!visible) {
					this.element.hide();
				}
			}
		}
	});

	/**
	 * This module is part of the Aloha API.
	 * It is valid to override this module via requirejs to provide a
	 * custom behaviour. An overriding module must implement all API
	 * methods. Every member must have an api annotation. No non-api
	 * members are allowed.
	 * @api
	 */
	return MultiSplit;
});

/*global define: true */
/**
 * This is a helper module for porting plugins from the old
 * ui-attributefield.js in the aloha core to the new ui-plugin.
 * This interface is obsolete and must not be used for new implementations.
 */
define('ui/port-helper-multi-split',[
	'aloha/core',
	'jquery',
	'ui/ui',
	'ui/multiSplit'
], function (
	Aloha,
	jQuery,
	Ui,
	MultiSplit
) {
	

	function MultiSplitButton(props) {
		var multiSplit;

		multiSplit = Ui.adopt(props.name, MultiSplit, {
			scope: props.scope,
			getButtons: function () {
				var buttons = [];
				jQuery.each(props.items, function (i, item) {
					buttons.push({
						tooltip: item.tooltip,
						text: item.text,
						name: item.name,
						icon: item.iconClass,
						click: item.click,
						init: function () {
							if (item.cls) {
								this.element.addClass(item.cls);
							}
							if (item.init) {
								item.init.call(this);
							}
						},
						wide: item.wide
					});
				});
				return buttons;
			}
		});

		return {
			// Expose this function so the cite-plugin can push its own
			// button to the format plugin's multi-split-button (which
			// is a disastrous hack I know).
			// TODO make it possible to combine the items of multiple
			// plugins into a single multi split button.
			pushItem: function (item) {
				multiSplit.addButton(item);
			},
			showItem: function (name) {
				multiSplit.show(name);
			},
			hideItem: function (name) {
				multiSplit.hide(name);
			},
			setActiveItem: function (name) {
				multiSplit.setActiveButton(name);
			}
		};
	}

	return MultiSplitButton;
});

define('table/nls/i18n',{
	"root":  {
		"floatingmenu.tab.table": "Table",
		"floatingmenu.tab.tablelayout": "Table Layout",
		"deleterows.confirm": "Do you really want to delete the selected rows?",
		"deletecolumns.confirm": "Do you really want to delete the selected columns?",
		"deletetable.confirm": "Do you really want to delete the table?",
		"Table": "Table",
		"button.createtable.tooltip": "Insert Table",
		"button.addcolleft.tooltip": "Add Column left",
		"button.addcolright.tooltip": "Add Column right",
		"button.delcols.tooltip": "Delete Columns",
		"button.addrowbefore.tooltip": "Add Row before",
		"button.addrowafter.tooltip": "Add Row after",
		"button.delrows.tooltip": "Delete Rows",
		"button.caption.tooltip": "Table caption",
		"button.naturalfit.tooltip": "Reset table sizing",
		"empty.caption": "Table caption",
		"button.removeFormat.tooltip": "Remove formatting",
		"button.removeFormat.text": "Remove formatting",
		"button.rowheader.tooltip": "Format row as table header",
		"button.columnheader.tooltip": "Format column as table header",
		"button.mergecells.tooltip": "Merge Cells",
		"button.splitcells.tooltip": "Split Cells",
		"table.label.target": "Summary",
		"table.sidebar.title": "Table",
		"table.mergeCells.notRectangular": "Only a rectangular selection can be merged",
		"table.addColumns.nonConsecutive": "Please select a single column or a consecutive range of columns",
		"table.createTable.nestedTablesNoSupported": "Sorry, nested tables are not supported"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

define('table/table-create-layer',
['jquery'],
function (jQuery) {
	/**
	 * Initialize of the CreateLayer object
	 */
	CreateLayer = function(TablePlugin){
		this.TablePlugin = TablePlugin;
	};

	/**
	 * Internal configuration of the create-table panel
	 */
	CreateLayer.prototype.parameters = {
		elemId: 'aloha-table-createLayer', // id of the create-table panel
		className: 'aloha-table-createdialog',   // class-name of the create-table panel
		numX: 10,	         // Number of cols in the create-layer
		numY: 10,            // Number of rows in the create-layer vertically
		layer: undefined,    // Attribute holding the create-layer
		target: undefined    // the clicktarget which was clicked on (mostly the button of the floatingmenu)
	};

	/**
	 * The configuration-object for the implementer of the plugin. All keys of
	 * the "parameters" object could be overwritten within this object and will
	 * simply be used instead.
	 */
	CreateLayer.prototype.config = new Object();

	/**
	 * Flag wether the CreateLayer is currently visble or not
	 */
	CreateLayer.prototype.visible = false;

	/**
	 * This function checks if there is an create-table-layer. If no layer exists, it creates one and puts it into the configuration.
	 * If the layer was already created it sets the position of the panel and shows it.
	 *
	 * @return void
	 */
	CreateLayer.prototype.show = function(){
		var layer = this.get('layer');

		// create the panel if the layer doesn't exist
		if (layer == null) {
			this.create();
		}else {
			// or reposition, cleanup and show the layer
			this.setPosition(layer);
			layer.find('td').removeClass('hover');
			layer.show();
		}
		this.visible = true;
	};

	/**
	 * Creates the div-layer which holds a table with the given number of rows and cols.
	 * It sets click and mouseover-events to the table data fields
	 *
	 * @return void
	 */
	CreateLayer.prototype.create = function () {
		var that = this;
		var layer = jQuery('<div></div>');
		layer.id = this.get('elemId');
		layer.addClass(this.get('className'));

		var table = jQuery('<table></table>');
		table.css('width', (this.get('numX') + 6) * 15);
		var tr;
		var td;

		for (var i = 0; i < this.get('numY'); i++) {
			tr = jQuery('<tr></tr>');

			for (var j = 0; j < this.get('numX'); j++) {
				td = jQuery('<td>\u00a0</td>');

				if (i == 0 && j == 0) {
					td.addClass('hover');
				}

				td.bind('mouseover', {rowId: i, colId: j}, function(e) {
					that.handleMouseOver(e, table);
				});

				td.bind('click', {rowId: i, colId: j}, function(e){
					var rows = e.data.rowId + 1;
					var cols = e.data.colId + 1;

					that.TablePlugin.createTable(cols, rows);
					that.hide();
				});

				tr.append(td);
			}
			table.append(tr);
		}
		layer.append(table);

		// set attributes
		this.set('layer', layer);
		this.setPosition();

		// stop bubbling the click on the create-dialog up to the body event
		layer.bind('click', function(e) {
			e.stopPropagation();
		}).mousedown(function(e) {
			e.stopPropagation();
		});

		// append layer to body and
		// hide the create layer if user clicks anywhere in the body
		jQuery('body').append(layer).bind('click', function(e) {
			// If the layer is visible and the event target is not the
			// button itself or a descendant of the button, hide the
			// layer.
			if (that.visible && !(e.target === that.get('target')[0] || jQuery.contains(that.get('target')[0], e.target))) {
				that.hide();
			}
		});
	};

	/**
	 * handles the mose over state for a cell
	 * @param e event object
	 * @param table the aeffected table
	 * @return void
	 */
	CreateLayer.prototype.handleMouseOver = function(e, table) {
		var rowId = e.data.rowId;
		var colId = e.data.colId;
		var innerRows = table.find('tr');

		for (var n = 0; n <= innerRows.length; n++) {
			var innerCells = jQuery(innerRows[n]).find('td');

			for (var k = 0; k <= innerCells.length; k++) {
				if (n <= rowId && k <= colId) {
					jQuery(innerCells[k]).addClass('hover');
				} else {
					jQuery(innerCells[k]).removeClass('hover');
				}
			}
		}
	};

	/**
	 * Sets the "left" and "top" style-attributes according to the clicked target-button
	 *
	 *  @return void
	 */
	CreateLayer.prototype.setPosition = function() {
		var targetObj = jQuery(this.get('target'));
		var pos = targetObj.offset();
		this.get('layer').css('left', pos.left + 'px');
		this.get('layer').css('top', (pos.top + targetObj.height()) + 'px');
	};

	/**
	 * Hides the create-table panel width the jQuery-method hide()
	 *
	 * @see jQuery().hide()
	 * @return void
	 */
	CreateLayer.prototype.hide = function() {
		this.get('layer').hide();
		this.visible = false;
	};

	/**
	 * The "get"-method returns the value of the given key. First it searches in the
	 * config for the property. If there is no property with the given name in the
	 * "config"-object it returns the entry associated with in the parameters-object
	 *
	 * @param property
	 * @return void
	 */
	CreateLayer.prototype.get = function(property) {
		// return param from the config
		if (this.config[property]) {
			return this.config[property];
		}
		// if config-param was not found return param from the parameters-object
		if (this.parameters[property]) {
			return this.parameters[property];
		}
		return undefined;
	};

	/**
	 * The "set"-method takes a key and a value. It checks if there is a key-value
	 * pair in the config-object. If so it saves the data in the config-object. If
	 * not it saves the data in the parameters-object.
	 *
	 * @param key
	 *            the key which should be set
	 * @param value
	 *            the value which should be set for the associated key
	 */
	CreateLayer.prototype.set = function (key, value) {
		// if the key already exists in the config-object, set it to the config-object
		if (this.config[key]) {
			this.config[key] = value;

			// otherwise "add" it to the parameters-object
		}else{
			this.parameters[key] = value;
		}
	};

	return CreateLayer;
});

define('table/table-plugin-utils',[
	'jquery',
	'aloha/copypaste',
	'util/browser',
	'aloha/console'
], function (
	$,
	CopyPaste,
	Browser,
    Console
) {
	

	function getAnchorCell(cells) {
		if (0 === cells.length) {
			return null;
		}

		var i;
		var editable;
		var range = CopyPaste.getRange();

		if (range) {
			editable = $(
				range.commonAncestorContainer
			).closest('.aloha-table-cell-editable')[0];
		}

		if (editable) {
			for (i = 0; i < cells.length; i++) {
				if ($(cells[i]).find(editable).length) {
					return cells[i];
				}
			}
		}

		return cells[0];
	}

	var Utils = {
		/**
		 * Translates the DOM-Element column offset of a table-cell to the
		 * column offset of a grid-cell, which is the column index adjusted
		 * by other cells' rowspan and colspan values.
		 *
		 * @param rows the rows of a table as an array or jQuery object
		 * @param rowIdx the index in rows of the cell to get the grid column index of
		 * @param colIdx the index in rows[row].cells of the cell to get the grid column index of
		 * @return the grid column index of the cell at the given rowIdx and colIdx, or null
		 *   if the given rowIdx and colIdx coordinates point to a cell outside of the table.
		 */
		'cellIndexToGridColumn': function (rows, rowIdx, colIdx) {
			var gridColumn = null;
			Utils.walkCells(rows, function(ri, ci, walkedGridColumn, rowspan, colspan) {
				if (ri === rowIdx && ci === colIdx) {
					gridColumn = walkedGridColumn;
					return false;
				}
			});
			return gridColumn;
		},
		/**
		 * Walks the table-cells of the table represented by the given rows,
		 * invoking the given callback for each.
		 * @param callback will receive the following arguments
		 *   o ri the row index of the table-cell
		 *   o ci the column index of the table-cell as the offset of the DOM-Element
		 *   o gridCi the column index of the table-cell in a virtual grid that overlays the table (see makeGrid())
		 *   o colspan the colspan attribute of the table-cell (as a number)
		 *   o rowspan the rowspan attribute of the table-cell (as a number)
		 *   returning false from the callback will terminate the walk early.
		 * @return void
		 */
		'walkCells': function (rows, callback) {
			var adjust = [];
			for (var ri = 0; ri < rows.length; ri++) {
				var cells = rows[ri].cells;
				var skip = 0;
				for (var ci = 0; ci < cells.length; ci++) {
					var cell = cells[ci];
					var colspan = Utils.colspan(cell);
					var rowspan = Utils.rowspan(cell);

					while (adjust[ci + skip]) {
						adjust[ci + skip] -= 1;
						skip += 1;
					}

					if (false === callback(ri, ci, ci + skip, colspan, rowspan)) {
						return;
					}

					for (var i = 0; i < colspan; i++) {
						if (adjust[ci + skip + i] ) {
							throw "an impossible case has occurred";
						}
						adjust[ci + skip + i] = rowspan - 1;
					}
					skip += colspan - 1;
				}
				for (; ci + skip < adjust.length; skip++) {
					if (adjust[ci + skip]) {
						adjust[ci + skip] -= 1;
					}
				}
			}
		},
		/**
		 * Makes a grid out of the table represented by the given rows.  A
		 * grid will contain one or multiple grid-cells for each table-cell.
		 * A table-cell that has a colspan or rowspan greater than one will
		 * be represented by multiple cells (colspan*rowspan) in the
		 * grid.
		 * @parm rows either an array or an jQuery object holding the DOM
		 *   rows of the table.
		 * @return the table translated to a grid of the form
		 *   [ [cell11, cell12, cell13, ...],
		 *     [cell21, cell22, cell23, ...],
		 *     ... ]
		 *  where each grid-cell is an object containing the following members:
		 *  cell: the DOM object in the table that is rendered into the grid-cell
		 *  colspan: the colspan attribute of the DOM object (as a number)
		 *  rowspan: the rowspan attribute of the DOM object (as a number)
		 *  spannedX: the row offset of the grid-cell in the table-cell (0 based)
		 *  spannedY: the column offset of the grid-cll in the table-cell (0 based)
		 */
		'makeGrid': function (rows) {
			var grid = [];
			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var cell = rows[ri].cells[ci];
				for (var spannedY = 0; spannedY < rowspan; spannedY++) {
					grid[ri + spannedY] = grid[ri + spannedY] || [];
					for (var spannedX = 0; spannedX < colspan; spannedX++) {
						grid[ri + spannedY][gridCi + spannedX] = {
							'cell'    : cell,
							'colspan' : colspan,
							'rowspan' : rowspan,
							'spannedX': spannedX,
							'spannedY': spannedY
						};
					}
				}
			});
			return grid;
		},
		/**
		 * A grid-cell is said to contain a dom-cell if it is located in the
		 * upper-left corner of a table-cell. A table-cell may have a
		 * rowspan and colspan, and as such may be comprised of several
		 * grid-cells, of which only one (the upper-left corner one)
		 * contains a dom-cell.
		 * @param cellInfo a cell in the grid returned by makeGrid()
		 * @return whether the given grid-cell maps to a dom-cell
		 */
		'containsDomCell': function (cellInfo) {
			return 0 === cellInfo.spannedX && 0 === cellInfo.spannedY;
		},
		/**
		 * A grid-cell may not contain a dom-cell (due to rowspan and
		 * colspan). If this function is given the coordinates of such a
		 * grid-cell, it will look to the left of the grid-cell, until it
		 * finds a grid-cell that contains a dom-cell and returns the
		 * DOM-Element of it.
		 *
		 * This function is useful to insert something into the DOM next to
		 * or in place of the grid-cell.
		 *
		 * @param grid the grid of the table (see makeGrid())
		 * @param ri the row index into the grid
		 * @param ci the column index into the grid
		 * @return the DOM-Element either at or to the left of the grid-cell
		 *   a the given coordinates.
		 */
		'leftDomCell': function (grid, ri, gridCi) {
			do {
				var cellInfo = grid[ri][gridCi];
				if ( 0 === cellInfo.spannedY ) {
					return cellInfo.cell;
				}
				gridCi -= cellInfo.spannedX + 1;
			} while (gridCi >= 0);
			return null;
		},
		/**
		 * Given a cell of a table (td/th) with a colspan or rowspan
		 * greater than one, will set the rowspan and colspan of the
		 * cell to one and insert empty cells where the original cell
		 * spanned (the number of cells allocated with createCell will
		 * be rowspan * colspan - 1).
		 *
		 * @param cell
		 *        the cell to split
		 * @param createCell
 		 *        a callback that will be invoked rowspan * colspan - 1
		 *        times, and which must return a table cell (td/th) that
		 *        will be inserted into the table
		 */
		'splitCell': function (cell, createCell) {
			var $cell = $(cell);
			var colspan = Utils.colspan( cell );
			var rowspan = Utils.rowspan( cell );

			//catch the most common case early
			if (1 === colspan && 1 === rowspan) {
				return;
			}

			var $row  = $cell.parent();
			var $rows = $row.parent().children();
			var rowIdx = $row.index();
			var colIdx = $cell.index();
			var grid = Utils.makeGrid($rows);
			var gridColumn = Utils.cellIndexToGridColumn($rows, rowIdx, colIdx);
			for (var i = 0; i < rowspan; i++) {
				for (var j = (0 === i ? 1 : 0); j < colspan; j++) {
					var leftCell = Utils.leftDomCell(grid, rowIdx + i, gridColumn);
					if (null == leftCell) {
						$rows.eq(rowIdx + i).prepend(createCell());
					} else {
						$( leftCell ).after(createCell());
					}
				}
			}
			// Note that attribute case (colSpan instead of colspan) matters on IE7.
			$cell.removeAttr('colSpan');
			$cell.removeAttr('rowSpan');
		},
		/**
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 * @return
		 *        a numeric value indicating the number of rows the cell spans
		 */
		'rowspan': function (cell) {
			return parseInt( $( cell ).attr('rowspan') ) || 1;
		},
		/**
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 * @return
		 *        a numeric value indicating the number of columns the cell spans
		 */
		'colspan': function (cell) {
			return parseInt( $( cell ).attr('colspan') ) || 1;
		},
		/**
		 * Calls the given callback with each object in the given
		 * two-dimensional array.
		 *
		 * @param grid
		 *        A two-dimensional array.
		 * @param callback
		 *        Invoked with each item in the given two-dimensional array.
		 *        Accepts the following parameters:
		 *        o item an item in the given two-dimensional array
		 *        o x the offset in the nested array (horizontal axis)
		 *        o y the offset in the outer array (veritcal axis)
		 *        If the callback returns a value identical to false,
		 *        the walk will be aborted early.
		 */
		'walkGrid': function(grid, callback) {
			var	row;
			for (var i = 0, gridLength = grid.length; i < gridLength; i++ ) {
				row = grid[i];
				for (var j = 0, rowLength = row.length; j < rowLength; j++ ) {
					if ( false === callback( row[ j ], j, i ) ) {
						return;
					}
				}
			}
		},
		/**
		 * Walks the cells of the given grid inside the given
		 * coordinates.
		 *
		 * @param {array} grid
		 *        A two-dimensional array.
		 * @param {object} rect
		 *        Must have the properties top, left, bottom, right
		 *        each of which referring to a coordinate in the given grid.
		 * @param {function} callback
		 *        A callback to invoke for each item in the given
		 *        two-dimensional array. See walkGrid() for the
		 *        specification of this parameter.
		 */
		'walkGridInsideRect': function ( grid, rect, callback ) {
			Utils.walkGrid( grid, function ( cellInfo, x, y ) {
				if ( y >= rect.top && y < rect.bottom && x >= rect.left && x < rect.right ) {
					return callback( cellInfo, x, y );
				}
			});
		},
		/**
		 * Slices leading null or undefined items off of an array
		 *
		 * @param array
		 *        the array to slice null or undefined items off from
		 * @return
		 *        a new array with the remaining items
		 */
		'leftTrimArray': function ( array ) {
			for (var i = 0; i < array.length; i++) {
				if ( null != array[i] ) {
					return array.slice( i, array.length );
				}
			}
			return [];
		},
		/**
		 * Given a two-dimensional array, will determine the smallest
		 * possible contour that contains all items for which
		 * hasCountour returns true.
		 *
		 * @param grid
		 *        A two-dimensional array
		 * @param hasContour
		 *        Invoked with each item in the given two dimensional array.
		 *        Accepts the following parameters:
		 *        o item an item in the given two-dimensional array
		 *        o x the offset in the nested array (horizontal axis)
		 *        o y the offset in the outer array (veritcal axis)
		 *        Returns a boolean value indicating whether the item is
		 *        considered to have a contour.
		 * @return
		 *        A set of arrays that indicate a contour
		 *        top:    an array of the smallest vertical offsets
		 *        right:  an array of the greatest horizontal offsets
		 *        bottom: an array of the greatest vertical offsets
		 *        left:   an array of the smallest horizontal offsets
		 */
		'makeContour': function ( grid, hasContour ) {
			var left = [];
			var right = [];
			var top = [];
			var bottom = [];
			Utils.walkGrid( grid, function ( item, x, y ) {
				if ( hasContour( item, x, y ) ) {
					if ( null == left[ y ] || x < left[ y ] ) {
						left[ y ] = x;
					}
					if ( null == right[ y ] || x > right[ y ] ) {
						right[ y ] = x;
					}
					if ( null == top[ x ] || y < top[ x ] ) {
						top[ x ] = y;
					}
					if ( null == bottom[ x ] || y > bottom[ x ] ) {
						bottom[ x ] = y;
					}
				}
			});
			left   = Utils.leftTrimArray(left);
			right  = Utils.leftTrimArray(right);
			top    = Utils.leftTrimArray(top);
			bottom = Utils.leftTrimArray(bottom);
			return {'left': left, 'right': right, 'top': top, 'bottom': bottom};
		},
		/**
		 * Returns the index of the first item that doesn't match the given value
		 *
		 * @param array
		 *        An array that contains arbitrary items
		 * @param but
		 *        A value to ignore while searching in the given array
		 * @return
		 *        The offset of the first item in the given array that doesn't match the given value.
		 *        If no such item was found, -1 is returned.
		 */
		'indexOfAnyBut': function ( array, but ) {
			for ( var i = 0; i < array.length; i++ ) {
				if ( but !== array[ i ] ) {
					return i;
				}
			}
			return -1;
		},
		/**
		 * @param array
		 *        an array of integers
		 * @return
		 *        true if each item in the given array has a
		 *        difference to its neighbor of exactly 1
		 */
		'isConsecutive': function ( array ) {
			for ( var i = 1; i < array.length; i++ ) {
				if ( 1 !== Math.abs( array[ i ] - array[ i - 1 ] ) ) {
					return false;
				}
			}
			return true;
		},

		/**
		 * resizes the width of the given cell
		 *
		 * @param {DOM element} cell
		 *        the DOM node for a table cell (td/th)
		 * @param {number | string} width
		 *        an integer value indicating the desired width
		 */

		'resizeCellWidth': function(cell, width) {
			$( cell ).css('width', width);
			$( cell ).find('.aloha-table-cell-editable').eq(0).css({
				'width': width,
				'word-wrap': 'break-word'
			});
		},

		/**
		 * Get the minimum column width based on the word size
		 *
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 *
		 * @return
		 * 				the minimum width as an integer value
		 */
		'getMinColWidth': function(cell) {
			var rows = cell.closest( 'tbody' ).children( 'tr' );
			var cellRow = cell.closest( 'tr' );
			var gridId = Utils.cellIndexToGridColumn( rows,
																							  rows.index( cellRow ),
																							  cellRow.children().index( cell )
																						  );

			var largestWord = "";

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				if (gridCi === gridId) {
					var curCell = $( $( rows[ri] ).children()[ ci ] );

					// skip the cells with a colspan
					if (colspan > 1) {
						return;
					}

					var cellWords = curCell.text().split(" ");

					// pick the largest word in the cell
					for ( var j = 0; j < cellWords.length; j++ ) {
						if ( cellWords[j].length > largestWord.length ) {
							largestWord = cellWords[j];
						}
					}
				}
			});

			var fakeCell = $("<span></span>");
			fakeCell.css( 'text-indent', -9999 );
			fakeCell.css( 'display', 'inline' );
			fakeCell.text( largestWord );

			$( cell ).append( fakeCell );

			var width = fakeCell.width();

			$( fakeCell ).remove();

			return width;
		},

		/**
		 * Get the maximum and minimum width a cell can be resized.
		 *
		 * @param gridId
		 * 			id of the cell based on the virtual grid
		 * @param rows
		 * 			Collection of rows as DOM objects
		 * @param callback
		 * 			a callback function
		 *
		 * @return void
		 */
		'getCellResizeBoundaries': function(gridId, rows, callback) {
			var maxPageX, minPageX;

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = $( $( rows[ri] ).children()[ ci ] );

				// skip the select cells
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) ) {
					return true;
				};

				if (gridCi === gridId && colspan === 1) {
					maxPageX = currentCell.offset().left + Utils.getCellBorder(currentCell) + currentCell.width() - Utils.getMinColWidth( currentCell );
				}

				if (gridCi === gridId - 1 && colspan === 1) {
					minPageX = currentCell.offset().left + Utils.getCellBorder(currentCell) + Utils.getCellPadding(currentCell) + Utils.getMinColWidth( currentCell );
				}

				// if both max page x and min page x is set,
				// stop walking over the cells.
				if ( maxPageX && minPageX ) {
					callback(maxPageX, minPageX);
					return false;
				}
			});
		},

		/**
		 * Get the border width of the cell
		 *
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 *
		 * @return
		 * 				the border width as an integer value
		 */
		'getCellBorder': function(cell) {
			return ( (cell.outerWidth() - cell.innerWidth()) / 2 );
		},

		/**
		 * Get the padding of the cell
		 *
		 * @param cell
		 *        the DOM node for a table cell (td/th)
		 *
		 * @return
		 * 				the padding as an integer value
		 */
		'getCellPadding': function(cell) {
			return ( cell.innerWidth() - cell.width() );
		},

		selectAnchorContents: function(selection) {
			var anchor = getAnchorCell(selection);
			if (anchor) {
				var element = $('>.aloha-table-cell-editable', anchor)[0];
				if (Browser.ie7) {
					try {
						CopyPaste.selectAllOf(element)
					} catch (e) {
						Console.error ('Table', e.message);
					}
				} else {
					CopyPaste.selectAllOf(element);
				}
			}
		}

	};

	return Utils;
});

define('table/table-cell',[
	'aloha',
	'aloha/jquery',
	'aloha/ephemera',
	'table/table-plugin-utils'
], function (
	Aloha,
	jQuery,
	Ephemera,
	Utils
) {
	/**
	 * Constructs a TableCell.
	 *
	 * @param {DomNode} cell
	 *        A td/th which will be represente by this TableCell.
	 * @param {Table} tableObj
	 *        The Table which contains the cell. The cell will be
	 *        activated/dactivated with the table.
	 */
	var TableCell = function (originalTd, tableObj) {
		if (null == originalTd) {
			originalTd = '<td>&nbsp;</td>';
		}

		//original Td must be a DOM node so that the this.obj.context property is available
		//this transformation will properly handle jQuery objects as well as DOM nodes
		originalTd = jQuery(originalTd).get(0);

		this.obj = jQuery(originalTd);
		this.tableObj = tableObj;

		tableObj.cells.push(this);
	};

	/**
	 * Reference to the jQuery-representation of the wrapping table
	 *
	 * @see TableCell.table
	 */
	TableCell.prototype.tableObj = undefined;

	/**
	 * Reference to the jQuery td-Object of the cell
	 */
	TableCell.prototype.obj = undefined;

	/**
	 * The jQuery wrapper of the cell
	 */
	TableCell.prototype.wrapper = undefined;

	/**
	 * Flag if the cell has focus
	 */
	TableCell.prototype.hasFocus = false;

	TableCell.prototype.activate = function () {
		var cell = this;
		var $elem = cell.obj;

		// wrap the created div into the contents of the cell
		$elem.wrapInner('<div/>');

		// create the editable wrapper for the cells
		var $wrapper = $elem.children('div').eq(0);
		$wrapper.contentEditable(true);
		$wrapper.addClass('aloha-table-cell-editable');

		// mark the editable wrapper as ephemeral
		Ephemera.markWrapper($wrapper);

		// attach events to the editable div-object
		$wrapper.bind('focus', function ($event) {
			// activate the button for splitting cells if the clicked cell has an active row- or colspan
			if (Utils.colspan(cell.obj) > 1 || Utils.rowspan(cell.obj) > 1) {
				cell.tableObj.tablePlugin._splitcellsButton.enable(true);
			} else {
				cell.tableObj.tablePlugin._splitcellsButton.enable(false);
			}

			// ugly workaround for ext-js-adapter problem in
			// ext-jquery-adapter-debug.js:1020
			if ($event.currentTarget) {
				$event.currentTarget.indexOf = function () {
					return -1;
				};
			}
			cell._editableFocus($event);
		});

		$wrapper.bind('mousedown', function ($event) {
			// ugly workaround for ext-js-adapter problem in ext-jquery-adapter-debug.js:1020
			if ($event.currentTarget) {
				$event.currentTarget.indexOf = function () {
					return -1;
				};
			}

			cell._editableMouseDown($event);

			cell.tableObj.selection.baseCellPosition = [cell._virtualY(), cell._virtualX()];

			if ($event.shiftKey) {
				// shift-click to select a coherent cell range
				//
				// in IE it's not possible to select multiple cells when you "select+drag" over other cells
				// click into the first cell and then "shift-click" into the last cell of the coherent cell range you want to select
				var right = cell.tableObj.selection.lastBaseCellPosition[1];
				var bottom = cell.tableObj.selection.lastBaseCellPosition[0];
				var topLeft = cell.tableObj.selection.baseCellPosition;
				var left = topLeft[1];
				if (left > right) {
					left = right;
					right = topLeft[1];
				}
				var top = topLeft[0];
				if (top > bottom) {
					top = bottom;
					bottom = topLeft[0];
				}
				var rect = {
					"top": top,
					"right": right,
					"bottom": bottom,
					"left": left
				};

				var table = cell.tableObj;
				var $rows = table.obj.children().children('tr');
				var grid = Utils.makeGrid($rows);

				table.selection.selectedCells = [];
				var selectClass = table.get('classCellSelected');
				Utils.walkGrid(grid, function (cellInfo, j, i) {
					if (Utils.containsDomCell(cellInfo)) {
						if (i >= rect.top && i <= rect.bottom && j >= rect.left && j <= rect.right) {
							jQuery(cellInfo.cell).addClass(selectClass);
							table.selection.selectedCells.push(cellInfo.cell);
						} else {
							jQuery(cellInfo.cell).removeClass(selectClass);
						}
					}
				});

				table.selection.notifyCellsSelected();
			} else {
				cell.tableObj.selection.lastBaseCellPosition = cell.tableObj.selection.baseCellPosition;
				cell._editableMouseDown($event);
				cell._startCellSelection();
			}
		});

		$wrapper.bind('blur', function ($event) {
			cell._editableBlur($event);
		});
		$wrapper.bind('keyup', function ($event) {
			cell._editableKeyUp($event);
		});
		$wrapper.bind('keydown', function ($event) {
			cell._editableKeyDown($event);
		});
		$wrapper.bind('mouseover', function ($event) {
			cell._selectCellRange();
		});

		// we will treat the wrapper just like an editable
		$wrapper.contentEditableSelectionChange(function ($event) {
			Aloha.Selection.onChange($wrapper, $event);
			return $wrapper;
		});

		$elem.bind('mousedown', function ($event) {
			window.setTimeout(function () {
				// Select the entire cell's content.
				cell.wrapper.trigger('focus');
				cell._selectAll($wrapper);
			}, 1);
			if (!$event.shiftKey) {
				cell.tableObj.selection.unselectCells();
				cell._startCellSelection();
			}
			$event.stopPropagation();
		});

		if ($elem.get(0)) {
			$elem.get(0).onselectstart = function () {
				return false;
			};
		}

		// set contenteditable wrapper div
		this.wrapper = $elem.children();
		if (this.wrapper.get(0)) {
			this.wrapper.get(0).onselectstart = function () {
				window.event.cancelBubble = true;
			};
			// Disabled the dragging of content, since it makes cell selection
			// difficult.
			this.wrapper.get(0).ondragstart = function () {
				return false
			};
		}

		return this;
	};

	/**
	 * The deactivate method removes the contenteditable helper div within the
	 * table-data field and wraps the innerHtml to the outerHTML
	 *
	 * @return void
	 */
	TableCell.prototype.deactivate = function () {
		var wrapper = jQuery(this.obj.children('.aloha-table-cell-editable'));

		if (wrapper.length) {
			// unwrap cell contents without re-creating dom nodes
			wrapper.parent().append(
				wrapper.contents()
			);

			// remove the contenteditable div and its attached events
			wrapper.remove();


			// remove the click event of the
			this.obj.unbind('click');
			this.obj.unbind('mousedown');

			if (jQuery.trim(this.obj.attr('class')) == '') {
				this.obj.removeAttr('class');
			}
		}
	}

	/**
	 * Native toString-method
	 *
	 * @return string name of the namespace
	 */
	TableCell.prototype.toString = function () {
		return 'TableCell';
	};

	/**
	 * Focus method for the contentediable div within a table data-field. The method
	 * requires the event-property Cell as a Cell object. If the
	 * Cell wasn't activated yet it does all relevant actions to activate the cell.
	 *
	 * @param e
	 *            the jquery event object
	 * @return void
	 */
	TableCell.prototype._editableFocus = function (e) {
		// only do activation stuff if the cell don't has the focus
		if (!this.hasFocus) {
			// set an internal flag to focus the table
			this.tableObj.focus();

			// add an active-class
			this.obj.addClass('aloha-table-cell_active');

			// set the focus flag
			this.hasFocus = true;

			// unset the selection type
			this.tableObj.selection.selectionType = 'cell';

		}
	};

	/**
	 * Blur event for the contenteditable div within a table-data field. The method
	 * requires the event-property TableCell as a TableCell object. It
	 * sets the hasFocus flag of the cell to false and removes the "active"
	 * css-class.
	 *
	 * @param jqEvent
	 *            the jquery event object
	 * @return void
	 */
	TableCell.prototype._editableBlur = function (jqEvent) {

		// reset the focus of the cell
		this.hasFocus = false;

		// remove "active class"
		this.obj.removeClass('aloha-table-cell_active');
	};

	/**
	 * Gives the X (column no) for a cell, after adding colspans
	 */
	TableCell.prototype._virtualX = function () {
		var $rows = this.tableObj.obj.children().children('tr');
		var rowIdx = this.obj.parent().index();
		var colIdx = this.obj.index();
		return Utils.cellIndexToGridColumn($rows, rowIdx, colIdx);
	};

	/**
	 * Gives the Y (row no) for a cell, after adding colspans
	 */
	TableCell.prototype._virtualY = function () {
		return this.obj.parent('tr').index();
	};

	/**
	 * Starts the cell selection mode
	 */
	TableCell.prototype._startCellSelection = function () {
		if(!this.tableObj.selection.cellSelectionMode) {

			//unselect currently selected cells
			this.tableObj.selection.unselectCells();

			// activate cell selection mode
			this.tableObj.selection.cellSelectionMode = true;

			//bind a global mouseup event handler to stop cell selection
			var that = this;
			jQuery('body').bind('mouseup.cellselection', function(event) {
				that._endCellSelection();
				event.stopPropagation();
			});

			this.tableObj.selection.baseCellPosition = [this._virtualY(), this._virtualX()];
		}
	};

	/**
	 * Ends the cell selection mode
	 */
	TableCell.prototype._endCellSelection = function() {
		if (this.tableObj.selection.cellSelectionMode) {
			Utils.selectAnchorContents(this.tableObj.selection.selectedCells);

			this.tableObj.selection.cellSelectionMode = false;
			this.tableObj.selection.baseCellPosition = null;
			this.tableObj.selection.lastSelectionRange = null;

			this.tableObj.selection.selectionType = 'cell';

			//unbind the global cell selection event
			jQuery('body').unbind('mouseup.cellselection');
		}
	};

	TableCell.prototype._getSelectedRect = function () {
		var right = this._virtualX();
		var bottom = this._virtualY();
		var topLeft = this.tableObj.selection.baseCellPosition;
		var left = topLeft[1];
		if (left > right) {
			left = right;
			right = topLeft[1];
		}
		var top = topLeft[0];
		if (top > bottom) {
			top = bottom;
			bottom = topLeft[0];
		}
		return {
			"top": top,
			"right": right,
			"bottom": bottom,
			"left": left
		};
	};

	/**
	 * Toggles selection of cell.
	 * This works only when cell selection mode is active.
	 */
	TableCell.prototype._selectCellRange = function() {
		if(this.tableObj.selection.resizeMode || !this.tableObj.selection.cellSelectionMode) {
			return;
		}

		var rect = this._getSelectedRect();

		var table = this.tableObj;
		var $rows = table.obj.children().children('tr');
		var grid = Utils.makeGrid($rows);

		table.selection.selectedCells = [];
		var selectClass = table.get('classCellSelected');
		Utils.walkGrid(grid, function (cellInfo, j, i) {
			if (Utils.containsDomCell(cellInfo)) {
				if (i >= rect.top && i <= rect.bottom && j >= rect.left && j <= rect.right) {
					jQuery(cellInfo.cell).addClass(selectClass);
					table.selection.selectedCells.push(cellInfo.cell);
				} else {
					jQuery(cellInfo.cell).removeClass(selectClass);
				}
			}
		});

		table.selection.notifyCellsSelected();
	};

	/**
	 * Selects all inner-contens of an contentEditable-object
	 *
	 * @param editableNode dom-representation of the editable node (div-element)
	 * @return void
	 */
	TableCell.prototype._selectAll = function (editableNode) {
		var e = (editableNode.jquery) ? editableNode.get(0) : editableNode;

		// Not IE
		if (!jQuery.browser.msie) {
			var s = window.getSelection();
			// WebKit
			if (s.setBaseAndExtent /*&& e> 0 */ ) {
				s.setBaseAndExtent(e, 0, e, Math.max(0, e.innerText.length - 1));
			}
			// Firefox and Opera
			else {
				// workaround for bug # 42885
				if (window.opera && e.innerHTML.substring(e.innerHTML.length - 4) == '<BR>') {
					e.innerHTML = e.innerHTML + '&#160;';
				}

				var r = document.createRange();
				r.selectNodeContents(e);
				s.removeAllRanges();
				s.addRange(r);
			}
		}
		// Some older browsers
		else if (document.getSelection) {
			var s = document.getSelection();
			var r = document.createRange();
			r.selectNodeContents(e);
			s.removeAllRanges();
			s.addRange(r);
		}
		// IE
		else if (document.selection) {
			var r = document.body.createTextRange();
			r.moveToElementText(e);
			r.select();
		}
	};

	/**
	 * The mouse-down event for the editable-div in the thd-field. Unselect all
	 * cells when clicking on the editable-div.
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	TableCell.prototype._editableMouseDown = function (jqEvent) {
		// deselect all highlighted cells registered in the this.tableObj.selection object
		this.tableObj.selection.unselectCells();

		if (this.tableObj.hasFocus) {
			if (typeof jqEvent.stopPropagation === 'function') {
				jqEvent.stopPropagation();
			} else if (typeof jqEvent.cancelBubble !== 'undefined') {
				jqEvent.cancelBubble = true;
			}
		}
	};

	/**
	 * The key-up event for the editable-div in the td-field. Just check if the div
	 * is empty and insert an &nbsp;
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	TableCell.prototype._editableKeyUp = function (jqEvent) {
		//TODO do we need to check for empty cells and insert a space?
		//this._checkForEmptyEvent(jqEvent);
	};

	/**
	 * The key-down event for the ediable-div in the td-field. Check if the the div
	 * is empty and insert an &nbsp. Furthermore if cells are selected, unselect
	 * them.
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	TableCell.prototype._editableKeyDown = function (jqEvent) {
		var KEYCODE_TAB = 9;
		this._checkForEmptyEvent(jqEvent);
		if (this.obj[0] === this.tableObj.obj.find('tr:last td:last')[0]) {
			// only add a row on a single key-press of tab (so check that alt-,
			// shift- or ctrl-key are NOT pressed)
			if (KEYCODE_TAB == jqEvent.keyCode && !jqEvent.altKey && !jqEvent.shiftKey && !jqEvent.ctrlKey) {
				this.tableObj.addRow(this.obj.parent().index() + 1);
			}
		}
	};

	/**
	 * The custom keyup event for a table-cell Checks if the cell is empty and
	 * inserts a space (\u00a0)
	 *
	 * @param e
	 *            the event object which is given by jquery
	 * @return void
	 */
	TableCell.prototype._checkForEmptyEvent = function (jqEvent) {
		var $wrapper = jQuery(this.wrapper),
			text = $wrapper.text();

		if ($wrapper.children().length > 0) {
			return;
		}

		// if empty insert a blank space and blur and focus the wrapper
		if (text === '') {
			this.wrapper.text('\u00a0');
			this.wrapper.get(0).blur();
			this.wrapper.get(0).focus();
		}
	};

	/**
	 * Given a cell, will return the container element of the contents
	 * of the cell. The container element may be the given cell itself,
	 * or a wrapper element, in the case of activated cells.
	 *
	 * @param {DomNode} cell
	 *        the TH/TD of a TableCell that may or may not be actived.
	 * @return {DomNode}
	 *        the element that contains the contents of the given cell.
	 */
	TableCell.getContainer = function (cell) {
		if (jQuery(cell.firstChild).hasClass("aloha-table-cell-editable")) {
			return cell.firstChild;
		} else {
			return cell;
		}
	};

	return TableCell;
});

define('table/table-selection',[
	'aloha',
	'aloha/jquery',
	'table/table-plugin-utils',
	'table/table-cell',
	'ui/dialog',
	'i18n!table/nls/i18n'
], function (
	Aloha,
	$,
	Utils,
	TableCell,
	Dialog,
	i18n
) {
	/**
	 * The TableSelection object is a helper-object
	 */
	var TableSelection = function (table) {
		this.table = table;
	};

	/**
	 * Returns if a content parameter is a content that be added in a 
	 * merge cell from the cells selecteds.
	 * 
	 * @param {String|TextNodeElement|HTMLElement} content - A content 
	 * from the cell, might be a TextNode, HTMLElement or a String
	 * 
	 * @return {Boolean}
	 */
	function isMergeableContent(content) {
		return ((
				'string' === typeof(content) && '' !== $.trim(content)
			) || (
				content.nodeType
				&& (
					3 === content.nodeType
					&&
					'' !== $.trim(content.data)
				) || (
					1 === content.nodeType
				)
			));
	}

	/**
	 * Gives the type of the cell-selection
	 * possible values are "cell", "row", "column" or "all".
	 * If the value is 'cell' means custom cell selections
	 */
	TableSelection.prototype.selectionType = undefined;

	/**
	 * Holds all currently selected table cells as an array of DOM "td" representations
	 */
	TableSelection.prototype.selectedCells = new Array();

	/**
	 * Holds all table columnIdx if selectiontype is column
	 */
	TableSelection.prototype.selectedColumnIdxs = new Array();

	/**
	 * Holds all table rowIds if selectiontype is column
	 */
	TableSelection.prototype.selectedRowIdxs = new Array();

	/**
	 * Holds the active/disabled state of cell selection mode 
	 */
	TableSelection.prototype.cellSelectionMode = false;

	/**
	 * Gives the position of the base cell of a selection - [row, column]
	 */
	TableSelection.prototype.baseCellPosition = null;

	/**
	 * Gives the range of last cell selection - [row, column]
	 */
	TableSelection.prototype.lastSelectionRange = null;

	/**
	 * Marks all cells of the specified column or columns as selected
	 *
	 * @return void
	 */
	TableSelection.prototype.selectColumns = function ( columnsToSelect ) {
		this.unselectCells();

		var rows = this.table.getRows();
		// first row is the selection row (dump it, it's not needed)
		rows.shift();
		
		var grid = Utils.makeGrid(rows);
		for (var j = 0; j < columnsToSelect.length; j++) {
			// check if this column is already selected.
			if ( -1 !== $.inArray(columnsToSelect[j], this.selectedColumnIdxs) ) {
				continue;
			}
			this.selectedColumnIdxs.push( columnsToSelect[j] );
			for (var i = 0; i < grid.length; i++) {
				var cellInfo = grid[i][columnsToSelect[j]];
				if ( Utils.containsDomCell(cellInfo) ) {
					$(cellInfo.cell).addClass(this.table.get('classCellSelected'));
					this.selectedCells.push( cellInfo.cell );
				}
			}
		}

		this.selectionType = 'column';
	};
	
	/**
	 * Marks all cells of the specified row or rows as selected
	 *
	 * @return void
	 */
	TableSelection.prototype.selectRows = function( rowsToSelect ) {
		this.unselectCells();

		var rows = this.table.getRows();
		
 	    rowsToSelect.sort( function ( a, b ) { return a - b; } );

		for (var i = 0; i < rowsToSelect.length; i++) {
			if ( rows[ rowsToSelect[i] ] ) {
				// check if this row is already selected.
	        	for ( var z = 0; z < this.selectedRowIdxs.length; z++ ) {
	        		if ( rowsToSelect[i] == this.selectedRowIdxs[z] ) {
	        			return;
	        		}
	        	}
				this.selectedRowIdxs.push( rowsToSelect[i] );
				// to not select first cell, which is a control cell
			    for ( var j = 1; j < rows[ rowsToSelect[i] ].cells.length; j++ ) {  
					this.selectedCells.push( rows[ rowsToSelect[i] ].cells[j] );
					// TODO make proper cell selection method
					$( rows[ rowsToSelect[i] ].cells[j] ).addClass( this.table.get('classCellSelected') );
			    }
			}
		}

	    this.selectionType = 'row';
	};

	TableSelection.prototype.selectAll = function () {
		var rowIndices = $.map( this.table.getRows(), function ( item, i ) {
			return i;
		});

		//getRows() returns all rows, even the header row which we must not select
		rowIndices.shift();

		this.selectRows( rowIndices );

		this.selectionType = 'all';
	};
	
	/**
	 * To be called when cells of the table were selected
	 * @see selectRows, selectColumns, selectCellRange
	 * TODO this should be private
	 */
	TableSelection.prototype.notifyCellsSelected = function () {
		Aloha.trigger( 'aloha-table-selection-changed' );
		
		// the UI feels more consisten when we remove the non-table
		// selection when cells are selected
		// TODO this code doesn't work right in IE as it causes the table
		//  scope of the floating menu to be lost. Maybe this can be
		//  handled by testing for an empty selection in the
		//  aloha-selection-changed event.
		//Aloha.getSelection().removeAllRanges();
	};

	/**
	 * To be called when a cell-selection is entirely removed
	 * @see unselectCells
	 */
	TableSelection.prototype._notifyCellsUnselected = function () {
		Aloha.trigger( 'aloha-table-selection-changed' );
	};

	/**
	 * This method return true if all sellected cells are TH cells.
	 *
	 * @return boolean
	 */
	TableSelection.prototype.isHeader = function ( ) {
		
        if ( this.selectedCells.length == 0 ) {
        	return false;
        }
        
        // take 1 column to detect if the header button is pressd
		for (var i = 0; i < this.selectedCells.length; i++) {
			if ( !this.selectedCells[i] || this.selectedCells[i].nodeName.toLowerCase() != 'th' ) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * This method removes the "selected" class from all selected cells
	 *
	 * @return void
	 */
	TableSelection.prototype.unselectCells = function(){
		//don't unselect cells if cellSelectionMode is active
		if ( this.cellSelectionMode ) {
    		return;
		}

		if (this.selectedCells.length > 0) {
			var
				cells = this.selectedCells,
				classCellSelected = this.table.get('classCellSelected');

			for (var i = 0, len = cells.length; i < len; i++) {
				$(cells[i]).removeClass(classCellSelected);
			}

			this.selectedCells = new Array();
			this.selectedColumnIdxs = new Array();
			this.selectedRowIdxs = new Array();

			//we keep 'cell' as the default selection type instead of
			//unsetting the selectionType to avoid an edge-case where a
			//click into a cell doesn't trigger a call to
			//TableCell.editableFocs (which would set the 'cell'
			//selection type) which would result in the FloatingMenu
			//losing the table scope.
			this.selectionType = 'cell';

			this._notifyCellsUnselected();
		}
	};

	/**
	 * Returns the index of a given cell, in selectedCells
	 * returns -1 if the given cell is not in selectedCells 
	 * @params cell
	 *          DOMElement
	 *
	 * @return integer 
	 */
	TableSelection.prototype.selectionIndex = function(cell){
		for(var i = 0; i < this.selectedCells.length; i++){
			if(this.selectedCells[i] === cell){
				return i; 
			} 
		}
		return -1;
	};


	/**
	 * Given a contour creates a object representing a rectangle.
	 * This function only gives a useful return value if the given
	 * contour rectangular.
	 *
	 * @param {object} contour
	 *        a rectangular contour
	 * @return {object}
	 *        an object with the properties top, right, bottom, left, 
	 *        representing the rectangular contour.
	 */
	function getRectFromContour( contour ) {
		return {
			'top'   : contour.top[0],
			'right' : contour.right[0] + 1,
			'bottom': contour.bottom[0] + 1,
			'left'  : contour.left[0]
		};
	}

	/**
	 * Given a grid and contour, determines whether the contour is
	 * rectangular, and each cell in the rectangle is selected.
	 *
	 * @param {array} grid
	 *        a two-dimensional array representing a grid see Utils.makeGrid
	 * @param {object} contour
	 *        an object reprensenting a contour see Utils.makeContour
	 * @param {function} isSelected
	 *        a function that determines whether a cell in the given grid
	 *        is selected for merging.
	 * @return {boolean}
	 *        true if all cells inside the contour are selected and can
	 *        be merged.
	 */
	function isMergeable(grid, contour, isSelected) {
		var mergeable = true;
		if (   -1 !== Utils.indexOfAnyBut( contour.top   , contour.top[0]    )
			|| -1 !== Utils.indexOfAnyBut( contour.right , contour.right[0]  )
			|| -1 !== Utils.indexOfAnyBut( contour.bottom, contour.bottom[0] )
			|| -1 !== Utils.indexOfAnyBut( contour.left  , contour.left[0]   ) ) {
			// the outside of the selected area is jagged (not a rectangle)
			mergeable = false;
		} else {
			// the outside of the selected area is a rectangle, but we
			// must also ensore that there are no holes in the selection
			var rect = getRectFromContour( contour )
			Utils.walkGridInsideRect( grid, rect, function ( cellInfo ) {
				if ( ! isSelected( cellInfo ) ) {
					mergeable = false;
					return false;
				}
			});
		}
		return mergeable;
	}

	/**
	 * This method merges all selected cells
	 *
	 * @return void
	 */
	TableSelection.prototype.mergeCells = function(){

		var selectedCells = this.selectedCells;
		if ( 0 === selectedCells.length ) {
			return;
		}

		var isSelected = function ( cellInfo ) {
			return -1 != $.inArray( cellInfo.cell, selectedCells );
		};

		var grid = Utils.makeGrid( this.table.getRows() );
		var contour = Utils.makeContour( grid, isSelected );

		if ( ! isMergeable( grid, contour, isSelected ) ) {
			Dialog.alert({
				title : i18n.t('Table'),
				text : i18n.t('table.mergeCells.notRectangular')
			});
			return;
		}

		var selectedRect = getRectFromContour( contour );
		var $firstCell = $( grid[ selectedRect.top ][ selectedRect.left ].cell );
		var $firstContainer = $( TableCell.getContainer( $firstCell.get( 0 ) ) );

		Utils.walkGridInsideRect( grid, selectedRect, function ( cellInfo, x, y ) {
			if (   x - cellInfo.spannedX === selectedRect.left
				&& y - cellInfo.spannedY === selectedRect.top ) {
				return;
			}
			var cell = cellInfo.cell;
			var contents = $( TableCell.getContainer( cell ) ).contents();
			// only append the delimiting space if there is some non-whitespace
			for ( var i = 0; i < contents.length; i++ ) {
				if(isMergeableContent(contents[i])){
					$firstContainer.append( " " );
					$firstContainer.append( contents );
					break;
				}
//
//				if (   "string" !== typeof contents[i]
//					|| "" !== $.trim( contents[i].data ) ) {
//					$firstContainer.append( " " );
//					$firstContainer.append( contents );
//					break;
//				}
			}
			$( cell ).remove();
		});

		$firstCell.attr({ 'rowspan': selectedRect.bottom - selectedRect.top,
						  'colspan': selectedRect.right  - selectedRect.left });

		//select the merged cell
		this.selectedCells = [ $firstCell.get( 0 ) ];

		//reset flags
		this.cellSelectionMode = false; 
		this.baseCellPosition = null;
		this.lastSelectionRange = null; 
		this.selectionType = 'cell';

		Aloha.trigger( 'aloha-table-selection-changed' );
	};

	/**
	 * This method splits all selected cells (if they are already have row or column spans)
	 *
	 * @return void
	 */
	TableSelection.prototype.splitCells = function(){
		var selection = this;

		// split the selected cells or currently active cell
		var cells_to_split = this.selectedCells;
		if (cells_to_split.length > 0) {

			$(cells_to_split).each(function(){
				Utils.splitCell(this, function () {
					return selection.table.newActiveCell().obj;
				});
			});

			//reset flags
			this.cellSelectionMode = false; 
			this.baseCellPosition = null;
			this.lastSelectionRange = null; 
			this.selectionType = 'cell';

			Aloha.trigger( 'aloha-table-selection-changed' );
		}
	};

	/**
	 * This method checks if the current selection of cells is merge able
	 *
	 * @return {boolean}
	 *        true if more than one cell is selected.
	 */
	TableSelection.prototype.cellsAreMergeable = function() {
		var selectedCells = this.selectedCells;

		if ( selectedCells.length < 2 ) {
			return false;
		}

		var isSelected = function ( cellInfo ) {
			return -1 != $.inArray( cellInfo.cell, selectedCells );
		};

		var grid = Utils.makeGrid( this.table.getRows() );
		var contour = Utils.makeContour( grid, isSelected );

		if ( ! isMergeable( grid, contour, isSelected ) ) {
			return false;
		} else {
			return true;
		}
	};

	/**
	 * This method checks if the current selection of cells is split able
	 *
	 * @return {boolean}
	 *        true if more than one cell is selected.
	 */
	TableSelection.prototype.cellsAreSplitable = function() {
		var splitable = 0;
		if ( this.selectedCells.length > 0 ) {
			$(this.selectedCells).each(function(){
				var cell = this;
				var colspan = Utils.colspan( cell );
				var rowspan = Utils.rowspan( cell );

				if ( colspan > 1 || rowspan > 1 ) {
					splitable++;
				}
			});
			
			if ( splitable > 0 ) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	};

	return TableSelection;
});

/**
 * Aloha Table Plugin
 * ------------------
 * This plugin provides advanced support for manipulating tables in Aloha
 * Editables.
 * Nested tables are not support. If nested tables are pasted into the
 * editable, they will simply be left alone.
 * Each (non-nested) table in the editable will have a corresponding Aloha
 * Table instance created for it, which will maintain internal state, and
 * information related to its DOM element.
 *
 * @todo: - selectRow/selectColumn should take into account the helper row/column.
 *			ie: selectRow(0) and selectColumn(0), should be zero indexed
 */
define('table/table',[
	'aloha',
	'jquery',
	'ui/scopes',
	'ui/dialog',
	'i18n!table/nls/i18n',
	'table/table-cell',
	'table/table-selection',
	'table/table-plugin-utils',
	'aloha/ephemera',
	'util/html',
	'util/dom2',
	'aloha/console'
], function (
	Aloha,
	jQuery,
	Scopes,
	Dialog,
	i18n,
	TableCell,
	TableSelection,
	Utils,
	Ephemera,
	Html,
	Dom,
    Console
) {
	var undefined = void 0;
	var GENTICS = window.GENTICS;

	/**
	 * Returns an Array with all elements and textnodes included in the 
	 * hierarchy of the element received. Is Similar to do 
	 * jQuery('*', element).contents(), the diference it's this function returns the 
	 * array in the correct order of apparition
	 * @example
	 * <pre>
	 *		&gt;p&lt;
	 *			textnode
	 *			<b>b textnode</b>
	 *			another text node
	 *		&gt;/p&lt;
	 *		
	 *		jQuery('*', lt).contents();
	 *			// returns ["textnode", "<b>", "another textnode", "b textnode"]
	 *		getPlainHierarchy(lt)
	 *			// returns ["textnode", "<b>", "b textnode", "another textnode"]
	 * </pre>
	 * 
	 * @return {Array.<HTMLElement|TextNode>}
	 */
	function getPlainHierarchy(element) {
		if (element.jquery) {
			element = element[0];
		}
		var i, result = [], child;
		for (i = 0; i < element.childNodes.length; i++) {
			child = element.childNodes[i];
			result.push(child);
			if (child.nodeType === 1) {
				result = result.concat(getPlainHierarchy(child));
			}
		}

		return result;
	}

	/**
	 * Find the first or the last element inside a table, even if in a td
	 * 
	 * @param {String} type Accepts two values: 'first' or 'last'
	 * @param {HTMLElement|jQuery} parent the parent element to search
	 * 
	 * @return {jQuery}
	 */
	function getNewSelectedElement(type, parent) {
		var toSelectElement;
		if ('first' === type) {
			toSelectElement = jQuery('[contenteditable]', parent).first()[0]
				.firstChild;
			if (undefined === toSelectElement) {
				toSelectElement = jQuery('*', parent).first()[0].firstChild;
			}
		} else if ('last' === type) {
			toSelectElement = getPlainHierarchy(jQuery('td:last', parent))
				.reverse()[0];
		}

		return toSelectElement;
	}

	/**
	 * Constructor of the table object
	 *
	 * @param table
	 *            the dom-representation of the held table
	 * @return void
	 */
	var Table = function ( table, tablePlugin ) {
		// set the table attribut "obj" as a jquery represenation of the dom-table
		this.obj = jQuery( table );

		correctTableStructure( this );

		if ( !this.obj.attr( 'id' ) ) {
			this.obj.attr( 'id', GENTICS.Utils.guid() );
		}

		// mark table id as ephemeral
		Ephemera.markAttr(this.obj, 'id');

		this.tablePlugin = tablePlugin;
		this.selection = new TableSelection( this );
		this.refresh();
	};

	jQuery.extend( Table.prototype, {
		/**
		 * Attribute holding the jQuery-table-represenation
		 */
		obj: undefined,

		/**
		 * The DOM-element of the outest div-container wrapped around the cell
		 */
		tableWrapper: undefined,

		/**
		 * An array of all Cells contained in the Table
		 *
		 * @see TableCell
		 */
		cells: undefined,

		/**
		 * Number of rows of the table
		 */
		numRows: undefined,

		/**
		 * Number of rows of the table
		 */
		numCols: undefined,

		/**
		 * Flag wether the table is active or not
		 */
		isActive: false,

		/**
		 * Flag wether the table is focused or not
		 */
		hasFocus: false,

		/**
		 * The editable which contains the table
		 */
		parentEditable: undefined,

		/**
		 * Flag to check if the mouse was pressed. For row- and column-selection.
		 */
		mousedown: false,

		/**
		 * ID of the column which was pressed when selecting columns
		 */
		clickedColumnId: -1,

		/**
		 * ID of the row which was pressed when selecting rows
		 */
		clickedRowId: -1,

		/**
		 * collection of columnindexes of the columns which should be selected
		 */
		columnsToSelect: [],

		/**
		 * collection of rowindexes of the rows which should be selected
		 */
		rowsToSelect: [],

		/**
		 * contains the plugin id used for interaction with the floating menu
		 */
		fmPluginId: undefined
	} );

	/**
	 * @hide
	 */
	Table.prototype.refresh = function () {
		// find the dimensions of the table
		this.numCols = this.countVirtualCols();

		var rows = this.getRows();
		this.numRows = rows.length;

		// init the cell-attribute with an empty array
		this.cells = [];

		// iterate over table cells and create Cell-objects
		for ( var i = 0; i < rows.length; i++ ) {
			var row = jQuery(rows[i]);
			var cols = row.children();
			for ( var j = 0; j < cols.length; j++ ) {
				var col = cols[j];
				var Cell = this.newCell( col );
			}
		}
	};

	Table.prototype.countVirtualCols = function () {
		var $firstRow = this.obj.children().children( 'tr:first-child' ).children();
		return $firstRow.length - $firstRow.filter( '.' + this.get( 'classLeftUpperCorner' ) ).length;
	};

	/**
	 * Wrapper-Mehotd to return a property of TablePlugin.get
	 *
	 * @see TablePlugin.get
	 * @param property
	 *            the property whichs value should be return
	 * @return the value associated with the property
	 */
	Table.prototype.get = function(property) {
		return this.tablePlugin.get(property);
	};

	/**
	 * Wrapper-Method for TablePlugin.set
	 *
	 * @see TablePlugin.set
	 * @param key
	 *            the key whichs value should be set
	 * @param value
	 *            the value for the key
	 * @return void
	 */
	Table.prototype.set = function(key, value) {
		this.tablePlugin.set(key, value);
	};

	/**
	 * Given an unbalanced table structure, pad it with the necessary cells to
	 * make it perfectly rectangular
	 *
	 * @param {Aloha.Table} tableObj
	 */
	function correctTableStructure ( tableObj ) {
		var table = tableObj.obj,

			i,
			j,
		    row,
		    rows = tableObj.getRows(),
		    rowsNum = rows.length,

			cols,
			colsNum,

		    colsCount,
		    maxColsCount = 0,
		    cachedColsCounts = [rowsNum],
		    colsCountDiff,
		    colSpan;

		for ( i = 0; i < rowsNum; i++ ) {
			row = jQuery( rows[ i ] );
			cols = row.children( 'td, th' );
			colsNum = cols.length;
			colsCount = Utils.cellIndexToGridColumn( rows, i, colsNum - 1 ) + 1;

			// Check if the last cell in this row has a col span, to account
			// for it in the total number of colums in this row

			colSpan = parseInt( cols.last().attr( 'colspan' ), 10 );

			if ( colSpan == 0 ) {
				// TODO: support colspan=0
				// http://dev.w3.org/html5/markup/td.html#td.attrs.colspan
				// http://www.w3.org/TR/html401/struct/tables.html#adef-colspan
				// The value zero ("0") means that the cell spans all columns
				// from the current column to the last column of the column
				// group (COLGROUP) in which the cel
			} else if ( !isNaN( colSpan ) ) {
				// The default value of this attribute is one ("1"), so where this
				// is the case, we will remove such superfluous colspan attributes
				if ( colSpan == 1 ) {
					cols.last().removeAttr( 'colspan' );
				}

				colsCount += ( colSpan - 1 );
			}

			// if a rowspan is set in the last element of the row, the row(s) below
			// are supposed to have one less column for every colspan the element has
			rowSpan = parseInt(cols.last().attr('rowspan'), 10);
			if (rowSpan > 1) {
				for (j = 1; j < rowSpan-1; j++) {
					if (colSpan > 1) {
						cachedColsCounts[i+j] += colSpan;
					} else {
						cachedColsCounts[i+j] += 1;
					}
				}
			}

			cachedColsCounts[i] += colsCount;

			if (cachedColsCounts[i] > maxColsCount) {
				maxColsCount = cachedColsCounts[i];
			}
		}

		for ( i = 0; i < rowsNum; i++ ) {
			colsCountDiff = maxColsCount - cachedColsCounts[ i ];
			if ( colsCountDiff > 0 ) {
				// Create as many td's as we need to complete the row
				jQuery( rows[ i ] ).append(
					( new Array( colsCountDiff + 1 ) ).join( '<td></td>' )
				);
			}
		}
	};

	/**
	 * If all of the selected cells have been set to the same predefined style,
	 * then its style-button is toggled on. Otherwise, all style-buttons are toggled off.
	 *
	 * @param selectedCells the cells to be checked
	 * @param config the list of styles as defined in the aloha-configuration
	 * @param items the multisplit-toggle-items
	 * @param button a multisplit-button
	 *
	 * @return void
	 */
	function setActiveStyle(selectedCells, config, items, button) {
		var className;
		var allSelected = false;

		// activate all formatting buttons
		for (var i = 0; i < items.length; i++) {
			button.showItem(items[i].name);
		}

		// clear active style block
		button.setActiveItem();

		// select class of first element as reference
		for (var i = 0; i < config.length; i++) {
			if (jQuery(selectedCells[0]).hasClass(config[i].cssClass)) {
				allSelected = true;
				className = config[i].name;
				break;
			}
		}

		// if all selected cells have the same class, set it as active
		jQuery(selectedCells).each(function(index) {
			if (!jQuery(this).hasClass(className)) {
				allSelected = false;
			}
		});
		if (allSelected) {
			button.setActiveItem(className);
		}
	}

	/**
	 * Transforms the existing dom-table into an editable aloha-table. In fact it
	 * replaces the td-elements with equivalent TableCell-elements
	 * with attached events.
	 * Furthermore it creates wrapping divs to realize a click-area for row- and
	 * column selection and also attaches events.
	 *
	 * @return void
	 */
	Table.prototype.activate = function () {
		if ( this.isActive ) {
			return;
		}

		var that = this,
		    htmlTableWrapper,
		    tableWrapper, eventContainer;

		// alter the table attributes
		this.obj.addClass( this.get( 'className' ) );
		this.obj.contentEditable( false );

		// set an id to the table if not already set
		if ( this.obj.attr( 'id' ) == '' ) {
			this.obj.attr( 'id', GENTICS.Utils.guid() );
		}

		// unset the selection type
		this.selection.selectionType = undefined;

		// the eventContainer will be the tbody (if there is one), or the table (if no tbody exists)
		eventContainer = this.obj.children('tbody');
		if (eventContainer.length === 0) {
			eventContainer = this.obj;
		}

		eventContainer.bind( 'keydown', function ( jqEvent ) {
			if ( !jqEvent.ctrlKey && !jqEvent.shiftKey ) {
				if ( that.selection.selectedCells.length > 0 &&
						that.selection.selectedCells[ 0 ].length > 0 ) {
					that.selection.selectedCells[ 0 ][ 0 ].firstChild.focus();
				}
			}
		} );

		function isNotUnrenderedNode(node) {
			return !Html.isUnrenderedNode(node);
		}

		/**
		 * @param {WrappedRange} range
		 */
		function isRangeVisiblyAtLeftBoundary(range) {
			var offset = range.startOffset;
			var node = range.startContainer;
			if (0 === offset) {
				return true;
			}
			if (1 === node.nodeType) {
				return !Html.findNodeRight(
					node.childNodes[offset - 1],
					isNotUnrenderedNode
				);
			}
			if (3 === node.nodeType) {
				return Html.isWSPorZWSPText(node.data.substr(0, offset));
			}
			return false;
		}

		/**
		 * @param {WrappedRange} range
		 */
		function isRangeVisiblyAtRightBoundary(range) {
			var offset = range.startOffset;
			var node = range.startContainer;
			if (Dom.nodeLength(node) === offset) {
				return true;
			}
			if (1 === node.nodeType) {
				return !Html.findNodeLeft(
					node.childNodes[offset - 1],
					isNotUnrenderedNode
				);
			}
			if (3 === node.nodeType) {
				return Html.isWSPorZWSPText(node.data.substr(offset));
			}
			return false;
		}

		Aloha.bind('aloha-command-will-execute', function (_, event){
			var range = Aloha.getSelection().getRangeAt(0);
			var adjacent;
			if ('forwarddelete' === event.commandId) {
				if (!range.collapsed || !isRangeVisiblyAtRightBoundary(range)) {
					return;
				}
				var node = range.commonAncestorContainer;
				if (3 === node.nodeType) {
					adjacent = node.parentNode
					        && Html.findNodeLeft(
								node.parentNode.nextSibling,
								isNotUnrenderedNode
							);
				} else if (1 === node.nodeType) {
					adjacent = Html.findNodeLeft(
						node.nextSibling,
						isNotUnrenderedNode
					);
				}
				if (adjacent === that.tableWrapper) {
					event.preventDefault = true;
					Aloha.getSelection().collapse(
						getNewSelectedElement('first', that.obj),
						0
					);
				}
			} else if ('delete' === event.commandId) {
				if (!range.collapsed || !isRangeVisiblyAtLeftBoundary(range)) {
					return;
				}
				var node = range.commonAncestorContainer;
				if (3 === node.nodeType) {
					adjacent = node.parentNode
					        && Html.findNodeRight(
								node.parentNode.previousSibling,
								isNotUnrenderedNode
							);
				} else if (1 === node.nodeType) {
					adjacent = Html.findNodeRight(
						node.previousSibling,
						isNotUnrenderedNode
					);
				}
				if (adjacent === that.tableWrapper) {
					var nodeToSelect = getNewSelectedElement('last', that.obj);
					event.preventDefault = true;
					Aloha.getSelection().collapse(
						nodeToSelect,
						Dom.nodeLength(nodeToSelect)
					);
				}
			}
		});


		this.obj.on('keydown', function (jqEvent) {
			// Delete button
			if (jqEvent.keyCode === 46) {
				if (that.selection.selectionType === 'row') {
					that.deleteRows();
				} else if (that.selection.selectionType === 'column') {
					that.deleteColumns();
				}

				// jqEvent.stopPropagation doesn't support cancelBubble
				// in the last jQuery versions. (query/jquery@97fa97f#diff-031bb62d959e7e4949d1847c82507f33L676)
				if (typeof jqEvent.stopPropagation === 'function') {
					jqEvent.stopPropagation();
				} else {
					// Workaround for IE
					jqEvent.cancelBubble = true;
				}
			}
		});

		/*
		We need to make sure that when the user has selected text inside a
		table cell we do not delete the entire row, before we activate this

		this.obj.bind( 'keyup', function ( $event ) {
			if ( $event.keyCode == 46 ) {
				if ( that.selection.selectedColumnIdxs.length ) {
					that.deleteColumns();
					$event.stopPropagation();
				} else if ( that.selection.selectedRowIdxs.length ) {
					that.deleteRows();
					$event.stopPropagation();
				} else {
					// Nothing to delete
				}
			}
		} );
		*/

		// handle click event of the table
	//	this.obj.bind('click', function(e){
	//		// stop bubbling the event to the outer divs, a click in the table
	//		// should only be handled in the table
	//		e.stopPropagation();
	//		return false;
	//	});
	//

	 // handle column/row resize
			eventContainer.delegate( 'th, td', 'mousemove', function( e ) {

				var jqObj = jQuery( this );
				// offset to be used for activating the resize cursor near a table border
				var mouseOffset = 3;

				// filter out the control cells
				if ( jQuery( this ).hasClass( 'aloha-table-selectrow' ) || jQuery( this ).closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ))
					return;

				var closeToLeftBorder = function(cell) {
					return ( ( e.pageX - cell.offset().left ) < mouseOffset );
				};

				var closeToTopBorder = function(cell) {
					return ( ( e.pageY - cell.offset().top ) < mouseOffset );
				};

				var closeToTableBottom = function(cell) {
					var row = cell.closest( 'tr');
					// check if it's the last row
					if ( row.next( 'tr').length > 0 ) {
						return false
					}

					var cursorOffset = e.pageY - ( row.offset().top + row.outerHeight() );
					return cursorOffset > (mouseOffset * -1) && cursorOffset < mouseOffset;
				}

				var colResize = that.tablePlugin.colResize;
				var rowResize = that.tablePlugin.rowResize;

				if ( colResize && closeToLeftBorder( jqObj ) ) {
					jqObj.css( 'cursor', 'col-resize' );
					return that.attachColumnResize( jqObj );
				} else if ( rowResize && closeToTopBorder( jqObj ) ) {
					jqObj.css( 'cursor', 'row-resize' );
					return that.attachRowResize( jqObj );
				} else if ( rowResize && closeToTableBottom( jqObj ) ) {
					jqObj.css( 'cursor', 'row-resize' );
					return that.attachRowResize( jqObj, true );
				} else {
					jqObj.css( 'cursor', 'default' );
					return that.detachRowColResize( jqObj );
				}
			});

			eventContainer.bind( 'mousemove', function( e ) {

				var jqObj = jQuery( this ).closest( 'table' );

				var isTableRightBorder = function( table ) {
					var cursorOffset = e.pageX - ( table.offset().left + table.outerWidth() );
					return cursorOffset > -5 && cursorOffset < 5;
				};

				var tableResize = that.tablePlugin.tableResize;

				if ( tableResize && isTableRightBorder( jqObj ) ) {
					return that.attachTableResizeWidth( jqObj );
				}

			});

		eventContainer.bind( 'mousedown', function ( jqEvent ) {
			// focus the table if not already done
			if ( !that.hasFocus ) {
				that.focus();
			}

			// DEACTIVATED by Haymo prevents selecting rows
			//		// if a mousedown is done on the table, just focus the first cell of the table
			//		setTimeout(function() {
			//			var firstCell = that.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//			TableSelection.unselectCells();
			//			jQuery(firstCell).get(0).focus();
			//			// move focus in first cell
			//			that.obj.cells[0].wrapper.get(0).focus();
			//		}, 0);

			// stop bubbling and default-behaviour
			jqEvent.stopPropagation();
			jqEvent.preventDefault();
			return false;
		} );

		// ### create a wrapper for the table (@see HINT below)
		// wrapping div for the table to suppress the display of the resize-controls of
		// the editable divs within the cells
		// tha data-block-skip-scope attribute will keep the block plugin from setting the
		// FloatingMenu's scope when the block is clicked
		tableWrapper = jQuery(
			'<div class="' + this.get( 'classTableWrapper' ) + '" data-block-skip-scope="true"></div>'
		);
		tableWrapper.contentEditable( false );

		// mark the wrapper as ephemeral
		Ephemera.markWrapper(tableWrapper);

		// wrap the tableWrapper around the table
		this.obj.wrap( tableWrapper );

		// Check because the aloha block plugin may not be loaded
		var parent = this.obj.parent();
		if (parent.alohaBlock) {
			parent.alohaBlock();
		}

		// :HINT The outest div (Editable) of the table is still in an editable
		// div. So IE will surround the the wrapper div with a resize-border
		// Workaround => just disable the handles so hopefully won't happen any ugly stuff.
		// Disable resize and selection of the controls (only IE)
		// Events only can be set to elements which are loaded from the DOM (if they
		// were created dynamically before) ;)

		htmlTableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) );
		htmlTableWrapper.get( 0 ).onresizestart = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).oncontrolselect = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).ondragstart = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).onmovestart = function ( e ) { return false; };
		// the following handler prevents proper selection in the editable div in the caption!
		// htmlTableWrapper.get( 0 ).onselectstart = function ( e ) { return false; };

		this.tableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) ).get( 0 );

		jQuery( this.cells ).each( function () {
			this.activate();
		} );

		// after the cells where replaced with contentEditables ... add selection cells
		// first add the additional columns on the left side
		this.attachSelectionColumn();
		// then add the additional row at the top
		this.attachSelectionRow();
		this.makeCaptionEditable();
		this.checkWai();
		this.isActive = true;

		Aloha.trigger( 'aloha-table-activated' );
	};

	/**
	 * Make the table caption editable (if present)
	 */
	Table.prototype.makeCaptionEditable = function() {
		var caption = this.obj.find('caption').eq(0);
		if (caption) {
			this.tablePlugin.makeCaptionEditable(caption);
		}
	};

	/**
	 * Check the WAI conformity of the table and sets the attribute.
	 *
	 * @returns {boolean} True is WAI is activated, False otherwise.
	 */
	Table.prototype.checkWai = function () {
		var thisWai = this.wai;
		if (!thisWai) {
			return false;
		}

		var waiGreen = this.get('waiGreen'),
			waiRed = this.get('waiRed');

		thisWai.removeClass(waiGreen + ' ' + waiRed);

		// Y U NO explain why we must check that summary is longer than 5 characters?
		// http://cdn3.knowyourmeme.com/i/000/089/665/original/tumblr_l96b01l36p1qdhmifo1_500.jpg

		if (jQuery.trim(this.obj[0].summary) !== '') {
			thisWai.addClass(waiGreen);
			return true;
		}

		thisWai.addClass(waiRed);
		return false;
	};

	/**
	 * Add the selection-column to the left side of the table and attach the events
	 * for selection rows
	 *
	 * @return void
	 */
	Table.prototype.attachSelectionColumn = function() {
		// create an empty cell
		var emptyCell = jQuery('<td>'),
			rowIndex, columnToInsert, rowObj, that = this, rows, i;

		// set the unicode '&nbsp;' code
		emptyCell.html('\u00a0');

		that = this;
		rows = this.obj.context.rows;

		// add a column before each first cell of each row
		for ( i = 0; i < rows.length; i++) {
			rowObj = jQuery(rows[i]);
			columnToInsert = emptyCell.clone();
			columnToInsert.addClass(this.get('classSelectionColumn'));
			columnToInsert.css('width', this.get('selectionArea') + 'px');
			//rowObj.find('td:first').before(columnToInsert);
			rowObj.prepend(columnToInsert);
			// rowIndex + 1 because an addtional row is still added
			rowIndex = i + 1;

			// this method sets the selection-events to the cell
			this.attachRowSelectionEventsToCell(columnToInsert);
		}
	};

	/**
	 * Binds the needed selection-mouse events to the given cell
	 *
	 * @param cell
	 *            The jquery object of the table-data field
	 * @return void
	 */
	Table.prototype.attachRowSelectionEventsToCell = function(cell){
		var that = this;

		// mark cell as ephemeral
		Ephemera.markElement(cell);

		// unbind eventually existing events of this cell
		cell.unbind('mousedown');
		cell.unbind('mouseover');

		// prevent ie from selecting the contents of the table
		cell.get(0).onselectstart = function() { return false; };

		cell.bind('mousedown', function(e) {
			// set flag that the mouse is pressed
//TODO to implement the mousedown-select effect not only must the
//mousedown be set here but also be unset when the mouse button is
//released.
//			that.mousedown = true;
			return that.rowSelectionMouseDown(e);
		});

		cell.bind('mouseover', function(e){
			// only select more crows if the mouse is pressed
			if ( that.mousedown ) {
				return that.rowSelectionMouseOver(e);
			}
		});
	};

	/**
	 * Mouse-Down event for the selection-cells on the left side of the table
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.rowSelectionMouseDown = function ( jqEvent ) {
		// focus the table (if not already done)
		this.focus();

		// if no cells are selected, reset the selection-array
		if ( this.selection.selectedCells.length == 0 ) {
			this.rowsToSelect = [];
		}

		// set the origin-rowId of the mouse-click
		this.clickedRowId = jqEvent.currentTarget.parentNode.rowIndex;

		// set single column selection
		if ( jqEvent.metaKey ) {
			var arrayIndex = jQuery.inArray( this.clickedRowId, this.rowsToSelect );
			if ( arrayIndex >= 0 ) {
				this.rowsToSelect.splice( arrayIndex, 1 );
			} else {
				this.rowsToSelect.push( this.clickedRowId );
			}
		// block of columns selection
		} else if ( jqEvent.shiftKey ) {
			this.rowsToSelect.sort( function( a, b ) { return a - b; } );
			var start = this.rowsToSelect[ 0 ];
			var end = this.clickedRowId;
			if ( start > end ) {
				start = end;
				end = this.rowsToSelect[ 0 ];
			}
			this.rowsToSelect = [];
			for ( var i = start; i <= end; i++ ) {
				this.rowsToSelect.push( i );
			}
		// single column
		} else {
			this.rowsToSelect = [ this.clickedRowId ];
		}

		// mark the selection visual
		this.selectRows();

		// prevent browser from selecting the table
		jqEvent.preventDefault();

		// stop bubble, otherwise the mousedown of the table is called ...
		jqEvent.stopPropagation();

		// prevent ff/chrome/safare from selecting the contents of the table
		return false;
	};

	/**
	 * The mouse-over event for the selection-cells on the left side of the table.
	 * On mouse-over check which column was clicked, calculate the span between
	 * clicked and mouse-overed cell and mark them as selected
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.rowSelectionMouseOver = function (jqEvent) {
		var rowIndex = jqEvent.currentTarget.parentNode.rowIndex,
			indexInArray, start, end, i;

		// only select the row if the mouse was clicked and the clickedRowId isn't
		// from the selection-row (row-id = 0)
		if (this.mousedown && this.clickedRowId >= 0) {

			// select first cell
			//var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//jQuery(firstCell).get(0).focus();

			indexInArray = jQuery.inArray(rowIndex, this.rowsToSelect);

			start = (rowIndex < this.clickedRowId) ? rowIndex : this.clickedRowId;
			end = (rowIndex < this.clickedRowId) ? this.clickedRowId : rowIndex;

			this.rowsToSelect = new Array();
			for ( i = start; i <= end; i++) {
				this.rowsToSelect.push(i);
			}

			// this actually selects the rows
			this.selectRows();

			// prevent browser from selecting the table
			jqEvent.preventDefault();

			// stop bubble, otherwise the mousedown of the table is called ...
			jqEvent.stopPropagation();

			// prevent ff/chrome/safare from selecting the contents of the table
			return false;
		}
	};

  /**
   * Binds the needed selection-mouse events to the given cell
   *
   * @param cell
   *            The jquery object of the table-data field
   * @return void
   */
	Table.prototype.attachSelectionRow = function () {
		var that = this;

		// create an empty td
		var emptyCell = jQuery('<td>');
		emptyCell.html('\u00a0');

		// get the number of columns in the table (first row)
		// iterate through all rows and find the maximum number of columns to add
		var numColumns = 0;
		for( var i = 0; i < this.obj.context.rows.length; i++ ){
			var curNumColumns = 0;

			for( var j = 0; j < this.obj.context.rows[i].cells.length; j++ ){
				var colspan = Utils.colspan( this.obj.context.rows[i].cells[j] );
				curNumColumns += colspan;
			}

			if( numColumns < curNumColumns ) {
				numColumns = curNumColumns;
			}
		}

		var selectionRow = jQuery('<tr>');
		selectionRow.addClass(this.get('classSelectionRow'));
		selectionRow.css('height', this.get('selectionArea') + 'px');

		// mark selection row as ephemeral
		Ephemera.markElement(selectionRow);

		for (var i = 0; i < numColumns; i++) {

			var columnToInsert = emptyCell.clone();
			// the first cell should have no function, so only attach the events for
			// the rest
			if (i > 0) {
				// bind all mouse-events to the cell
				this.attachColumnSelectEventsToCell(columnToInsert);
				//set the colspan of selection column to match the colspan of first row columns
			} else {
				var columnToInsert = jQuery('<td>').clone();
				columnToInsert.addClass(this.get('classLeftUpperCorner'));
				var clickHandler = function (e) {
					// select the Table
					that.focus();
					that.selection.selectAll();

					// set the selection type before updating the scope
					that.tablePlugin.activeTable.selection.selectionType = 'cell';
					that.tablePlugin.updateFloatingMenuScope();

					// As side-effect of the following call the focus
					// will be set on the first selected cell.
					// This will be overwritten with the summary
					// attribute-field, if the setting summaryinsidebar
					// is false.
					that._removeCursorSelection();

					//If the summary should be modified in the sidebar
					//we activate the sidebar panel
					if (that.tablePlugin.settings.summaryinsidebar) {
						that.tablePlugin.sidebar.open();
						that.tablePlugin.sidebarPanel.activate(that.obj);
						that.tablePlugin.sidebar.correctHeight();
					}

					// jump in Summary field
					// attempting to focus on summary input field will occasionally result in the
					// following exception:
					//uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMHTMLInputElement.setSelectionRange]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: src/dep/ext-3.2.1/ext-all.js :: <TOP_LEVEL> :: line 11" data: no]
					// this occurs when the tab in which the summary field is contained is not visible
					// TODO: I'm adding a try catch clause here for the time being, but a proper solution, which addresses the problem of how to handle invisible fields ought to be persued.

					try {
						that.tablePlugin.summary.focus();
						e.stopPropagation();
						e.preventDefault();
					} catch (e) {
						Console.error ('Table', e.message);
					}

					return false;
				};
				this.wai = jQuery('<div/>').width(25).height(12).click(clickHandler);
				columnToInsert.append(this.wai);
			}

			// add the cell to the row
			selectionRow.append(columnToInsert);
		}

		// global mouseup event to reset the selection properties
		jQuery(document).bind('mouseup', function(e) { that.columnSelectionMouseUp(e) } );

		this.obj.find('tr:first').before( selectionRow );
	};

	/**
	 * Binds the events for the column selection to the given cell.
	 *
	 * @param cell
	 *            the jquery object of the td-field
	 * @return void
	 */
	Table.prototype.attachColumnSelectEventsToCell = function ( cell ) {
		var that = this;

		// unbind eventually existing events of this cell
		cell.unbind( 'mousedown' );
		cell.unbind( 'mouseover' );

		// prevent ie from selecting the contents of the table
		cell.get( 0 ).onselectstart = function () { return false; };

		cell.bind( 'mousedown',  function ( e ) { that.columnSelectionMouseDown( e ) } );
		cell.bind( 'mouseover',  function ( e ) { that.columnSelectionMouseOver( e ) } );
	};

	/**
	 * Handles the mouse-down event for the selection-cells on the top of the
	 * menu
	 *
	 * @param {jQuery:Event} jqEvent - the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseDown = function ( jqEvent ) {
		// focus the table (if not already done)
		this.focus();

		// if no cells are selected, reset the selection-array
		if ( this.selection.selectedCells.length == 0 ) {
			this.columnsToSelect = [];
		}

		// set the origin-columnId of the mouse-click
		this.clickedColumnId = jQuery( jqEvent.currentTarget.parentNode )
									.children().index( jqEvent.currentTarget );

		// set single column selection
		if ( jqEvent.metaKey ) {
			var arrayIndex = jQuery.inArray( this.clickedColumnId, this.columnsToSelect );
			if ( arrayIndex >= 0 ) {
				this.columnsToSelect.splice( arrayIndex, 1 );
			} else {
				this.columnsToSelect.push( this.clickedColumnId );
			}
		// block of columns selection
		} else if ( jqEvent.shiftKey ) {
			this.columnsToSelect.sort( function( a, b ) { return a - b; } );
			var start = this.columnsToSelect[ 0 ];
			var end = this.clickedColumnId;
			if ( start > end ) {
				start = end;
				end = this.columnsToSelect[ 0 ];
			}
			this.columnsToSelect = [];
			for ( var i = start; i <= end; i++ ) {
				this.columnsToSelect.push( i );
			}
		// single column
		} else {
			this.columnsToSelect = [ this.clickedColumnId ];
		}

		// mark the selection visual
		this.selectColumns();

		// prevent browser from selecting the table
		jqEvent.preventDefault();

		// stop bubble, otherwise the mousedown of the table is called ...
		jqEvent.stopPropagation();

		// prevent ff/chrome/safare from selecting the contents of the table
		return false;
	};

	/**
	 * Mouseover-event for the column-selection cell. This method calcluates the
	 * span between the clicked column and the mouse-overed cell and selects the
	 * columns inbetween. and mark them as selected
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseOver = function (jqEvent) {

		var
			colIdx = jqEvent.currentTarget.cellIndex,
			columnsToSelect = [],
			start,
			end;

		// select all columns from the last clicked to the hoverd
		if ( this.mouseDownColIdx ) {
			start = (colIdx < this.mouseDownColIdx) ? colIdx : this.mouseDownColIdx;
			end = (colIdx < this.mouseDownColIdx) ? this.mouseDownColIdx : colIdx;
			for (var i = start; i <= end; i++) {
				columnsToSelect.push(i);
			}
			this.selectColumns( columnsToSelect );
		}
	};

	/**
	 * MouseUp-event for the column-selection. This method resets the
	 * selection mode
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseUp = function (jqEvent) {
		this.mouseDownColIdx = false;
	};

	/**
	 * Deletes the selected rows. If no row are selected, delete the row, where the
	 * cursor is positioned. If all rows of the table should be deleted, the whole
	 * table is deletet and removed from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteRows = function() {
		var
			rowIDs = [],
			rowsToDelete = {},
			table = this;

		// if a selection was made, delete the selected cells
		if (0 === this.selection.selectedCells.length) {
			return;
		}

		for (var i = 0; i < this.selection.selectedCells.length; i++) {
			rowsToDelete[this.selection.selectedCells[i].parentNode.rowIndex] = true;
		}

	    for (rowId in rowsToDelete) {
	       rowIDs.push(rowId);
	    }

		// if all rows should be deleted, set a flag to remove the WHOLE table
		var deleteTable = false;
		if (rowIDs.length == this.numRows) {
			deleteTable = true;
		}

		// delete the whole table
		if (deleteTable) {
			var that = this;
			Dialog.confirm({
				title : i18n.t('Table'),
				text : i18n.t('deletetable.confirm'),
				yes : function () {
					that.deleteTable();
				}
			});
		} else {

			rowIDs.sort(function(a,b){return a - b;});

			// check which cell should be focused after the deletion
			var focusRowId = rowIDs[0];
			if (focusRowId > (this.numRows - rowIDs.length)) {
				focusRowId --;
			}

			// get all rows
			var rows = this.getRows();

			//splits all cells on the rows to be deleted
			jQuery.each( rowIDs, function ( unused, rowId ) {
				var row = rows[ rowId ];
				for (var i = 0; i < row.cells.length; i++) {
					Utils.splitCell( row.cells[i], function () {
						return table.newActiveCell().obj;
					});
				}
			});

			//decreases rowspans of cells that span the row to be deleted
			//and removes the row
			var grid = Utils.makeGrid( rows );
			jQuery.each( rowIDs, function ( unused, rowId ) {
				var row = grid[ rowId ];
				for ( var j = 0; j < row.length; ) {
					var cellInfo = row[ j ];
					var rowspan = Utils.rowspan( cellInfo.cell );
					if ( 1 < rowspan ) {
						jQuery( cellInfo.cell ).attr( 'rowspan', rowspan - 1);
					}
					j += cellInfo.colspan;
				}
				jQuery( rows[ rowId ] ).remove();
			});

			// reduce the attribute storing the number of rows in the table
			this.numRows -= rowIDs.length;

			// IE needs a timeout to work properly
			window.setTimeout( function() {
				var lastCell = jQuery( rows[1].cells[ focusRowId +1 ] );
				lastCell.focus();
			}, 5);

			// finally unselect the marked cells
			this.selection.unselectCells();
		}
	};

	/**
	 * Deletes the selected columns. If no columns are selected, delete the column, where the
	 * cursor is positioned. If all columns of the table should be deleted, the whole
	 * table is deleted from the dom and removed from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteColumns = function() {
		var
			colIDs = [],
			cellToDelete = [],
			// get all rows to iterate
		    rows = this.getRows(),
			that = this,
			changeColspan = [],
			cells,
			cellInfo;

		var grid = Utils.makeGrid(rows);
		var selectColWidth = 1; //width of the select-row column

		// if all columns should be deleted, remove the WHOLE table
		// delete the whole table
		if ( this.selection.selectedColumnIdxs.length == grid[0].length - selectColWidth ) {

			Dialog.confirm({
				title : i18n.t('Table'),
				text : i18n.t('deletetable.confirm'),
				yes : function () {
					that.deleteTable();
				}
			});

		} else {

			colIDs.sort(function(a,b) {return a - b;} );

			//TODO there is a bug that that occurs if a column is
			//selected and deleted, and then a column with a greater
			//x-index is selected and deleted.

			//sorted so we delete from right to left to minimize interfernce of deleted rows
			var gridColumns = this.selection.selectedColumnIdxs.sort(function(a,b){ return b - a; });
			for (var i = 0; i < gridColumns.length; i++) {
				var gridColumn = gridColumns[i];
				for (var j = 0; j < rows.length; j++) {
					var cellInfo = grid[j][gridColumn];
					if ( ! cellInfo ) {
						//TODO this case occurred because of a bug somewhere which should be fixed
						continue;
					}
					if ( 0 === cellInfo.spannedX ) {
						if (1 < cellInfo.colspan) {
							var nCell = this.newActiveCell().obj;
							jQuery( cellInfo.cell ).after(nCell);
							nCell.attr('rowspan', cellInfo.rowspan);
							nCell.attr('colspan', cellInfo.colspan - 1);
						}
						jQuery( cellInfo.cell ).remove();
					} else {
						jQuery( cellInfo.cell ).attr('colspan', cellInfo.colspan - 1);
					}
					//ensures that always 0 === cellInfo.spannedY
					j += cellInfo.rowspan - 1;
				}
				//rebuild the grid to reflect the table structure change
				grid = Utils.makeGrid(rows);
			}

			// reduce the attribute storing the number of rows in the table
			this.numCols -= colIDs.length;

			// IE needs a timeout to work properly
			window.setTimeout( function() {
				var lastCell = jQuery( rows[1].cells[1] );
				lastCell.focus();
			}, 5);

			this.selection.unselectCells();
		}
	};

	/**
	 * Deletes the table from the dom and remove it from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteTable = function() {
		var deleteIndex = -1;
		for (var i = 0; i < this.tablePlugin.TableRegistry.length; i++){
			if (this.tablePlugin.TableRegistry[i].obj.attr('id') == this.obj.attr('id')) {
				deleteIndex = i;
				break;
			}
		}
		if (deleteIndex >= 0) {
			// before deleting the table, deactivate it
			this.deactivate();

			this.selection.selectionType = undefined;
			this.tablePlugin.TableRegistry.splice(i, 1);

			// we will set the cursor right before the removed table
			var newRange = Aloha.Selection.rangeObject;
			// TODO set the correct range here (cursor shall be right before the removed table)
			newRange.endContainer = this.obj.get(0).parentNode;
			newRange.startContainer = newRange.endContainer;

			newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(this.obj.get(0));
			newRange.startOffset = newRange.endOffset;

			newRange.clearCaches();

			this.obj.remove();
			this.parentEditable.obj.focus();
			// select the new range
			newRange.correctRange();
			newRange.select();
		}
	};

	/**
	 * @param {string} position
	 *            could be 'after' or 'before'. defines the position where the new
	 *            rows should be inserted
	 */
	function rowIndexFromSelection( position, selection ) {

		var newRowIndex = -1;

		// get the index where the new rows should be inserted
		var cellOfInterest = null;
		if ( 'before' === position ) {
			cellOfInterest = selection.selectedCells[ 0 ];
		} else if ( 'after' === position ) {
			var offset = selection.selectedCells.length - 1;
			cellOfInterest = selection.selectedCells[ offset ];
		}

		if (cellOfInterest && cellOfInterest.nodeType == 1) {
			newRowIndex = cellOfInterest.parentNode.rowIndex;
		}

		return newRowIndex;
	}

	/**
	 * Wrapper function for this.addRow to add a row before the active row
	 *
	 * @see Table.prototype.addRow
	 */
	Table.prototype.addRowBeforeSelection = function(highlightNewRows) {
		var newRowIndex = rowIndexFromSelection( 'before', this.selection );
		if ( -1 !== newRowIndex ) {
			this.addRow( newRowIndex );
		}
	};

	/**
	 * Wrapper function for this.addRow to add a row after the active row
	 *
	 * @see Table.prototype.addRow
	 */
	Table.prototype.addRowAfterSelection = function() {
		var newRowIndex = rowIndexFromSelection( 'after', this.selection );
		if ( -1 !== newRowIndex ) {
			this.addRow( newRowIndex + 1 );
		}
	};

	/**
	 * Adds a new row to the table.
	 *
	 * @param {int} rowIndex
	 *        the index at which the new row shall be inserted
	 */
	Table.prototype.addRow = function(newRowIndex) {

		var that = this;
		var rowsToInsert = 1;

		var numCols = this.countVirtualCols();
		var $rows = this.obj.children().children('tr');
		for (var j = 0; j < rowsToInsert; j++) {
			var insertionRow = jQuery('<tr>');

			// create the first column, the "select row" column
			var selectionColumn = jQuery('<td>');
			selectionColumn.addClass(this.get('classSelectionColumn'));
			this.attachRowSelectionEventsToCell(selectionColumn);
			insertionRow.append(selectionColumn);

			var grid = Utils.makeGrid($rows);
			var selectColOffset = 1;
			if ( newRowIndex >= grid.length ) {
				for (var i = selectColOffset; i < grid[0].length; i++) {
					insertionRow.append(this.newActiveCell().obj);
				}
			} else {
				for (var i = selectColOffset; i < grid[newRowIndex].length; ) {
					var cellInfo = grid[newRowIndex][i];
					if (Utils.containsDomCell(cellInfo)) {
						var colspan = cellInfo.colspan;
						while (colspan--) {
							insertionRow.append(this.newActiveCell().obj);
						}
					} else {
						jQuery( cellInfo.cell ).attr('rowspan', cellInfo.rowspan + 1);
					}
					i += cellInfo.colspan;
				}
			}

			if ( newRowIndex >= $rows.length ) {
				$rows.eq( $rows.length - 1 ).after( insertionRow );
			} else {
				$rows.eq( newRowIndex ).before( insertionRow );
			}
		}

		this.numRows += rowsToInsert;
	};

	/**
	 * Wrapper method to add columns on the right side
	 *
	 * @see Table.addColumns
	 * @return void
	 */
	Table.prototype.addColumnsRight = function () {
		this.addColumns('right');
	};

	/**
	 * Wrapper method to add columns on the left side
	 *
	 * @see Table.addColumns
	 * @return void
	 */
	Table.prototype.addColumnsLeft = function() {
		this.addColumns('left');
	};

	/**
	 * Inserts new columns into the table. Either on the right or left side. If
	 * columns are selected, the amount of selected columns will be inserted on the
	 * 'right' or 'left' side. If no cells are selected, 1 new column will be
	 * inserted before/after the column of the last active cell.
	 * As well all column-selection events must be bound to the firsts row-cell.
	 *
	 * @param position
	 *            could be 'left' or 'right'. defines the position where the new
	 *            columns should be inserted
	 * @return void
	 */
	Table.prototype.addColumns = function( position ) {
		var
			that = this,
			emptyCell = jQuery( '<td>' ),
		    rows = this.getRows(),
			cell,
			currentColIdx,
			columnsToSelect = [],
			selectedColumnIdxs = this.selection.selectedColumnIdxs;

		if ( 0 === selectedColumnIdxs.length ) {
			return;
		}

		selectedColumnIdxs.sort( function ( a, b ) { return a - b; } );

		// refuse to insert a column unless a consecutive range has been selected
		if ( ! Utils.isConsecutive( selectedColumnIdxs ) ) {
			Dialog.alert( {
				title : i18n.t( 'Table' ),
				text  : i18n.t( 'table.addColumns.nonConsecutive' )
			});
			return;
		}

		if ( 'left' === position ) {
			currentColIdx = selectedColumnIdxs[ 0 ];
			// inserting a row before the selected column indicies moves
			// all selected columns one to the right
			for ( var i = 0; i < this.selection.selectedColumnIdxs.length; i++ ) {
				this.selection.selectedColumnIdxs[ i ] += 1;
			}
		} else {//"right" == position
			currentColIdx = selectedColumnIdxs[ selectedColumnIdxs.length - 1 ];
		}

		var grid = Utils.makeGrid( rows );

		for ( var i = 0; i < rows.length; i++ ) {
			// prepare the cell to be inserted
			cell = emptyCell.clone();
			cell.html( '\u00a0' );

			// on first row correct the position of the selected columns
			if ( i == 0 ) {
				// this is the first row, so make a column-selection cell
				this.attachColumnSelectEventsToCell( cell );
			} else {
				// activate the cell for this table
				cellObj = this.newActiveCell( cell.get(0) );
				cell = cellObj.obj;
			}

			var leftCell = Utils.leftDomCell( grid, i, currentColIdx );
			if ( null == leftCell ) {
				jQuery( rows[i] ).prepend( cell );
			} else {
				if ( 'left' === position && Utils.containsDomCell( grid[ i ][ currentColIdx ] ) ) {
					jQuery( leftCell ).before( cell );
				} else {//right
					jQuery( leftCell ).after( cell );
				}
			}

			this.numCols++;
		}
	};

	/**
	 * Helper method to set the focus-attribute of the table to true
	 *
	 * @return void
	 */
	Table.prototype.focus = function() {
		if (!this.hasFocus) {
			if (!this.parentEditable.isActive) {
				this.parentEditable.obj.focus();
			}

			// @iefix
			this.tablePlugin.setFocusedTable(this);

			// select first cell
			// TODO put cursor in first cell without selecting
			//var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//jQuery(firstCell).get(0).focus();

		}

		// TODO workaround - fix this. the selection is updated later on by the browser
		// using setTimeout here is hideous, but a simple execution-time call will fail
	// DEACTIVATED by Haymo prevents selecting rows
	//	setTimeout('Aloha.Selection.updateSelection(false, true)', 50);

	};

	/**
	 * Helper method to set the focus-attribute of the table to false
	 *
	 * @return void
	 */
	Table.prototype.focusOut = function() {
		if (this.hasFocus) {
			this.tablePlugin.setFocusedTable(undefined);
			this.selection.selectionType = undefined;
		}
	};

	/**
	 * Undoes the cursor-selection after cells have been selected.  This
	 * is done to be more consistent in the UI - there should either be
	 * a cursor-selection or a cell-selection, but not both.
	 */
	Table.prototype._removeCursorSelection = function() {
		// We can't remove the selection on IE because whenever a
		// row/column is selected, and then another row/column is
		// selected, the browser windows scrolls to the top of the page
		// (som kind of browser bug).

		// This is no problem for IE because IE removes the
		// cursor-selection by itself and shows a frame around the
		// table, with resize handles (the frame seems useless).

		// On other browsers, we can't remove the selection because the
		// floating menu will disappear when one selects a rows/column
		// and types a key (that's the same effect as when one clicks
		// outside the editable).

		//TODO: currently, removing the cursor selection can't be
		//     reliably implemented.
		//if ( ! jQuery.browser.msie ) {
		//    Aloha.getSelection().removeAllRanges();
		//}

		// The following is a workaround for the above because we can't
		// leave the cursor-selection outside of the table, since
		// otherwise the floating menu scope will be incorrect when one
		// CTRL-clicks on the rows or columns.

		var selection = Aloha.getSelection();

		if ( !selection ||
				!selection._nativeSelection ||
					selection._nativeSelection._ranges.length == 0 ) {
			return;
		}

		var range = selection.getRangeAt( 0 );
		if ( null == range.startContainer ) {
			return;
		}

		// if the selection is  already in the table, do nothing
		if ( 0 !== jQuery( range.startContainer ).closest('table').length ) {
			return;
		}

		// if no cells are selected, do nothing
		if ( 0 === this.selection.selectedCells.length ) {
			return;
		}

		// set the foces to the first selected cell
		var container = TableCell.getContainer( this.selection.selectedCells[ 0 ] );
		jQuery( container ).focus();
	}

	/**
	 * Marks all cells of the specified column as marked (adds a special class)
	 *
	 * @return void
	 */
	Table.prototype.selectColumns = function ( columns ) {
		var columnsToSelect;

		if (columns) {
			columnsToSelect = columns;
		} else {
			columnsToSelect = this.columnsToSelect;
		}

		this.selection.selectColumns(columnsToSelect);
		this.tablePlugin._columnheaderButton.setState(this.selection.isHeader());

		// ====== BEGIN UI specific code - should be handled on event aloha-table-selection-changed by UI =======

		setActiveStyle(this.selection.selectedCells, this.tablePlugin.columnConfig,
				this.tablePlugin.columnMSItems,	this.tablePlugin.columnMSButton);

		// ====== END UI specific code - should be handled by UI =======

		// blur all editables within the table
		this.obj.find('div.aloha-ui-table-cell-editable').blur();

		Utils.selectAnchorContents(this.selection.selectedCells);

		this.selection.notifyCellsSelected();
		this._removeCursorSelection();

		Scopes.setScope(this.tablePlugin.name + '.column');
	};

	/**
	 * Marks all cells of the specified row as marked (adds a special class)
	 *
	 * @return void
	 */
	Table.prototype.selectRows = function () {

		this.selection.selectRows(this.rowsToSelect);
		this.tablePlugin._rowheaderButton.setState(this.selection.isHeader());

		// ====== BEGIN UI specific code - should be handled on event aloha-table-selection-changed by UI =======

		setActiveStyle(this.selection.selectedCells, this.tablePlugin.rowConfig,
				this.tablePlugin.rowMSItems, this.tablePlugin.rowMSButton);

		// ====== END UI specific code - should be handled by UI =======

		// blur all editables within the table
		this.obj.find('div.aloha-ui-table-cell-editable').blur();

		Utils.selectAnchorContents(this.selection.selectedCells);

		this.selection.notifyCellsSelected();
		this._removeCursorSelection();

		Scopes.setScope(this.tablePlugin.name + '.row');
	};

	/**
	 * Deactivation of a Aloha-table. Clean up ... remove the wrapping div and the
	 * selection-helper divs
	 *
	 * @return void
	 */
	Table.prototype.deactivate = function() {
		// unblockify the table wrapper
		var parent = this.obj.parent();
		if (parent.mahaloBlock) {
			parent.mahaloBlock();
		}

		this.obj.removeClass(this.get('className'));
		if (jQuery.trim(this.obj.attr('class')) == '') {
			this.obj.removeAttr('class');
		}
		this.obj.removeAttr('contenteditable');
	//	this.obj.removeAttr('id');

		// unwrap the selectionLeft-div if available
		if (this.obj.parents('.' + this.get('classTableWrapper')).length){
			this.obj.unwrap();
		}

		// remove the selection row
		this.obj.find('tr.' + this.get('classSelectionRow') + ':first').remove();
		// remove the selection column (first column left)
		var that = this;
		jQuery.each(this.obj.context.rows, function(){
			jQuery(this).children('td.' + that.get('classSelectionColumn')).remove();
		});

		// remove the "selection class" from all td and th in the table
		this.obj.find('td, th').removeClass(this.get('classCellSelected'));
		this.obj.find('td, th').removeClass('aloha-table-cell_active');

		// remove cursor-styles
		this.obj.find('td, th').css('cursor', '');

		this.obj.unbind();
		this.obj.children('tbody').unbind();

		// wrap the inner html of the contentEditable div to its outer html
		for (var i = 0; i < this.cells.length; i++) {
			var Cell = this.cells[i];
			Cell.deactivate();
		}

		// remove editable span in caption (if any)
		this.obj.find('caption div').each(function() {
			jQuery(this).contents().unwrap();
		});

		// better unset ;-) otherwise activate() may think you're activated.
		this.isActive = false;
	};

	/**
	 * Attach the event for column resize for the given cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.attachColumnResize = function(cell) {

		var that = this;

		//unbind any exisiting resize event handlers
		that.detachRowColResize( cell );

		var rows = cell.closest( 'tbody' ).children( 'tr' );
		var cellRow = cell.closest( 'tr' );
		var gridId = Utils.cellIndexToGridColumn(rows,
			rows.index(cellRow),
			cellRow.children().index(cell)
		);

		var resizeColumns = function(pixelsMoved) {
			var expandToWidth, reduceToWidth;

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] );

				// skip the select & cells with colspans
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) {
					return true;
				}

				if (gridCi === gridId ) {
					if (!reduceToWidth) {
						reduceToWidth = currentCell.width() - pixelsMoved;
					}

					Utils.resizeCellWidth( currentCell, reduceToWidth );

				} else if (gridCi === gridId - 1) {
					if (!expandToWidth) {
						expandToWidth = currentCell.width() + pixelsMoved;
					}

					Utils.resizeCellWidth( currentCell, expandToWidth );

				}

				return true;
			});
		};

		cell.bind('mousedown.resize', function() {

			// create a guide
			var guide = jQuery( '<div></div>' );
			var $cell = jQuery(cell);
			var width = $cell.outerWidth() - $cell.innerWidth();
			var height = $cell.closest( 'tbody' ).innerHeight();
			guide.css({
				'height': (height < 1) ? 1 : height,
				'width': (width < 1) ? 1 : width,
				'top': $cell.closest( 'tbody' ).offset().top,
				'left': $cell.offset().left,
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery('body').append(guide);

			Utils.getCellResizeBoundaries(gridId, rows, function(maxPageX, minPageX) {

				// unset the selection type
				that.selection.resizeMode = true;

				// move the guide while dragging
				jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) {
					// limit the maximum resize
					if ( e.pageX > minPageX && e.pageX < maxPageX ) {
						guide.css( 'left', e.pageX );
					}
				});

				// do the actual resizing after drag stops
				jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) {
					var pixelsMoved = 0;

					if ( e.pageX < minPageX ) {
						pixelsMoved = minPageX - cell.offset().left;
					} else if ( e.pageX > minPageX && e.pageX < maxPageX ) {
						pixelsMoved = e.pageX - cell.offset().left;
					} else if ( e.pageX > maxPageX ) {
						pixelsMoved = maxPageX - cell.offset().left;
					}

					if ( pixelsMoved !== 0 ) {
						resizeColumns( pixelsMoved );
					}

					jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' );
					jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' );

					// unset the selection resize mode
					that.selection.resizeMode = false;

					guide.remove();
				});

			});

		});

	};

	/**
	 * Attach the event handler for row resize for the given cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.attachRowResize = function(cell, lastRow) {

		var that = this;

		//unbind any exisiting resize event handlers
		that.detachRowColResize( cell );

		var resizeRows = function(pixelsMoved) {
			var expandingRow;

			if (lastRow) {
				expandingRow = cell.closest( 'tr' );
			} else {
				expandingRow = cell.closest( 'tr' ).prev( 'tr' );
			}

			var currentRowHeight = expandingRow.height();
			var expandToHeight = currentRowHeight + pixelsMoved;

			// correct if the height is a minus value
			if ( expandToHeight < 0 ) {
				expandToHeight = 1;
			}

			expandingRow.css( 'height', expandToHeight );
		};

		cell.bind( 'mousedown.resize', function(){

			// create a guide
			var guide = jQuery( '<div></div>' );

			var guideTop = function() {
				if (lastRow) {
					return cell.offset().top + cell.outerHeight();
				} else {
					return cell.offset().top;
				}
			};

			var width = cell.closest( 'tbody' ).innerWidth();
			var height = cell.outerHeight() - cell.innerHeight();

			guide.css({
				'width': (width < 1) ? 1 : width,
				'height': (height < 1) ? 1: height,
				'top': guideTop(),
				'left': cell.closest( 'tbody' ).offset().left,
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery( 'body' ).append( guide );

			// set the minimum resize
			var minHeight = function() {
				if (lastRow) {
					return cell.closest('tr').offset().top;
				} else {
					return cell.closest('tr').prev( 'tr' ).offset().top;
				}
			};

			// set the selection resize mode
			that.selection.resizeMode = true;

			// move the guide while dragging
			jQuery( 'body' ).bind( 'mousemove.dnd_row_resize', function(e) {
				if ( e.pageY > minHeight() ) {
					guide.css( 'top', e.pageY );
				}
			});

			// do the actual resizing after drag stops
			jQuery( 'body' ).bind( 'mouseup.dnd_row_resize', function(e) {

				var pixelsMoved = 0;

				if (lastRow) {
					pixelsMoved = e.pageY - ( cell.offset().top + cell.outerHeight() );
				} else {
					pixelsMoved = e.pageY - cell.offset().top;
				}

				resizeRows( pixelsMoved );

				jQuery( 'body' ).unbind( 'mousemove.dnd_row_resize' );
				jQuery( 'body' ).unbind( 'mouseup.dnd_row_resize' );

				// unset the selection resize mode
				that.selection.resizeMode = false;

				guide.remove();
			});

		});

	};

	/**
	 * Attach the table width resize event.
	 * @param {DOMElement} table
	 *
	 * @return void
	 */
	Table.prototype.attachTableResizeWidth = function(table) {

		var that = this;
		var tableContainer = table.closest('.aloha-table-wrapper');
		var trSelector = "tr:not(.aloha-table-selectcolumn)";
		var lastColumn = table.find(trSelector + " th:last-child, " +
			                        trSelector + " td:last-child");
		var lastCell;

		jQuery.each( lastColumn, function() {
			// don't use colspanned cell as the base cell
			if ( !jQuery( this ).attr('colspan') || jQuery( this ).attr('colspan') < 2 ) {
				lastCell = jQuery( this );
				return false;
			}
		});

		// change the cursor
		lastColumn.css( 'cursor', 'col-resize' );

		var resizeColumns = function(pixelsMoved) {
			var rows = table.find( 'tr' );
			var lastCellRow = lastCell.closest( 'tr' );
			var gridId = Utils.cellIndexToGridColumn(
				                   rows,
			                      rows.index( lastCellRow ),
			                      lastCellRow.children().index( lastCell )
			                   );
			var expandToWidth = pixelsMoved - Utils.getCellBorder(lastCell) - Utils.getCellPadding(lastCell);

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] )

				// skip the select cells and cells with colspans
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) {
					return true;
				}

				if (gridCi === gridId ) {
					Utils.resizeCellWidth( currentCell, expandToWidth );
				} else {
					Utils.resizeCellWidth( currentCell, currentCell.width() );
				}

				return true;
			});
		};

		lastColumn.bind( 'mousedown.resize', function() {

			// create a guide
			var guide = jQuery( '<div></div>' );

			var height = table.children( 'tbody' ).innerHeight();
			var width = lastCell.outerWidth() - lastCell.innerWidth();

			guide.css({
				'height': (height < 1) ? 1 : height,
				'width': (width < 1) ? 1 : width,
				'top': table.find('tbody').offset().top,
				'left': table.offset().left + table.outerWidth(),
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery('body').append( guide );

			// set the maximum and minimum resize
			var maxPageX = tableContainer.offset().left + tableContainer.width();
			var minPageX = lastCell.offset().left + ( lastCell.innerWidth() - lastCell.width() ) + Utils.getMinColWidth( lastCell );

			// unset the selection type
			that.selection.resizeMode = true;

			// move the guide while dragging
			jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) {
				// limit the maximum resize
				if ( e.pageX > minPageX && e.pageX < maxPageX ) {
					guide.css( 'left', e.pageX );
				}
			});

			// do the actual resizing after drag stops
			jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) {
				var pixelsMoved = 0;

				if ( e.pageX <= minPageX ) {
				 	pixelsMoved = minPageX - lastCell.offset().left;
				} else if ( e.pageX > minPageX && e.pageX < maxPageX ) {
					pixelsMoved = e.pageX - lastCell.offset().left;
				} else if ( e.pageX > maxPageX ) {
				  pixelsMoved = maxPageX - lastCell.offset().left;
				}

				// set the table width
				resizeColumns( pixelsMoved );

				// unbind the events and reset the cursor
				jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' );
				jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' );
				lastColumn.unbind( 'mousedown.resize' );
				lastColumn.css( 'cursor', 'default' );

				// unset the selection resize mode
				that.selection.resizeMode = false;

				guide.remove();
			});

		});

	};

	/**
	 * Detach any column/row resize event handlers attached to the cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.detachRowColResize = function(cell) {
		return cell.unbind('mousedown.resize');
	};

	/**
	 * toString-method for Table object
	 *
	 * @return void
	 */
	Table.prototype.toString = function() {
		return 'Table';
	};

	Table.prototype.newCell = function(domElement) {
		return new TableCell(domElement, this);
	};

	Table.prototype.newActiveCell = function(domElement) {
		var cell = new TableCell(domElement, this);
		cell.activate();
		return cell;
	};

	/**
	 * @return the rows of the table as an array of DOM nodes
	 */
	Table.prototype.getRows = function () {
		//W3C DOM property .rows supported by all modern browsers
		var rows = this.obj.get( 0 ).rows;
		//converts the HTMLCollection to a real array
		return jQuery.makeArray( rows );
	};

	return Table;
});

/* table-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('table/table-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'aloha/pluginmanager',
	'ui/ui',
	'ui/scopes',
	'ui/button',
	'ui/toggleButton',
	'ui/dialog',
	'ui/port-helper-attribute-field',
	'ui/port-helper-multi-split',
	'i18n!table/nls/i18n',
	'i18n!aloha/nls/i18n',
	'table/table-create-layer',
	'table/table',
	'table/table-plugin-utils',
	'util/dom',
	'aloha/ephemera',
	'aloha/console'
], function(
	Aloha,
	jQuery,
	Plugin,
	PluginManager,
	Ui,
	Scopes,
	Button,
	ToggleButton,
	Dialog,
	AttributeField,
	MultiSplitButton,
	i18n,
	i18nCore,
	CreateLayer,
	Table,
	Utils,
	Dom,
	Ephemera,
	Console
) {
	var $ = jQuery;
	var GENTICS = window.GENTICS;

	/**
	 * Register the TablePlugin as Aloha.Plugin
	 */
	var TablePlugin = new Plugin('table');

	/**
	 * The Create-Layer Object of the TablePlugin
	 *
	 * @see Table.CreateLayer
	 */
	TablePlugin.createLayer = undefined;

	/**
	 * default button configuration
	 */
	TablePlugin.config = [ 'table' ];

	/**
	 * An Array which holds all newly created tables contains DOM-Nodes of
	 * table-objects
	 */
	TablePlugin.TableRegistry = new Array();

	/**
	 * Holds the active table-object
	 */
	TablePlugin.activeTable = undefined;

	/**
	 * parameters-objects for tables
	 *
	 * @param className
	 *            The class of activated tables
	 */
	TablePlugin.parameters = {
		className            : 'aloha-table',                 // class of editable tables
		classSelectionRow    : 'aloha-table-selectcolumn',    // class for the upper table-row to select columns
		classSelectionColumn : 'aloha-table-selectrow',       // class for the left bound table-cells to select rows
		classLeftUpperCorner : 'aloha-table-leftuppercorner', // class for the left upper corner cell
		classTableWrapper    : 'aloha-table-wrapper',         // class of the outest table-wrapping div
		classCellSelected    : 'aloha-cell-selected',         // class of cell which are selected (row/column selection)
		waiRed               : 'aloha-wai-red',               // class that shows wai of div
		waiGreen             : 'aloha-wai-green',             // class that shows wai of div
		selectionArea        : 10                             // width/height of the selection rows (in pixel)
	};

	/**
	 * @hide
	 * {name:'green', text:'Green',tooltip:'Green',iconClass:'GENTICS_table GENTICS_button_green',cssClass:'green'}
	 */
	TablePlugin.checkConfig = function (c){
		if (typeof c == 'object' && c.length) {
			var newC = [];

			for (var i = 0; i < c.length; i++) {
				if (c[i]) {
					newC.push({
						name      : c[i].name,
						text	  : c[i].text	   ? c[i].text		: c[i].name,
						tooltip	  : c[i].tooltip   ? c[i].tooltip	: c[i].text,
						iconClass : c[i].iconClass ? c[i].iconClass	: 'aloha-icon-' + c[i].name,
						cssClass  : c[i].cssClass  ? c[i].cssClass	: c[i].name
					});
				}
			}

			c = newC;
		} else {
			c = [];
		}

		return c;
	};

	/**
	 * Update the WAI image
	 *
	 * @param {TablePlugin} tablePlugin tablePlugin
	 */
	function updateWaiImage(tablePlugin) {
		var $element = $(tablePlugin.summary.getInputElem()),
			waiRed = tablePlugin.activeTable.get('waiRed'),
			waiGreen = tablePlugin.activeTable.get('waiGreen');

		$element.removeClass(waiRed + ' ' + waiGreen);
		if (tablePlugin.activeTable.checkWai()) {
			$element.addClass(waiGreen);
		}
		else {
			$element.addClass(waiRed);
		}
	}

	/**
	 * Checks whether the given DOM element is nested within a table.
	 *
	 * @param {jQuery.<HTMLElement>} $element
	 * @return {boolean} True if the given element is nested in a table.
	 */
	function isWithinTable($element) {
		return 0 < $element.parents('.aloha-editable table').length;
	}

	/**
	 * Checks whether the given DOM element is nested within an aloha block.
	 *
	 * @param {jQuery.<HTMLElement>} $element
	 * @return {boolean} True if the given element is nested in an aloha block.
	 */
	function isWithinBlock($element) {
		var i;
		var $node;
		var $parents = $element.parents();
		for (i = 0; i < $parents.length; i++) {
			$node = $parents.eq(i);
			if ($node.is('.aloha-editable')) {
				return false;
			}
			if ($node.is('.aloha-block')) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks whether the table is an editable element.
	 *
	 * @return {boolean} True if the table's parent element is contentEditable;
	 *                   false otherwise.
	 */
	function isEditableTable(table) {
		return GENTICS.Utils.Dom.isEditable(table);
	}

	/**
	 * Checks for the presence of nested tables in the given element.
	 *
	 * @param {jQuery.<HTMLElement>} $element jQuery unit set containing a DOM
	 *                                        element.
	 * @return {boolean} True if nested tables were detected; false otherwise.
	 */
	function checkForNestedTables($element) {
		var selector = $element.is('table') ? 'table' : 'table table';
		if ($element.find(selector).length) {
			Console.warn('Table Plugin',
					'Nested tables found. They will not be initialized.');
			return true;
		}
		return false;
	}

	/**
	 * Creates a table, if it is allowed, and registers a new Table object for
	 * the given table DOM element.
	 *
	 * If the table's editable parent is activated, the table will also
	 * automatically be activated.
	 *
	 * @param {HTMLElement} element HTML table element.
	 * @return {Table|null} The created Table object or null if it was not
	 *                      allowed to create the table.
	 */
	function createNewTable(element) {
		var $table = $(element);
		var create = isEditableTable(element)
		          && !isWithinTable($table)
		          && !isWithinBlock($table);
		if (create) {
			var table = new Table(element, TablePlugin);
			var $host = $(Dom.getEditingHostOf(element));
			table.parentEditable = Aloha.getEditableById($host.attr('id'));
			TablePlugin.TableRegistry.push(table);
			checkForNestedTables($table);
			if (Aloha.activeEditable === table.parentEditable) {
				table.activate();
			}
			return table;
		}
		return null;
	}

	/**
	 * Sets the currently selected elements as headers of the table, or removes header-status
	 * if the whole selection is already used as a header
	 *
	 * @param {Aloha.Table} table the table-object for which the headers are to be set
	 * @param {string} scope for which the header should be used (i.e. 'row' or 'column')
	 */
	function toggleHeaderStatus(table, scope) {
		var	i,
			j,
			allHeaders = table.selection.isHeader(),
			domCell, // representation of the cell in the dom
			tableCell, // table-cell object
			bufferCell; // temporary buffer

		for (i = 0; i < table.selection.selectedCells.length; i++) {
			domCell = table.selection.selectedCells[i];

			// tries to match the current cell with a cell-object in the table
			for (j = 0; j < table.cells.length; j++) {
				if (domCell === table.cells[j].obj[0]) {
					cell = table.cells[j];
					break;
				}
			}

			// the transformed dom objects are first stored in a buffer, and only applied to
			// the table-cell-object if a match was found
			if (allHeaders) {
				bufferCell = Aloha.Markup.transformDomObject(domCell, 'td').removeAttr('scope').get(0);
			} else {
				bufferCell = Aloha.Markup.transformDomObject(domCell, 'th').attr('scope', scope).get(0);
			}

			if (cell != null) {
				// assign the changed dom-element to the table-cell
				cell.obj[0] = bufferCell;

				// reactivate the table cell in order to bind events to the changed dom object
				// TODO: re-attaching event-handlers should be factored out into a utility function
				// so we don't have to do the whole activation/deactivation process for the cells
				cell.deactivate();
				cell.activate();
			}

			// uncommented code-segment, presumably added to force IE to target the wrapper
			// on mouse-down by applying a timeout after event propagation
			jQuery(table.selection.selectedCells[i]).bind('mousedown', function (jqEvent) {
				var wrapper = jQuery(this).children('div').eq(0);
				window.setTimeout(function () {
					wrapper.trigger( 'focus' );
				}, 1);
			});
		}
	}

	/**
	 * If the specified style is not already active in all selected cells, it is applied;
	 * otherwise, it is removed from the cells
	 *
	 * @param {Array} config defined styles as defined in the configuration
	 * @param {String} cssClass
	 * @param {Array} sc the selection of target table cells
	 */
	function applyStyle(config, cssClass, sc) {
		var appliedToAll = true;

		for (var i = 0; i < sc.length; i++) {
			if (jQuery(sc[i]).attr('class').indexOf(cssClass) < 0 ) {
				appliedToAll = false;
				break;
			}
		}

		if (!appliedToAll) {
			for (var i = 0; i < sc.length; i++) {
				jQuery(sc[i]).addClass(cssClass);
				for (var f = 0; f < config.length; f++) {
					if (config[f].cssClass != cssClass) {
						jQuery(sc[i]).removeClass(config[f].cssClass);
					}
				}
			}
		} else {
			for (var i = 0; i < sc.length; i++) {
				jQuery(sc[i]).removeClass(cssClass);
			}
		}
	}

	/**
	 * Init method of the Table-plugin transforms all tables in the document
	 *
	 * @return void
	 */
	TablePlugin.init = function() {
		var that = this,
		    isEnabled = {};

		// register ephemeral classes
		Ephemera.classes(this.get('className'), this.get('classCellSelected'));

		// apply settings
		this.tableConfig = this.checkConfig(this.tableConfig||this.settings.tableConfig);
		this.columnConfig = this.checkConfig(this.columnConfig||this.settings.columnConfig);
		this.rowConfig = this.checkConfig(this.rowConfig||this.settings.rowConfig);
		this.cellConfig = this.checkConfig(this.cellConfig||this.settings.cellConfig);

		// table resize settings
		this.tableResize = this.settings.tableResize === undefined ? false : this.settings.tableResize;
		this.colResize = this.settings.colResize === undefined ? false : this.settings.colResize;
		this.rowResize = this.settings.rowResize === undefined ? false : this.settings.rowResize;

		// disable table resize settings on browsers below IE8
		if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) {
			this.tableResize = false;
			this.colResize = false;
			this.rowResize = false;
		}

		// add reference to the create layer object
		this.createLayer = new CreateLayer( this );

		// subscribe for the 'editableActivated' event to activate all tables in the editable
		Aloha.bind( 'aloha-editable-created', function (event, editable) {
			var config = that.getEditableConfig(editable.obj);
			isEnabled[editable.getId()] = (-1 !== jQuery.inArray('table', config));

			// add a mousedown event to all created editables to check if focus leaves a table
			editable.obj.bind( 'mousedown', function ( jqEvent ) {
				TablePlugin.setFocusedTable( undefined );
			} );

			editable.obj.find('table').each(function (__unused__, elem) {
				createNewTable(elem);
			});
		} );

		// initialize the table buttons
		this.initTableButtons();

		Aloha.bind( 'aloha-table-selection-changed', function () {

			// check if selected cells are split/merge able and set button status
			if ( typeof TablePlugin.activeTable !== 'undefined' &&
				TablePlugin.activeTable.selection ) {

				TablePlugin.updateFloatingMenuScope();

				if ( TablePlugin.activeTable.selection.cellsAreSplitable() ) {
					that._splitcellsButton.enable(true);
					that._splitcellsRowButton.enable(true);
					that._splitcellsColumnButton.enable(true);
				} else {
					that._splitcellsButton.enable(false);
					that._splitcellsRowButton.enable(false);
					that._splitcellsColumnButton.enable(false);
				}

				if ( TablePlugin.activeTable.selection.cellsAreMergeable() ) {
					that._mergecellsButton.enable(true);
					that._mergecellsRowButton.enable(true);
					that._mergecellsColumnButton.enable(true);
				} else {
					that._mergecellsButton.enable(false);
					that._mergecellsRowButton.enable(false);
					that._mergecellsColumnButton.enable(false);
				}
			}

		});

		Aloha.bind( 'aloha-selection-changed', function (event, rangeObject) {
			// this case probably occurs when the selection is empty?
			if (!rangeObject.startContainer || !Aloha.activeEditable) {
				return;
			}

			// show hide buttons regarding configuration and DOM position
			if (isEnabled[Aloha.activeEditable.getId()] && Aloha.Selection.mayInsertTag('table') ) {
				that._createTableButton.show();
			} else {
				that._createTableButton.hide();
			}

			if (!that.activeTable) {
				return;
			}

			// check wheater we are inside a table
			var table = rangeObject.findMarkup(function() {
				return this.nodeName === 'TABLE';
			}, Aloha.activeEditable.obj);
			if (table) {
				TablePlugin.updateFloatingMenuScope();
				TablePlugin.setActiveCellStyle();
			} else {
				that.activeTable.selection.cellSelectionMode = false;
				that.activeTable.selection.baseCellPosition = null;
				that.activeTable.selection.lastSelectionRange = null;
				that.activeTable.focusOut();
			}
		});

		Aloha.bind('aloha-editable-activated', function (__event__, data) {
			that._splitcellsButton.enable(false);
			that._mergecellsButton.enable(false);
			that._splitcellsRowButton.enable(false);
			that._mergecellsRowButton.enable(false);
			that._splitcellsColumnButton.enable(false);
			that._mergecellsColumnButton.enable(false);

			data.editable.obj.find('table').each(function () {
				var registry = TablePlugin.TableRegistry;
				for (var i = 0; i < registry.length; i++) {
					if (registry[i].obj.attr('id') === jQuery(this).attr('id')) {
						registry[i].activate();
						return true;
					}
				}

				// Because table this is a new table that is not yet in the
				// registry.
				createNewTable(this);
			});

		});

		Aloha.bind('aloha-editable-deactivated', function () {
			if (TablePlugin.activeTable) {
				TablePlugin.activeTable.selection.unselectCells();
			}
			TablePlugin.setFocusedTable(undefined);
			var registry = TablePlugin.TableRegistry;
			for (var i = 0; i < registry.length; i++) {
				registry[i].deactivate();
			}
		});

		Aloha.bind('aloha-smart-content-changed', function () {
			if (Aloha.activeEditable) {
				Aloha.activeEditable.obj.find('table').each(function () {
					if (TablePlugin.indexOfTableInRegistry(this) == -1) {
						if (createNewTable(this)) {
							this.id = GENTICS.Utils.guid();
						}
					}
				});
			}
		});

		if (this.settings.summaryinsidebar) {
			Aloha.bind('aloha-plugins-loaded', function () {
				that.initSidebar(Aloha.Sidebar.right.show());
			});
		}
	};

	//namespace prefix for this plugin
	var tableNamespace = 'aloha-table';

	function nsSel () {
		var stringBuilder = [], prefix = tableNamespace;
		jQuery.each(arguments, function () { stringBuilder.push('.' + (this == '' ? prefix : prefix + '-' + this)); });
		return jQuery.trim(stringBuilder.join(' '));
	};

	//Creates string with this component's namepsace prefixed the each classname
	function nsClass () {
		var stringBuilder = [], prefix = tableNamespace;
		jQuery.each(arguments, function () { stringBuilder.push(this == '' ? prefix : prefix + '-' + this); });
		return jQuery.trim(stringBuilder.join(' '));
	};

	TablePlugin.initSidebar = function(sidebar) {
		var pl = this;
		pl.sidebar = sidebar;
		pl.sidebarPanel = sidebar.addPanel({

            id       : nsClass('sidebar-panel'),
            title    : i18n.t('table.sidebar.title'),
            content  : '',
            expanded : true,
            activeOn : 'table',

            onInit   : function () {
            	var that = this,
	            content = this.setContent(
	                '<label class="' + nsClass('label') + '" for="' + nsClass('textarea') + '" >' + i18n.t('table.label.target') + '</label>' +
	                	'<textarea id="' + nsClass('textarea') + '" class="' + nsClass('textarea') + '" />').content;

            	jQuery(nsSel('textarea')).live('keyup', function() {
					//The original developer thought that escaping the
					//quote characters of the textarea value are
					//necessary to work around a bug in IE. I could not
					//reproduce the bug, so I commented the following
					//out.
					//.replace("\"", '&quot;').replace("'", "&#39;")
 					jQuery(that.effective).attr('summary', jQuery(nsSel('textarea')).val());
 					var waiDiv = jQuery('div[class*="wai"]', 'table#' + jQuery(that.effective).attr('id'));
 					waiDiv.removeClass(pl.get('waiGreen'));
 					waiDiv.removeClass(pl.get('waiRed'));

 					if (jQuery(nsSel('textarea')).val().trim() != '') {
 						waiDiv.addClass(pl.get('waiGreen'));
				    } else {
				    	waiDiv.addClass(pl.get('waiRed'));
				    }
 				});
            },

            onActivate: function (effective) {
            	var that = this;
				that.effective = effective;
				jQuery(nsSel('textarea')).val(jQuery(that.effective).attr('summary'));
            }

        });
		sidebar.show();
	};

	/**
	 * test if the table is editable
	 * @return boolean true if the table's parent element is contentEditable, false otherwise
	 */
	TablePlugin.isEditableTable = function (table) {
		return GENTICS.Utils.Dom.isEditable( table );
	};

	/**
	 * @param {DOMElement} table
	 * @return {Number}
	 */
	TablePlugin.indexOfTableInRegistry = function ( table ) {
		var registry = this.TableRegistry;

		for ( var i = 0; i < registry.length; i++ ) {
			// We need to find exactly the same object from the
			// registry since we could also deal with cloned objects
			if ( registry[ i ].obj[ 0 ].id == table.id ) {
				return i;
			}
		}

		return -1;
	};

	/**
	 * @param {DOMElement} table
	 * @return {Table}
	 */
	TablePlugin.getTableFromRegistry = function ( table ) {
		var i = this.indexOfTableInRegistry( table );
		if ( i > -1 ) {
			return this.TableRegistry[ i ];
		}
		return null;
	};

	/**
	 * Checks whether the current selection is inside a table within an
	 * editable
	 *
	 * @return {Boolean} true if we are inside a table
	 */
	TablePlugin.isSelectionInTable = function () {
		var range = Aloha.Selection.getRangeObject();
		var container = jQuery( range.commonAncestorContainer );

		if ( container.length == 0 ) {
			return  false;
		}

		if ( container.parents( '.aloha-editable table' ).length ) {
			return true;
		}

		return false;
	};

	TablePlugin.preventNestedTables = function () {
		if ( this.isSelectionInTable() ) {
			Dialog.alert({
				title : i18n.t( 'Table' ),
				text  : i18n.t( 'table.createTable.nestedTablesNoSupported' )
			});

			return true;
		}

		return false;
	};

	TablePlugin.initMergeSplitCellsBtns = function(){
		// TODO current it is not possible to add the same buttons to
		//      multiple tabs. To work around this limitation we are
		//      defining the mergecells and splitcells components
		//      multiple times, once for each tab.

		this._mergecellsButton = Ui.adopt("mergecells", Button, {
			tooltip: i18n.t("button.mergecells.tooltip"),
			icon: "aloha-icon aloha-icon-mergecells",
			scope: this.name + '.cell',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.mergeCells();
				}
			}
		});

		this._splitcellsButton = Ui.adopt("splitcells", Button, {
			tooltip: i18n.t("button.splitcells.tooltip"),
			icon: "aloha-icon aloha-icon-splitcells",
			scope: this.name + '.cell',
			click: function() {
				var activeCell;
				if (TablePlugin.activeTable) {
					if (TablePlugin.activeTable.selection.selectedCells.length > 0) {
						TablePlugin.activeTable.selection.splitCells();
					} else {
						// if there is currently no selection, the active cell is split instead
						activeCell = TablePlugin.selectedOrActiveCells();
						if (activeCell.length > 0) {
							Utils.splitCell(activeCell, function () {
								return TablePlugin.activeTable.newActiveCell().obj;
							});
							Aloha.trigger('aloha-table-selection-changed');
						}
					}
				}
			}
		});

		this._mergecellsRowButton = Ui.adopt("mergecellsRow", Button, {
			tooltip: i18n.t("button.mergecells.tooltip"),
			icon: "aloha-icon aloha-icon-mergecells",
			scope: this.name + '.row',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.mergeCells();
				}
			}
		});

		this._splitcellsRowButton = Ui.adopt("splitcellsRow", Button, {
			tooltip: i18n.t("button.splitcells.tooltip"),
			icon: "aloha-icon aloha-icon-splitcells",
			scope: this.name + '.row',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.splitCells();
				}
			}
		});

		this._mergecellsColumnButton = Ui.adopt("mergecellsColumn", Button, {
			tooltip: i18n.t("button.mergecells.tooltip"),
			icon: "aloha-icon aloha-icon-mergecells",
			scope: this.name + '.column',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.mergeCells();
				}
			}
		});

		this._splitcellsColumnButton = Ui.adopt("splitcellsColumn", Button, {
			tooltip: i18n.t("button.splitcells.tooltip"),
			icon: "aloha-icon aloha-icon-splitcells",
			scope: this.name + '.column',
			click: function() {
				if (TablePlugin.activeTable) {
					TablePlugin.activeTable.selection.splitCells();
				}
			}
		});
	};

	TablePlugin.initNaturalFitBtn = function() {
		var that = this;

		if (this.colResize || this.rowResize) {
			this._tableNaturalFitButton = Ui.adopt("naturalFit", Button, {
				tooltip: i18n.t("button.naturalfit.tooltip"),
				icon: "aloha-icon aloha-icon-table-naturalfit",
				scope: this.name + '.cell',
				click: function() {
					if (that.activeTable) {
						var tableObj = that.activeTable.obj;
						tableObj.find('td, th').each(function() {
							jQuery(this).find('div').css('width', '');
							jQuery(this).css('width', '');
						});
						tableObj.find('tr').each(function() {
							jQuery(this).css('height', '');
						});
					}
				}
			});
		}
	};

	/**
	 * Adds default row buttons, and custom formatting buttons to floating menu
	 */
	TablePlugin.initRowsBtns = function () {
		var that = this;

		this._addrowbeforeButton = Ui.adopt("addrowbefore", Button, {
			tooltip: i18n.t( "button.addrowbefore.tooltip"),
			icon: "aloha-icon aloha-icon-addrowbefore",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addRowBeforeSelection();
				}
			}
		});

		this._addrowafterButton = Ui.adopt("addrowafter", Button, {
			tooltip: i18n.t("button.addrowafter.tooltip"),
			icon: "aloha-icon aloha-icon-addrowafter",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addRowAfterSelection();
				}
			}
		});

		this._deleterowsButton = Ui.adopt("deleterows", Button, {
			tooltip: i18n.t("button.delrows.tooltip"),
			icon: "aloha-icon aloha-icon-deleterows",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					var aTable = that.activeTable;
					Dialog.confirm({
						title: i18n.t('Table'),
						text: i18n.t('deleterows.confirm'),
						yes: function(){
							aTable.deleteRows();
						}
					});
				}
			}
		});

		this._rowheaderButton = Ui.adopt("rowheader", ToggleButton, {
			tooltip: i18n.t("button.rowheader.tooltip"),
			icon: "aloha-icon aloha-icon-rowheader",
			scope: this.name + '.row',
			click: function() {
				if (that.activeTable) {
					that.activeTable.refresh();

					toggleHeaderStatus(that.activeTable, 'col');

					that.activeTable.selection.unselectCells();
					that.activeTable.selection.selectRows(that.activeTable.selection.selectedRowIdxs);
				}
			}
		});

		// generate formatting buttons
		this.rowMSItems = [];
		jQuery.each(this.rowConfig, function (j, itemConf) {
			that.rowMSItems.push({
				name: itemConf.name,
				text: i18n.t(itemConf.text),
				tooltip: i18n.t(itemConf.tooltip),
				iconClass: 'aloha-icon aloha-row-layout ' + itemConf.iconClass,
				click: function () {
					if (that.activeTable) {
						applyStyle(that.rowConfig, itemConf.cssClass, that.activeTable.selection.selectedCells);

						// selection could have changed.
						that.activeTable.selectRows();
					}
				}
			});
		});

		if (this.rowMSItems.length > 0) {
			this.rowMSItems.push({
				name    : 'removeFormat',
				text    : i18n.t('button.removeFormat.text'),
				tooltip : i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide    : true,
				click   : function () {
					if (that.activeTable) {
						var sc = that.activeTable.selection.selectedCells;
						// if a selection was made, transform the selected cells
						for (var i = 0; i < sc.length; i++) {
							for (var f = 0; f < that.rowConfig.length; f++) {
								jQuery(sc[i]).removeClass(that.rowConfig[f].cssClass);
							}
						}
						// selection could have changed.
						that.activeTable.selectRows();
					}
				}
			});
		}

		this.rowMSButton = MultiSplitButton({
			items: this.rowMSItems,
			name: 'formatRow',
			hideIfEmpty: true,
			scope: this.name + '.row'
		});
	};

	/**
	 * Adds default column buttons, and custom formatting buttons to floating menu
	 */
	TablePlugin.initColumnBtns = function () {
		var that = this;

		this._addcolumnleftButton = Ui.adopt("addcolumnleft", Button, {
			tooltip: i18n.t("button.addcolleft.tooltip"),
			icon: "aloha-icon aloha-icon-addcolumnleft",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addColumnsLeft();
				}
			}
		});

		this._addcolumnrightButton = Ui.adopt("addcolumnright", Button, {
			tooltip: i18n.t("button.addcolright.tooltip"),
			icon: "aloha-icon aloha-icon-addcolumnright",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					that.activeTable.addColumnsRight();
				}
			}
		});

		this._deletecolumnsButton = Ui.adopt("deletecolumns", Button, {
			tooltip: i18n.t("button.delcols.tooltip"),
			icon: "aloha-icon aloha-icon-deletecolumns",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					var aTable = that.activeTable;
					Dialog.confirm({
						title: i18n.t('Table'),
						text: i18n.t('deletecolumns.confirm'),
						yes: function(){
							aTable.deleteColumns();
						}
					});
				}
			}
		});

		this._columnheaderButton = Ui.adopt("columnheader", ToggleButton, {
			tooltip: i18n.t("button.columnheader.tooltip"),
			icon: "aloha-icon aloha-icon-columnheader",
			scope: this.name + '.column',
			click: function() {
				if (that.activeTable) {
					that.activeTable.refresh();

					toggleHeaderStatus(that.activeTable, 'row');

					that.activeTable.selection.unselectCells();
					that.activeTable.selection.selectColumns(that.activeTable.selection.selectedColumnIdxs);
				}
			}
		});

		// generate formatting buttons
		this.columnMSItems = [];
		jQuery.each(this.columnConfig, function (j, itemConf) {
			var item = {
				name	  : itemConf.name,
				text	  : i18n.t(itemConf.text),
				tooltip	  : i18n.t(itemConf.tooltip),
				iconClass : 'aloha-icon aloha-column-layout ' + itemConf.iconClass,
				click	  : function (x,y,z) {
					if (that.activeTable) {
						applyStyle(that.columnConfig, itemConf.cssClass, that.activeTable.selection.selectedCells);

						// selection could have changed.
						that.activeTable.selectColumns();
					}
				}
			};

			that.columnMSItems.push(item);
		});

		if (this.columnMSItems.length > 0) {
			this.columnMSItems.push({
				name	: 'removeFormat',
				text	: i18n.t('button.removeFormat.text'),
				tooltip	: i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide	: true,
				click	: function () {
					if (that.activeTable) {
						var sc = that.activeTable.selection.selectedCells;
						// if a selection was made, transform the selected cells
						for (var i = 0; i < sc.length; i++) {
							for (var f = 0; f < that.columnConfig.length; f++) {
								jQuery(sc[i]).removeClass(that.columnConfig[f].cssClass);
							}
						}
						// selection could have changed.
						that.activeTable.selectColumns();
					}
				}
			});
		}

		this.columnMSButton = MultiSplitButton({
			items: this.columnMSItems,
			name: 'formatColumn',
			hideIfEmpty: true,
			scope: this.name + '.column'
		});
	};

	/**
	 * Adds custom formatting buttons for cells to floating menu
	 */
	TablePlugin.initCellBtns = function () {
		var that = this;

		// generate formatting buttons
		this.cellMSItems = [];
		jQuery.each(this.cellConfig, function (j, itemConf) {
			var item = {
				name	  : itemConf.name,
				text	  : i18n.t(itemConf.text),
				tooltip	  : i18n.t(itemConf.tooltip),
				iconClass : 'aloha-icon aloha-column-layout ' + itemConf.iconClass,
				click	  : function (x,y,z) {
					if (that.activeTable) {
						applyStyle(that.cellConfig, itemConf.cssClass, that.selectedOrActiveCells());

						that.setActiveCellStyle();
					}
				}
			};

			that.cellMSItems.push(item);
		});

		if (this.cellMSItems.length > 0) {
			this.cellMSItems.push({
				name	: 'removeFormat',
				text	: i18n.t('button.removeFormat.text'),
				tooltip	: i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide	: true,
				click	: function () {
					if (that.activeTable) {
						var sc = that.selectedOrActiveCells();
						// if a selection was made, transform the selected cells
						for (var i = 0; i < sc.length; i++) {
							for (var f = 0; f < that.cellConfig.length; f++) {
								jQuery(sc[i]).removeClass(that.cellConfig[f].cssClass);
							}
						}

						that.setActiveCellStyle();
					}
				}
			});
		}

		this.cellMSButton = MultiSplitButton({
			items: this.cellMSItems,
			name: 'formatCell',
			hideIfEmpty: true,
			scope: this.name + '.cell'
		});
	};


	/**
	 * initialize the buttons and register them on floating menu
	 */
	TablePlugin.initTableButtons = function () {
		var that = this;

		// generate the new scopes
		Scopes.createScope(this.name + '.row', 'Aloha.continuoustext');
		Scopes.createScope(this.name + '.column', 'Aloha.continuoustext');
		Scopes.createScope(this.name + '.cell', 'Aloha.continuoustext');

		this._createTableButton = Ui.adopt("createTable", Button, {
			tooltip: i18n.t("button.createtable.tooltip"),
			icon: "aloha-icon aloha-icon-createTable",
			scope: 'Aloha.continuoustext',
			click: function() {
				TablePlugin.createDialog(this.element);
			}
		});

		// now the specific table buttons

		// generate formatting buttons for columns
		this.initColumnBtns();

		// generate formatting buttons for rows
		this.initRowsBtns();

		// generate formatting buttons for cells
		this.initCellBtns();

		this.initMergeSplitCellsBtns();

		this.initNaturalFitBtn();

		// generate formatting buttons for tables
		this.tableMSItems = [];

		var tableConfig = this.tableConfig;

		jQuery.each(tableConfig, function(j, itemConf){
			that.tableMSItems.push({
				name: itemConf.name,
				text: i18n.t(itemConf.text),
				tooltip: i18n.t(itemConf.tooltip),
				iconClass: 'aloha-icon aloha-table-layout ' + itemConf.iconClass,
				click: function(){
					// set table css class
					if (that.activeTable) {
						if (!that.activeTable.obj.hasClass(itemConf.cssClass)) {
							for (var f = 0; f < tableConfig.length; f++) {
								that.activeTable.obj.removeClass(tableConfig[f].cssClass);
							}
							that.activeTable.obj.addClass(itemConf.cssClass);
							that.tableMSButton.setActiveItem(itemConf.cssClass);
						} else {
							for (var f = 0; f < tableConfig.length; f++) {
								that.activeTable.obj.removeClass(tableConfig[f].cssClass);
							}
							that.tableMSButton.setActiveItem();
						}
					}
				}
			});
		});

		if(this.tableMSItems.length > 0) {
			this.tableMSItems.push({
				name    : 'removeFormat',
				text    : i18n.t('button.removeFormat.text'),
				tooltip : i18n.t('button.removeFormat.tooltip'),
				'cls'   : 'aloha-ui-multisplit-fullwidth',
				wide    : true,
				click   : function () {
					// remove all table classes
					if (that.activeTable) {
						for (var f = 0; f < tableConfig.length; f++) {
							that.activeTable.obj.removeClass(that.tableConfig[f].cssClass);
						}
						that.tableMSButton.setActiveItem();
					}
				}
			});
		}

		this.tableMSButton = MultiSplitButton({
			items : this.tableMSItems,
			name : 'formatTable',
			hideIfEmpty: true,
			scope: this.name + '.cell'
		});

		this._tableCaptionButton = Ui.adopt("tableCaption", ToggleButton, {
			tooltip: i18n.t("button.caption.tooltip"),
			icon: "aloha-icon aloha-icon-table-caption",
			scope: this.name + '.cell',
			click: function() {
				if (that.activeTable) {
					// look if table object has a child caption
					var $caption = that.activeTable.obj.children("caption");

					if ( $caption.is('caption') && $caption.is(':visible') ) {
						$caption.hide();
					} else {
						if (!$caption.is('caption')) {
							$caption = jQuery('<caption></caption>');
							that.activeTable.obj.prepend($caption);
						}
						$caption.show();
						if (jQuery.trim($caption.text()).length === 0) {
							$caption.text(i18n.t('empty.caption'));
						}

						that.makeCaptionEditable($caption, $caption.text());

						// get the editable span within the caption and select it
						var cDiv = $caption.find('div').eq(0);
						var captionContent = cDiv.contents().eq(0);
						if (captionContent.length > 0) {
							var newRange = new GENTICS.Utils.RangeObject();
							newRange.startContainer = newRange.endContainer = captionContent.get(0);
							newRange.startOffset = 0;
							newRange.endOffset = captionContent.text().length;

							// blur all editables within the table
							that.activeTable.obj.find('div.aloha-table-cell-editable').blur();

							cDiv.focus();
							newRange.select();
							Aloha.Selection.updateSelection();
						}
					}
				}
			}
		});

		this.summary = AttributeField( {
			width : 275,
			name  : 'tableSummary',
			noTargetHighlight: true,
			scope: this.name + '.cell',
			element: jQuery('<input id="aloha-attribute-field-tableSummary" class="aloha-wai-red" style="color: black; padding-left: 32px; background-color: white"/>')
		} );

		this.summary.addListener('keyup', function() {
			if (that.activeTable) {
				updateWaiImage(that);
			}
		});
	};

	/**
	 * Helper method to make the caption editable
	 * @param caption caption as jQuery object
	 * @param captionText default text for the caption
	 */
	TablePlugin.makeCaptionEditable = function(caption, captionText) {
		var that = this;
		var cSpan = caption.children('div');
		if (cSpan.length === 0) {
			// generate a new div
			cSpan = jQuery('<div></div>');
			cSpan.addClass('aloha-ui aloha-editable-caption aloha-block');

			// mark the editable wrapper as ephemeral
			Ephemera.markWrapper(cSpan);

			if (caption.contents().length > 0) {
				// when the caption has content, we wrap it with the new div
				cSpan.append(caption.contents());
				caption.append(cSpan);
			} else {
				// caption has no content, so insert the default caption text
				if (captionText) {
					cSpan.text(captionText);
				}
				// and append the div into the caption
				caption.append(cSpan);
			}
		} else if (cSpan.length > 1) {
			// merge multiple divs (they are probably created by IE)
			caption.children('div:not(:first-child)').each(function () {
				$this = jQuery(this);
				cSpan.eq(0).append($this.contents());
				$this.remove();
			});
			cSpan = cSpan.eq(0);
		}
		// make the div editable
		cSpan.contentEditable(true);
	};

	/**
	 * This function adds the createDialog to the calling element
	 *
	 * @param callingElement
	 *            The element, which was clicked. It's needed to set the right
	 *            position to the create-table-dialog.
	 */
	TablePlugin.createDialog = function(callingElement) {
		// set the calling element to the layer the calling element mostly will be
		// the element which was clicked on it is used to position the createLayer
		this.createLayer.set('target', callingElement);

		// show the createLayer
		this.createLayer.show();
	};

	/**
	 * Creates a normal html-table, "activates" this table and inserts it into the
	 * active Editable
	 *
	 * @param cols
	 *            number of colums for the created table
	 * @param cols
	 *            number of rows for the created table
	 * @return void
	 */
	TablePlugin.createTable = function(cols, rows) {
		if ( this.preventNestedTables() ) {
			return;
		}

		// Check if there is an active Editable and that it contains an element (= .obj)
		if ( Aloha.activeEditable && typeof Aloha.activeEditable.obj !== 'undefined' ) {
			// create a dom-table object
			var table = document.createElement( 'table' );
			var tableId = table.id = GENTICS.Utils.guid();
			var tbody = document.createElement( 'tbody' );

			// create "rows"-number of rows
			for ( var i = 0; i < rows; i++ ) {
				var tr = document.createElement( 'tr' );
				// create "cols"-number of columns
				for ( var j = 0; j < cols; j++ ) {
					var text = document.createTextNode( '\u00a0' );
					var td = document.createElement( 'td' );
					td.appendChild( text );
					tr.appendChild( td );
				}
				tbody.appendChild( tr );
			}
			table.appendChild( tbody );

			prepareRangeContainersForInsertion(
				Aloha.Selection.getRangeObject(), table );

			// insert the table at the current selection
			GENTICS.Utils.Dom.insertIntoDOM(
				jQuery( table ),
				Aloha.Selection.getRangeObject(),
				Aloha.activeEditable.obj
			);

			cleanupAfterInsertion();

			var tableReloadedFromDOM = document.getElementById( tableId );
			var tableObj = createNewTable(tableReloadedFromDOM);

			if (tableObj) {
				// Because without the 10ms delay, we cannot place the cursor
				// automatically into the first cell in IE.
				if ($.browser.msie) {
					window.setTimeout(function () {
						tableObj.cells[0].wrapper.get(0).focus();
					}, 20 );
				} else {
					tableObj.cells[0].wrapper.get(0).focus();
				}
			}

			// The selection starts out in the first cell of the new
			// table. The table tab/scope has to be activated
			// accordingly.
			tableObj.focus();
			TablePlugin.activeTable.selection.selectionType = 'cell';
			TablePlugin.updateFloatingMenuScope();

		} else {
			this.error( 'There is no active Editable where the table can be\
				inserted!' );
		}
	};

	TablePlugin.setFocusedTable = function(focusTable) {
		var that = this;

		// clicking outside the table unselects the cells of the table
		if (null != this.activeTable ) {
			this.activeTable.selection.unselectCells();
		}

		for (var i = 0; i < TablePlugin.TableRegistry.length; i++) {
			TablePlugin.TableRegistry[i].hasFocus = false;
		}
		if (typeof focusTable != 'undefined') {
			this.summary.setTargetObject(focusTable.obj, 'summary');
			if ( focusTable.obj.children("caption").is('caption') ) {
				// set caption button
				this._tableCaptionButton.setState(true);
				var c = focusTable.obj.children("caption");
				that.makeCaptionEditable(c);
			}
			focusTable.hasFocus = true;
		}
		TablePlugin.activeTable = focusTable;
		if (TablePlugin.activeTable) {
			updateWaiImage(TablePlugin);
		}

		// show configured formatting classes
		for (var i = 0; i < this.tableMSItems.length; i++) {
			this.tableMSButton.showItem(this.tableMSItems[i].name);
		}
		this.tableMSButton.setActiveItem();

		if (this.activeTable) {
			for (var i = 0; i < this.tableConfig.length; i++) {
				if (this.activeTable.obj.hasClass(this.tableConfig[i].cssClass)) {
					this.tableMSButton.setActiveItem(this.tableConfig[i].name);
				}
			}
		}
	};

	/**
	 * Calls the Aloha.log function with 'error' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.error = function(msg) {
		Aloha.Log.error(this, msg);
	};

	/**
	 * Calls the Aloha.log function with 'debug' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.debug = function(msg) {
		Aloha.Log.debug(this, msg);
	};

	/**
	 * Calls the Aloha.log function with 'info' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.info = function(msg) {
		Aloha.Log.info(this, msg);
	};

	/**
	 * Calls the Aloha.log function with 'info' level
	 *
	 * @see Aloha.log
	 * @param msg
	 *            The message to display
	 * @return void
	 */
	TablePlugin.log = function(msg) {
		Aloha.log('log', this, msg);
	};

	/**
	 * The "get"-method returns the value of the given key.
	 * First it searches in the config for the property.
	 * If there is no property with the given name in the
	 * "config"-object it returns the entry associated with
	 * in the parameters-object
	 *
	 * @param property
	 * @return void
	 *
	 */
	TablePlugin.get = function (property) {
		if (this.config[property]) {
			return this.config[property];
		}
		if (this.parameters[property]) {
			return this.parameters[property];
		}
		return undefined;
	};

	/**
	 * The "set"-method takes a key and a value. It checks if there is a
	 * key-value pair in the config-object. If so it saves the data in the
	 * config-object. If not it saves the data in the parameters-object.
	 *
	 * @param key the key which should be set
	 * @param value the value which should be set for the associated key
	 */
	TablePlugin.set = function (key, value) {
		if (this.config[key]) {
			this.config[key] = value;
		}else{
			this.parameters[key] = value;
		}
	};

	/**
	 * Make the given jQuery object (representing an editable) clean for saving
	 * Find all tables and deactivate them
	 * @param obj jQuery object to make clean
	 * @return void
	 */
	TablePlugin.makeClean = function ( obj ) {
		// it is generally not necessary to do anything here, because everything is handled by Ephemera.
		// the only exception is removing the cursor styles, because as of this moment, Ephemera is not capable
		// of removing css
		obj.find('td, th').css('cursor', '');
	};

	/**
	 * String representation of the Table-object
	 *
	 * @return The plugins namespace (string)
	 */
	TablePlugin.toString = function() {
		return this.prefix;
	};

	TablePlugin.updateFloatingMenuScope = function() {
		if ( null != TablePlugin.activeTable && null != TablePlugin.activeTable.selection.selectionType ) {
			Scopes.setScope(TablePlugin.name + '.' + TablePlugin.activeTable.selection.selectionType);
		}
	};

	/**
	 * Set the cell-style to match the active item, if all selected cells have the same style
	 * TODO: Algorithm very similar to setActiveStyle in table.js, should be refactored
	 */
	TablePlugin.setActiveCellStyle = function() {
		var that = this;
		var allSelected = false;
		var className;

		// reset any selected cell styles
		this.cellMSButton.setActiveItem();

		var selectedCells = that.selectedOrActiveCells();

		for (var i = 0; i < that.cellConfig.length; i++) {
			if (jQuery(selectedCells[0]).hasClass(that.cellConfig[i].cssClass) ) {
				className = that.cellConfig[i].name;
				allSelected = true;
				break;
			}
		}

		// if all selected cells have the same class, set it as active
		jQuery(selectedCells).each(function(index) {
			if (!jQuery(this).hasClass(className)) {
				allSelected = false;
			}
		});
		if (allSelected) {
			this.cellMSButton.setActiveItem(className);
		}
	};

	TablePlugin.selectedOrActiveCells = function() {
		var that = this;
		var sc = this.activeTable.selection.selectedCells;

		// if there are no selected cells,
		// set the active cell as the selected cell.
		if (!sc || sc.length < 1) {
			var activeCell = function() {
			var range = Aloha.Selection.getRangeObject();
				if (Aloha.activeEditable) {
					return range.findMarkup( function() {
							return this.nodeName.toLowerCase() === 'td';
					}, Aloha.activeEditable.obj );
				} else {
					return null;
				}
			}

			var active_cell = activeCell();
			return (active_cell ? [ active_cell ] : []);
		} else {
			return sc;
		}
	};

	PluginManager.register(TablePlugin);

	/**
	 * Detects a situation where we are about to insert content into a
	 * selection that looks like this: <p> [</p>...
	 * We will assume that the nbsp inside the <p> node was placed there to
	 * "prop-up" the empty paragraph--that is--to make the empty paragraph
	 * visible in HTML5 conformant rendering engines, like WebKit. Without the
	 * white space, such browsers would correctly render an empty <p> as
	 * invisible.
	 *
	 * If we detect this situation, we remove the white space so that when we
	 * paste new content into the paragraph, it is not be split and leaving an
	 * empty paragraph on top of the pasted content.
	 *
	 * Note that we do not use <br />'s to prop up the paragraphs, as WebKit
	 * does, because IE, will break from the HTML5 specification and will
	 * display empty paragraphs if they are content-editable. So a <br />
	 * inside an empty content-editable paragraph will result in 2 lines to be
	 * shown instead of 1 in IE.
	 *
	 * @param {Object} range
	 * @param {DOMElement} table
	 */
	function prepareRangeContainersForInsertion ( range, table ) {
		var	eNode = range.endContainer,
			sNode = range.startContainer,
			eNodeLength = ( eNode.nodeType == 3 )
				? eNode.length
				: eNode.childNodes.length;


		if ( sNode.nodeType == 3 &&
				sNode.parentNode.tagName == 'P' &&
					sNode.parentNode.childNodes.length == 1 &&
						/^(\s|%A0)$/.test( escape( sNode.data ) ) ) {
			sNode.data = '';
			range.startOffset = 0;

			// In case ... <p> []</p>
			if ( eNode == sNode ) {
				range.endOffset = 0;
			}
		}

		// If the table is not allowed to be nested inside the startContainer,
		// then it will have to be split in order to insert the table.
		// We will therefore check if the selection touches the start and/or
		// end of their container nodes.
		// If they do, we will mark their container so that after they are
		// split we can check whether or not they should be removed
		if ( !GENTICS.Utils.Dom.allowsNesting(
				sNode.nodeType == 3 ? sNode.parentNode : sNode, table ) ) {

			if ( range.startOffset == 0 ) {
				jQuery( sNode.nodeType == 3 ? sNode.parentNode : sNode )
					.addClass( 'aloha-table-cleanme' );
			}

			if ( range.endOffset == eNodeLength ) {
				jQuery( eNode.nodeType == 3 ? eNode.parentNode : eNode )
					.addClass( 'aloha-table-cleanme' );
			}
		}
	};

	/**
	 * Looks for elements marked with "aloha-table-cleanme", and removes them
	 * if they are absolutely empty.
	 * Note that this will leave paragraphs which contain empty nested elements
	 * even though they are also invisible.
	 * We can consider removing these as well at a later stage, if needed.
	 */
	function cleanupAfterInsertion () {
		var dirty = jQuery( '.aloha-table-cleanme' ).removeClass(
						'aloha-table-cleanme' );

		for ( var i = 0; i < dirty.length; i++ ) {
			if ( jQuery.trim( jQuery( dirty[ i ] ).html() ) == '' &&
					!GENTICS.Utils.Dom.isEditingHost( dirty[ i ] ) ) {
				jQuery( dirty[ i ] ).remove();

				/*
				// For debugging: to see what we are deleting
				jQuery( dirty[ i ] ).css({
					border: '3px solid red',
					display: 'block'
				});
				*/
			}
		}
	};

	return TablePlugin;
});

define('format/nls/i18n',{
	"root":  {
		"button.strong.tooltip": "Strong",
		"button.em.tooltip": "Emphasize",
		"button.b.tooltip": "Bold",
		"button.i.tooltip": "Italic",
		"button.u.tooltip": "Underline",
		"button.cite.tooltip": "Cite",
		"button.q.tooltip": "Tooltip",
		"button.code.tooltip": "Code",
		"button.abbr.tooltip": "Abbreviation",
		"button.del.tooltip": "Strikethrough",
		"button.s.tooltip": "Strikethrough",
		"button.sub.tooltip": "Subscript",
		"button.sup.tooltip": "Superscript",
		"button.p.tooltip": "Paragraph",
		"button.h1.tooltip": "Heading 1",
		"button.h2.tooltip": "Heading 2",
		"button.h3.tooltip": "Heading 3",
		"button.h4.tooltip": "Heading 4",
		"button.h5.tooltip": "Heading 5",
		"button.h6.tooltip": "Heading 6",
		"button.pre.tooltip": "Preformatted text",
		"button.title.tooltip": "Title",
		"button.removeFormat.tooltip": "Remove formatting",
		"button.removeFormat.text": "Remove formatting",
		"GENTICS_button_p": "GENTICS_button_p",
		"GENTICS_button_h1": "GENTICS_button_h1",
		"GENTICS_button_h2": "GENTICS_button_h2",
		"GENTICS_button_h3": "GENTICS_button_h3",
		"GENTICS_button_h4": "GENTICS_button_h4",
		"GENTICS_button_h5": "GENTICS_button_h5",
		"GENTICS_button_h6": "GENTICS_button_h6",
		"GENTICS_button_pre": "GENTICS_button_pre",
		"GENTICS_button_title": "GENTICS_button_title",
		"formatBold": "Ctrl+b",
		"formatItalic": "Ctrl+i",
		"formatUnderline": "Ctrl+u",
		"formatParagraph": "Alt+Ctrl+0",
		"formatH1": "Alt+Ctrl+1",
		"formatH2": "Alt+Ctrl+2",
		"formatH3": "Alt+Ctrl+3",
		"formatH4": "Alt+Ctrl+4",
		"formatH5": "Alt+Ctrl+5",
		"formatH6": "Alt+Ctrl+6",
		"formatPre": "Alt+Ctrl+P",
		"formatDel": "Ctrl+\\",
		"formatSub": "Ctrl+,",
		"formatSup": "Ctrl+.",
		"floatingmenu.tab.format": "Format",
		"format.class.legend": "CSS class",
		"format.class.none": "none"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* format-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('format/format-plugin', [
	'aloha',
	'aloha/plugin',
	'aloha/state-override',
	'jquery',
	'util/arrays',
	'util/maps',
	'ui/ui',
	'ui/toggleButton',
	'ui/port-helper-multi-split',
	'PubSub',
	'i18n!format/nls/i18n',
	'i18n!aloha/nls/i18n',
	'aloha/selection'
], function (
	Aloha,
	Plugin,
	StateOverride,
	jQuery,
	Arrays,
	Maps,
	Ui,
	ToggleButton,
	MultiSplitButton,
	PubSub,
	i18n,
	i18nCore
) {
	

	var GENTICS = window.GENTICS;
	var pluginNamespace = 'aloha-format';
	var commandsByElement = {
		'b': 'bold',
		'strong': 'bold',
		'i': 'italic',
		'em': 'italic',
		'del': 'strikethrough',
		'sub': 'subscript',
		'sup': 'superscript',
		'u': 'underline',
		's': 'strikethrough'
	};
	var componentNameByElement = {
		'strong': 'strong',
		'em': 'emphasis',
		's': 'strikethrough2'
	};
	var textLevelSemantics = {
		'u': true,
		'em': true,
		'strong': true,
		'b': true,
		'i': true,
		'cite': true,
		'q': true,
		'code': true,
		'abbr': true,
		'del': true,
		's': true,
		'sub': true,
		'sup': true
	};
	var blockLevelSemantics = {
		'p': true,
		'h1': true,
		'h2': true,
		'h3': true,
		'h4': true,
		'h5': true,
		'h6': true,
		'pre': true
	};
	var interchangeableNodeNames = {
		"B": ["STRONG", "B"],
		"I": ["EM", "I"],
		"STRONG": ["STRONG", "B"],
		"EM": ["EM", "I"]
	};

	function formatInsideTableWorkaround(button) {
		var selectedCells = jQuery('.aloha-cell-selected');
		if (selectedCells.length > 0) {
			var cellMarkupCounter = 0;
			selectedCells.each(function () {
				var cellContent = jQuery(this).find('div'),
				cellMarkup = cellContent.find(button);
				if (cellMarkup.length > 0) {
					// unwrap all found markup text
					// <td><b>text</b> foo <b>bar</b></td>
					// and wrap the whole contents of the <td> into <b> tags
					// <td><b>text foo bar</b></td>
					cellMarkup.contents().unwrap();
					cellMarkupCounter++;
				}
				cellContent.contents().wrap('<'+button+'></'+button+'>');
			});

			// remove all markup if all cells have markup
			if (cellMarkupCounter === selectedCells.length) {
				selectedCells.find(button).contents().unwrap();
			}
			return true;
		}
		return false;
	}

	function textLevelButtonClickHandler(formatPlugin, button) {
		if (formatInsideTableWorkaround(button)) {
			return false;
		}
		formatPlugin.addMarkup( button ); 
		return false;
	}

	function blockLevelButtonClickHandler(formatPlugin, button) {
		if (formatInsideTableWorkaround(button)) {
			return false;
		}

		formatPlugin.changeMarkup( button );

		// setting the focus is needed for mozilla to have a working rangeObject.select()
		if (Aloha.activeEditable && jQuery.browser.mozilla) {
			Aloha.activeEditable.obj.focus();
		}
		
		// triggered for numerated-headers plugin
		if (Aloha.activeEditable) {
			Aloha.trigger( 'aloha-format-block' );
		}
	}

	function makeTextLevelButton(formatPlugin, button) {
		var command = commandsByElement[button];
		var componentName = command;
		if (componentNameByElement.hasOwnProperty(button)) {
			componentName = componentNameByElement[button];
		}
		var component = Ui.adopt(componentName, ToggleButton, {
			tooltip : i18n.t('button.' + button + '.tooltip'),
			icon: 'aloha-icon aloha-icon-' + componentName,
			scope: 'Aloha.continuoustext',
			click: function () { return textLevelButtonClickHandler(formatPlugin, button); }
		});
		return component;
	}

	function makeBlockLevelButton(formatPlugin, button) {
		return {
			name: button,
			tooltip: i18n.t('button.' + button + '.tooltip'),
			iconClass: 'aloha-icon ' + i18n.t('aloha-large-icon-' + button),
			markup: jQuery('<' + button + '>'),
			click: function () { return blockLevelButtonClickHandler(formatPlugin, button); }
		};
	}

	function makeRemoveFormatButton(formatPlugin, button) {
		return {
			name: button,
			text: i18n.t('button.' + button + '.text'),
			tooltip: i18n.t('button.' + button + '.tooltip'),
			wide: true,
			cls: 'aloha-ui-multisplit-fullwidth',
			click: function () {
				formatPlugin.removeFormat();
			}
		};
	}

	function changeMarkup(button) {
		Aloha.Selection.changeMarkupOnSelection(jQuery('<' + button + '>'));
	}

	function updateUiAfterMutation(formatPlugin, rangeObject) {
		// select the modified range
		rangeObject.select();
		// update Button toggle state. We take 'Aloha.Selection.getRangeObject()'
		// because rangeObject is not up-to-date
		onSelectionChanged(formatPlugin, Aloha.Selection.getRangeObject());
	}

	function format(formatPlugin, rangeObject, markup) {
		GENTICS.Utils.Dom.addMarkup(rangeObject, markup);
		updateUiAfterMutation(formatPlugin, rangeObject);
	}

	function isFormatAllowed(tagname, plugin, editable) {
		var config = plugin.getEditableConfig(editable.obj);
		return jQuery.inArray(tagname, config) > -1;
	}

	function addMarkup(button) {
		var formatPlugin = this;
		var markup = jQuery('<'+button+'>');
		var rangeObject = Aloha.Selection.rangeObject;
		
		if ( typeof button === "undefined" || button == "" ) {
			return;
		}

		// check whether the markup is found in the range (at the start of the range)
		var nodeNames = interchangeableNodeNames[markup[0].nodeName] || [markup[0].nodeName];
		var foundMarkup = rangeObject.findMarkup(function() {
			return -1 !== Arrays.indexOf(nodeNames, this.nodeName);
		}, Aloha.activeEditable.obj);

		if (foundMarkup) {
			// remove the markup
			if (rangeObject.isCollapsed()) {
				// when the range is collapsed, we remove exactly the one DOM element
				GENTICS.Utils.Dom.removeFromDOM(foundMarkup, rangeObject, true);
			} else {
				// the range is not collapsed, so we remove the markup from the range
				GENTICS.Utils.Dom.removeMarkup(rangeObject, jQuery(foundMarkup), Aloha.activeEditable.obj);
			}
			updateUiAfterMutation(formatPlugin, rangeObject);
		} else {
			// when the range is collapsed, extend it to a word
			if (rangeObject.isCollapsed()) {
				GENTICS.Utils.Dom.extendToWord(rangeObject);
				if (rangeObject.isCollapsed()) {
					if (StateOverride.enabled()) {
						StateOverride.setWithRangeObject(
							commandsByElement[button],
							rangeObject,
							function (command, rangeObject) {
								format(formatPlugin, rangeObject, markup);
							}
						);
						return;
					}
				}
			}
			format(formatPlugin, rangeObject, markup);
		}
	}

	function onSelectionChanged(formatPlugin, rangeObject) {
		var effectiveMarkup,
		    foundMultiSplit, i, j, multiSplitItem;

		jQuery.each(formatPlugin.buttons, function (index, button) {
			var statusWasSet = false;
			var nodeNames = interchangeableNodeNames[button.markup[0].nodeName] || [button.markup[0].nodeName];
			for (i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[i];
				for (j = 0; j < nodeNames.length; j++) {
					if (Aloha.Selection.standardTextLevelSemanticsComparator(effectiveMarkup, jQuery('<' + nodeNames[j] + '>'))) {
						button.handle.setState(true);
						statusWasSet = true;
					}
				}
			}
			if (!statusWasSet) {
				button.handle.setState(false);
			}
		});

		if (formatPlugin.multiSplitItems.length > 0) {
			foundMultiSplit = false;

			// iterate over the markup elements
			for (i = 0; i < rangeObject.markupEffectiveAtStart.length && !foundMultiSplit; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[i];

				for (j = 0; j < formatPlugin.multiSplitItems.length && !foundMultiSplit; j++) {
					multiSplitItem = formatPlugin.multiSplitItems[j];

					if (!multiSplitItem.markup) {
						continue;
					}

					// now check whether one of the multiSplitItems fits to the effective markup
					if (Aloha.Selection.standardTextLevelSemanticsComparator(effectiveMarkup, multiSplitItem.markup)) {
						formatPlugin.multiSplitButton.setActiveItem(multiSplitItem.name);
						foundMultiSplit = true;
					}
				}
			}

			if (!foundMultiSplit) {
				formatPlugin.multiSplitButton.setActiveItem(null);
			}
		}
	}

	/**
	 * register the plugin with unique name
	 */
	return Plugin.create('format', {
		/**
		 * default button configuration
		 */
		config: [ 'b', 'i', 'sub', 'sup', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'removeFormat' ],

		/**
		 * available options / buttons
		 * 
		 * @todo new buttons needed for 'code'
		 */
		availableButtons: [ 'u', 'strong', 'del', 'em', 'b', 'i', 's', 'sub', 'sup', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'removeFormat' ],

		/**
		 * HotKeys used for special actions
		 */
		hotKey: { 
			formatBold: 'ctrl+b',
			formatItalic: 'ctrl+i',
			formatParagraph: 'alt+ctrl+0',
			formatH1: 'alt+ctrl+1',
			formatH2: 'alt+ctrl+2',
			formatH3: 'alt+ctrl+3',
			formatH4: 'alt+ctrl+4',
			formatH5: 'alt+ctrl+5',
			formatH6: 'alt+ctrl+6',
			formatPre: 'ctrl+p',
			formatDel: 'ctrl+d',
			formatSub: 'alt+shift+s',
			formatSup: 'ctrl+shift+s'
		},

		/**
		 * Initialize the plugin and set initialize flag on true
		 */
		init: function () {
			// Prepare
			var me = this;

			if (typeof this.settings.hotKey !== 'undefined') {
				jQuery.extend(true, this.hotKey, this.settings.hotKey);
			}

			this.initButtons();

			Aloha.bind('aloha-plugins-loaded', function () {
				// @todo add config option for sidebar panel
				me.initSidebar(Aloha.Sidebar.right);
			});

			// apply specific configuration if an editable has been activated
			Aloha.bind('aloha-editable-activated',function (e, params) {
				me.applyButtonConfig(params.editable.obj);

				var createAdder = function (tagname) {
					return function () {
						if (isFormatAllowed(tagname, me, params.editable)) {
							me.addMarkup(tagname);
						}
						return false;
					};
				};

				var createChanger = function (tagname) {
					return function () {
						if (isFormatAllowed(tagname, me, params.editable)) {
							me.changeMarkup(tagname);
						}
						return false;
					};
				};

				params.editable.obj.bind('keydown.aloha.format',  me.hotKey.formatBold,     createAdder('b'));
				params.editable.obj.bind('keydown.aloha.format',  me.hotKey.formatItalic,   createAdder('i'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatDel,       createAdder('del'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatSub,       createAdder('sub'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatSup,       createAdder('sup'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatParagraph, createChanger('p'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatH1,        createChanger('h1'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatH2,        createChanger('h2'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatH3,        createChanger('h3'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatH4,        createChanger('h4'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatH5,        createChanger('h5'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatH6,        createChanger('h6'));
				params.editable.obj.bind('keydown.aloha.format', me.hotKey.formatPre,       createChanger('pre'));
			});

			Aloha.bind('aloha-editable-deactivated',function (e, params) {
				params.editable.obj.unbind('keydown.aloha.format');
			});
		},

		/**
		 * applys a configuration specific for an editable
		 * buttons not available in this configuration are hidden
		 * @param {Object} id of the activated editable
		 * @return void
		 */
		applyButtonConfig: function (obj) {
			var config = this.getEditableConfig(obj),
			    button, i, len;

			if ( typeof config === 'object' ) {
				var config_old = [];
				jQuery.each(config, function(j, button) {
					if ( !(typeof j === 'number' && typeof button === 'string') ) {
						config_old.push(j);
					}
				});
				
				if ( config_old.length > 0 ) {
					config = config_old;
				}
			}
			this.formatOptions = config;

			// now iterate all buttons and show/hide them according to the config
			for ( button in this.buttons) {
				if (this.buttons.hasOwnProperty(button)) {
					if (jQuery.inArray(button, config) !== -1) {
						this.buttons[button].handle.show();
					} else {
						this.buttons[button].handle.hide();
					}
				}
			}

			// and the same for multisplit items
			len = this.multiSplitItems.length;
			for (i = 0; i < len; i++) {
				if (jQuery.inArray(this.multiSplitItems[i].name, config) !== -1) {
					this.multiSplitButton.showItem(this.multiSplitItems[i].name);
				} else {
					this.multiSplitButton.hideItem(this.multiSplitItems[i].name);
				}
			}
		},

		/**
		 * initialize the buttons and register them on floating menu
		 * @param event event object
		 * @param editable current editable object
		 */
		initButtons: function () {
			var that = this;

			this.buttons = {};
			this.multiSplitItems = [];

			jQuery.each(this.availableButtons, function(j, button) {
				var button_config = false;

				if (typeof j !== 'number' && typeof button !== 'string') {
					button_config = button;
					button = j;
				}

				if (textLevelSemantics[button]) {
					that.buttons[button] = {
						handle: makeTextLevelButton(that, button),
						markup: jQuery('<'+button+'>', {'class': button_config || ''})
					};
				} else if (blockLevelSemantics[button]) {
					that.multiSplitItems.push(makeBlockLevelButton(that, button));
				} else if ('removeFormat' === button) {
					that.multiSplitItems.push(makeRemoveFormatButton(that, button));
				} else {
					Aloha.log('warn', that, 'Button "' + button + '" is not defined');
				}
			});

			this.multiSplitButton = MultiSplitButton({
				name: 'formatBlock',
				items: this.multiSplitItems,
				hideIfEmpty: true,
				scope: 'Aloha.continuoustext'
			});

			PubSub.sub('aloha.selection.context-change', function(message) {
				onSelectionChanged(that, message.range);
			});
		},

		initSidebar: function ( sidebar ) {
			var pl = this;
			pl.sidebar = sidebar;
			sidebar.addPanel( {

				id       : pl.nsClass( 'sidebar-panel-class' ),
				title    : i18n.t( 'floatingmenu.tab.format' ),
				content  : '',
				expanded : true,
				activeOn : this.formatOptions || false,

				onInit: function () {
				},

				onActivate: function ( effective ) {
					var that = this;
					that.effective = effective;
					
					if ( !effective[0] ) {
						return;
					}
					that.format = effective[0].nodeName.toLowerCase();

					var dom = jQuery('<div>').attr('class', pl.nsClass( 'target-container' ));
					var fieldset = jQuery('<fieldset>');
					fieldset.append(jQuery('<legend>' + that.format + ' ' + i18n.t( 'format.class.legend' )).append(jQuery('<select>')));
					
					dom.append(fieldset);
					
					var html = 
						'<div class="' + pl.nsClass( 'target-container' ) + '"><fieldset><legend>' + i18n.t( 'format.class.legend' ) + '</legend><select name="targetGroup" class="' + pl.nsClass( 'radioTarget' ) + '">' + 
						'<option value="">' + i18n.t( 'format.class.none' ) + '</option>';

					if ( pl.config[that.format] && pl.config[that.format]['class'] ) {
						jQuery.each(pl.config[that.format]['class'], function(i ,v) {
							html += '<option value="' + i + '" >' + v + '</option>';
						});
					}

					html += '</select></fieldset></div>';

					var that = this,
					content = this.setContent(html).content; 

					jQuery( pl.nsSel( 'framename' ) ).live( 'keyup', function () {
						jQuery( that.effective ).attr( 'target', jQuery( this ).val().replace( '\"', '&quot;' ).replace( "'", "&#39;" ) );
					} );
					

					var that = this;
					that.effective = effective;
					jQuery( pl.nsSel( 'linkTitle' ) ).val( jQuery( that.effective ).attr( 'title' ) );
				}

			} );

			sidebar.show();
		},

		// duplicated code from link-plugin
		//Creates string with this component's namepsace prefixed the each classname
		nsClass: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each( arguments, function () {
				stringBuilder.push( this == '' ? prefix : prefix + '-' + this );
			} );
			return jQuery.trim(stringBuilder.join(' '));
		},

		// duplicated code from link-plugin
		nsSel: function () {
			var stringBuilder = [], prefix = pluginNamespace;
			jQuery.each( arguments, function () {
				stringBuilder.push( '.' + ( this == '' ? prefix : prefix + '-' + this ) );
			} );
			return jQuery.trim(stringBuilder.join(' '));
		},

		addMarkup: addMarkup,
		changeMarkup: changeMarkup,

		/**
		 * Removes all formatting from the current selection.
		 */
		removeFormat: function() {
			var formats = [ 'u', 'strong', 'em', 'b', 'i', 'q', 'del', 's', 'code', 'sub', 'sup', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'quote', 'blockquote' ],
			    rangeObject = Aloha.Selection.rangeObject,
			    i;

			// formats to be removed by the removeFormat button may now be configured using Aloha.settings.plugins.format.removeFormats = ['b', 'strong', ...]
			if (this.settings.removeFormats) {
				formats = this.settings.removeFormats;
			}

			if (rangeObject.isCollapsed()) {
				return;
			}

			for (i = 0; i < formats.length; i++) {
				GENTICS.Utils.Dom.removeMarkup(rangeObject, jQuery('<' + formats[i] + '>'), Aloha.activeEditable.obj);
			}

			// select the modified range
			rangeObject.select();
			// TODO: trigger event - removed Format
		},

		/**
		 * toString method
		 * @return string
		 */
		toString: function () {
			return 'format';
		}
	});
});

define('list/nls/i18n',{
	"root":  {
		"button.createulist.tooltip": "Insert Unordered List",
		"button.createolist.tooltip": "Insert Ordered List",
		"button.indentlist.tooltip": "Indent List",
		"button.outdentlist.tooltip": "Outdent List",
		"floatingmenu.tab.list": "Lists"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* list-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('list/list-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'ui/ui',
	'ui/scopes',
	'ui/button',
	'ui/toggleButton',
	'i18n!list/nls/i18n',
	'i18n!aloha/nls/i18n',
	'aloha/engine',
	'PubSub'
], function(
	Aloha,
	jQuery,
	Plugin,
	Ui,
	Scopes,
	Button,
	ToggleButton,
	i18n,
	i18nCore,
	Engine,
	PubSub
) {
	

	var GENTICS = window.GENTICS;
	
	/**
	 * Transforms the given list element and its sub elements (if they are in the selection) into
	 * the given transformTo target.
	 * @param domToTransform - The list object that should be transformed
	 * @param transformTo - Transformationtarget e.g. 'ul' / 'ol'
	 */
	function transformExistingListAndSubLists (domToTransform, transformTo) {
		// find and transform sublists if they are in the selection
		jQuery(domToTransform).find(domToTransform.nodeName).each(function(){
			if (isListInSelection(this)) {
				Aloha.Markup.transformDomObject(this, transformTo, Aloha.Selection.rangeObject);
			}
		});

		// the element itself
		Aloha.Markup.transformDomObject(domToTransform, transformTo, Aloha.Selection.rangeObject);
	}

	/**
	 * Checks if a dom element is in the given Slectiontree.
	 * @param needle - the searched element
	 * @return returns true if the needle is found in the current selection tree.
	 */
	function isListInSelection(needle) {
		var selectionTree = Aloha.Selection.getSelectionTree();
		return checkSelectionTreeEntryForElement(selectionTree, needle);
	}

	/**
	 * Checks if the given needle is in the given treeElement or in one of its sub elements.
	 * @param treeElement - the tree element to be searched in
	 * @param needle - the searched element
	 */
	function checkSelectionTreeEntryForElement(treeElementArray, needle) {
		var found = false;
		jQuery.each(treeElementArray, function(index, element){
			if ((element.domobj === needle && element.selection !== "none") || checkSelectionTreeEntryForElement(element.children, needle)) {
				found = true;
			}
		});
		return found;
	}

	/**
	 * Register the ListPlugin as Aloha.Plugin
	 */
	var ListPlugin = Plugin.create('list', {
		/**
		 * default button configuration
		 */
		config: [ 'ul', 'ol' ],

		/**
		 * List of transformable elements
		 */
		transformableElements: {'p' : true, 'h1' : true, 'h2' : true, 'h3' : true, 'h4' : true, 'h5' : true, 'h6' : true, 'ul' : true, 'ol' : true},

		/**
		 * Initialize the plugin, register the buttons
		 */
		init: function() {

			var that = this;

			this._orderedListButton = Ui.adopt("orderedList", ToggleButton, {
				tooltip: i18n.t("button.createolist.tooltip"),
				icon: "aloha-icon aloha-icon-orderedlist",
				scope: 'Aloha.continuoustext',
				click: function(){
					that.transformList(true);
				}
			});

			this._unorderedListButton = Ui.adopt("unorderedList", ToggleButton, {
				tooltip: i18n.t("button.createulist.tooltip"),
				icon: "aloha-icon aloha-icon-unorderedlist",
				scope: 'Aloha.continuoustext',
				click: function(){
					that.transformList(false);
				}
			});

			this._indentListButton = Ui.adopt("indentList", Button, {
				tooltip: i18n.t('button.indentlist.tooltip'),
				icon: 'aloha-icon aloha-icon-indent',
				scope: 'Aloha.continuoustext',
				click: function() {
					that.indentList();
				}
			});

			this._outdentListButton = Ui.adopt("outdentList", Button, {
				tooltip: i18n.t('button.outdentlist.tooltip'),
				icon: 'aloha-icon aloha-icon-outdent',
				scope: 'Aloha.continuoustext',
				click: function() {
					that.outdentList();
				}
			});

			Scopes.createScope('Aloha.List', 'Aloha.continuoustext');

			// add the event handler for context selection change
			PubSub.sub('aloha.selection.context-change', function(message){
				var i,
					effectiveMarkup,
					rangeObject = message.range;
				
				// Hide all buttons in the list tab will make the list tab disappear
				that._outdentListButton.show(false);
				that._indentListButton.show(false);
				that._unorderedListButton.setState(false);
				that._orderedListButton.setState(false);
				
				for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
					effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
					if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ul></ul>'))) {
						that._unorderedListButton.setState(true);
						// Show all buttons in the list tab
						that._outdentListButton.show(true);
						that._indentListButton.show(true);
						break;
					}
					if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ol></ol>'))) {
						that._orderedListButton.setState(true);
						// Show all buttons in the list tab
						that._outdentListButton.show(true);
						that._indentListButton.show(true);
						break;
					}
				}

				if (Aloha.activeEditable) {
					that.applyButtonConfig(Aloha.activeEditable.obj);
				}
			});

			// add the key handler for Tab
			Aloha.Markup.addKeyHandler(9, function(event) {
				return that.processTab(event);
			});
		},

		/**
		 * Applys a configuration specific for an editable
		 * buttons not available in this configuration are hidden
		 * @param {jQuery} obj jQuery object of the activated editable
		 */
		applyButtonConfig: function (obj) {
			var config = this.getEditableConfig(obj);

			if (Aloha.Selection.rangeObject.unmodifiableMarkupAtStart[0]) {
				// show/hide them according to the config
				if (jQuery.inArray('ul', config) != -1 && Aloha.Selection.canTag1WrapTag2(Aloha.Selection.rangeObject.unmodifiableMarkupAtStart[0].nodeName, "ul") != -1) {
					this._unorderedListButton.show(true);
				} else {
					this._unorderedListButton.show(false);
				}

				if (jQuery.inArray('ol', config) != -1 && Aloha.Selection.canTag1WrapTag2(Aloha.Selection.rangeObject.unmodifiableMarkupAtStart[0].nodeName, "ol") != -1) {
					this._orderedListButton.show(true);
				} else {
					this._orderedListButton.show(false);
				}

			}
		},

		/**
		 * Process Tab and Shift-Tab pressed in lists
		 */
		processTab: function (event) {
			if (event.keyCode === 9/*tab*/ ) {
				if (event.shiftKey) {
					return this.outdentList();
				} else {
					return this.indentList();
				}
			}
			return true;
		},

		/**
		 * For the current selection, get the DOM object, which will be transformed to/from the list
		 * @return dom object or false
		 */
		getStartingDomObjectToTransform: function () {
			var rangeObject = Aloha.Selection.rangeObject,
				i, effectiveMarkup;

			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (this.transformableElements[effectiveMarkup.nodeName.toLowerCase()]) {
					return effectiveMarkup;
				}
			}

			return false;
		},

		/**
		 * For the current selection, get the nearest list item as dom object
		 * @return dom object or false
		 */
		getNearestSelectedListItem: function () {
			var rangeObject = Aloha.Selection.rangeObject,
				i, effectiveMarkup;

			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (GENTICS.Utils.Dom.isListElement(effectiveMarkup)) {
					return effectiveMarkup;
				}
			}

			return false;
		},

		/**
		 * Transform the current selection to/from a list
		 * @param ordered true when transforming to/from an ordered list, false for unordered lists
		 */
		transformList: function (ordered) {
			var domToTransform = this.getStartingDomObjectToTransform(),
				lastLi, i, jqNewLi, jqList, selectedSiblings, jqParentList,
				newPara, jqToTransform, nodeName;

			// visible is set to true, but the button is not visible
			this._outdentListButton.show(true);
			this._indentListButton.show(true);

			if (!domToTransform) {
				// wrap a paragraph around the selection
				Aloha.Selection.changeMarkupOnSelection(jQuery('<p></p>'));
				domToTransform = this.getStartingDomObjectToTransform();

				if (!domToTransform) {
					if ( Aloha.Selection.rangeObject.startContainer.contentEditable ) {
						// create a new list with an empty item
						jqList = ordered ? jQuery('<ol></ol>') : jQuery('<ul></ul>');
						jqNewLi = jQuery('<li></li>');
						jqList.append(jqNewLi);
						
						var li = jqNewLi.get(0);
						var editable = Aloha.getActiveEditable().obj;
						//IE7 requires an (empty or non-empty) text node
						//inside the li for the selection to work.
						li.appendChild(document.createTextNode(""));

						editable.append(jqList);
						editable.focus();

						var range = Aloha.createRange();
						var selection = Aloha.getSelection();
						range.setStart( li.firstChild, 0 );
						range.setEnd( li.firstChild, 0 );
						selection.removeAllRanges();
						selection.addRange( range );
						Aloha.Selection.updateSelection();
						
						return;
					} else {
					Aloha.Log.error(this, 'Could not transform selection into a list');
					return;
				}
			}
			}

			// check the dom object
			nodeName = domToTransform.nodeName.toLowerCase();

			if (nodeName == 'ul' && !ordered) {
				// first check whether the list is nested into another list
				jqList = jQuery(domToTransform);

				jqParentList = jqList.parent();
				if (jqParentList.length > 0
						&& GENTICS.Utils.Dom.isListElement(jqParentList.get(0))) {
					// when the list is nested into another, our list items will be
					// added to the list items of the outer list

					// find the place where to put the children of the inner list
					if (jqParentList.get(0).nodeName.toLowerCase() === 'li') {
						// inner table is nested in a li (this conforms to the html5 spec)
						jqParentList.after(jqList.children());
						jqList.remove();
					} else {
						// inner table is nested in the outer list directly (this violates the html5 spec)
						jqList.children().unwrap();
					}
				} else {
					// we are in an unordered list and shall transform it to paragraphs

					// transform all li into p
					jqToTransform = jQuery(domToTransform);
					jQuery.each(jqToTransform.children('li'), function(index, li) {
						newPara = Aloha.Markup.transformDomObject(li, 'p', Aloha.Selection.rangeObject);
						// if any lists are in the paragraph, move the to after the paragraph
						newPara.after(newPara.children('ol,ul'));
						Engine.ensureContainerEditable(newPara.get(0));
					});

					// unwrap the li (remove the enclosing ul)
					jqToTransform.children().unwrap();
				}
			} else if (nodeName == 'ul' && ordered) {
				// we are in an unordered list and shall transform it to an ordered list

				// transform the ul into an ol
				transformExistingListAndSubLists(domToTransform, 'ol');
				
				// merge adjacent lists
				this.mergeAdjacentLists(jQuery(domToTransform));
			} else if (nodeName == 'ol' && !ordered) {
				// we are in an ordered list and shall transform it to an unordered list

				// transform the ol into an ul
				transformExistingListAndSubLists(domToTransform, 'ul');

				// merge adjacent lists
				this.mergeAdjacentLists(jQuery(domToTransform));
			} else if (nodeName == 'ol' && ordered) {
				// first check whether the list is nested into another list
				jqList = jQuery(domToTransform);

				jqParentList = jqList.parent();
				if (jqParentList.length > 0
						&& GENTICS.Utils.Dom.isListElement(jqParentList.get(0))) {
					// when the list is nested into another, our list items will be
					// added to the list items of the outer list

					// find the place where to put the children of the inner list
					if (jqParentList.get(0).nodeName.toLowerCase() === 'li') {
						// inner table is nested in a li (this conforms to the html5 spec)
						jqParentList.after(jqList.children());
						jqList.remove();
					} else {
						// inner table is nested in the outer list directly (this violates the html5 spec)
						jqList.children().unwrap();
					}
				} else {
					// we are in an unordered list and shall transform it to paragraphs

					// transform all li into p
					jqToTransform = jQuery(domToTransform);
					jQuery.each(jqToTransform.children('li'), function(index, li) {
						newPara = Aloha.Markup.transformDomObject(li, 'p', Aloha.Selection.rangeObject);
						// if any lists are in the paragraph, move the to after the paragraph
						newPara.after(newPara.children('ol,ul'));
						Engine.ensureContainerEditable(newPara.get(0));
					});

					// unwrap the li (remove the enclosing ul)
					jqToTransform.children().unwrap();
				}
			} else {
				// we are in something different from a list and shall transform it into a list

				// get the also selected siblings of the dom object
				selectedSiblings = Aloha.Selection.rangeObject.getSelectedSiblings(domToTransform);

				// create a new list
				jqList = ordered ? jQuery('<ol></ol>') : jQuery('<ul></ul>');
				// add a new list item
				jqNewLi = jQuery('<li></li>');
				// add the li into the list
				jqList.append(jqNewLi);
				// append the contents of the old dom element to the li
				jQuery(domToTransform).contents().appendTo(jqNewLi);
				// replace the old dom element with the new list
				jQuery(domToTransform).replaceWith(jqList);

				// update the selection range
				if (Aloha.Selection.rangeObject.startContainer == domToTransform) {
					Aloha.Selection.rangeObject.startContainer = jqNewLi.get(0);
				}
				if (Aloha.Selection.rangeObject.endContainer == domToTransform) {
					Aloha.Selection.rangeObject.endContainer = jqNewLi.get(0);
				}

				var lastAppendedLi = jqNewLi;

				// now also transform all siblings
				if (selectedSiblings) {
					lastLi = false;
					for ( i = 0; i < selectedSiblings.length; ++i) {
						if (GENTICS.Utils.Dom.isBlockLevelElement(selectedSiblings[i])) {
							if (lastLi) {
								lastLi = false;
							}

							// transform the block level element
							jqNewLi = Aloha.Markup.transformDomObject(selectedSiblings[i], 'li', Aloha.Selection.rangeObject);
							jqList.append(jqNewLi);
							lastAppendedLi = jqNewLi;
						} else {
							if (selectedSiblings[i].nodeType == 3
									&& jQuery.trim(selectedSiblings[i].data).length === 0) {
								continue;
							}
							if (!lastLi) {
								lastLi = jQuery('<li></li>');
								jqList.append(lastLi);
								lastAppendedLi = lastLi;
							}
							lastLi.append(selectedSiblings[i]);
						}
					}
				}

				// merge adjacent lists
				this.mergeAdjacentLists(jqList);

				//use rangy to change the selection to the contents of
				//the last li that was appended to the list
				var li = lastAppendedLi.get(0);
				if (GENTICS.Utils.Dom.isEmpty(li)) {
					var range = Aloha.createRange();
					var selection = Aloha.getSelection();
					//IE7 requires an (empty or non-empty) text node
					//inside the li for the selection to work.
					li.appendChild(document.createTextNode(""));
					range.selectNodeContents( li.lastChild );
					selection.removeAllRanges();
					selection.addRange( range );
					Aloha.Selection.updateSelection();
				}
			}

			// refresh the selection
			this.refreshSelection();
		},


		/**
		 * Indent the selected list items by moving them into a new created, nested list
		 */
		indentList: function () {
			var listItem = this.getNearestSelectedListItem(),
				i, jqNewList, selectedSiblings, jqOldList, jqItemBefore;

			if (listItem) {
				jqItemBefore = jQuery(listItem).prev('li');

				// when we are in the first li of a list, there is no indenting
				if (jqItemBefore.length === 0) {
					// but we handled the TAB keystroke
					return false;
				}
				jqOldList = jQuery(listItem).parent();

				// get the also selected siblings of the dom object
				selectedSiblings = Aloha.Selection.rangeObject.getSelectedSiblings(listItem);

				// create the new list element by cloning the selected list element's parent
				jqNewList = jQuery(listItem).parent().clone(false).empty();
				jqNewList.append(listItem);

				// we found a list item before the first selected one, so append the new list to it
				jqItemBefore.append(jqNewList);

				// check for multiple selected items
				if (selectedSiblings) {
					for ( i = 0; i < selectedSiblings.length; ++i) {
						jqNewList.append(jQuery(selectedSiblings[i]));
					}
				}

				// merge adjacent lists
				this.mergeAdjacentLists(jqNewList, true);

				// refresh the selection
				this.refreshSelection();

				return false;
			}

			return true;
		},

		/**
		 * Outdent nested list items by moving them into the outer list
		 */
		outdentList: function () {
			var
				listItem = this.getNearestSelectedListItem(),
				i, jqNewPostList,
				jqListItem, jqList, jqParentList, wrappingLi,
				selectedSiblings, lastSelected;

			if (listItem) {
				// check whether the list is nested into another list
				jqListItem = jQuery(listItem);
				jqList = jqListItem.parent();

				// get the parent list
				jqParentList = jqList.parents('ul,ol');

				// check whether the inner list is directly inserted into a li element
				wrappingLi = jqList.parent('li');

				if (jqParentList.length > 0
						&& GENTICS.Utils.Dom.isListElement(jqParentList.get(0))) {
					// the list is nested into another list

					// get the also selected siblings of the dom object
					selectedSiblings = Aloha.Selection.rangeObject.getSelectedSiblings(listItem);

					// check for multiple selected items
					if (selectedSiblings && selectedSiblings.length > 0) {
						lastSelected = jQuery(selectedSiblings[selectedSiblings.length - 1]);
					} else {
						lastSelected = jqListItem;
					}

					// check whether we found not selected li's after the selection
					if (lastSelected.nextAll('li').length > 0) {
						jqNewPostList = jqList.clone(false).empty();
						jqNewPostList.append(lastSelected.nextAll());
						lastSelected.append(jqNewPostList);
					}

					// now move all selected li's into the higher list
					if (wrappingLi.length > 0) {
						wrappingLi.after(jqListItem);
					} else {
						jqList.before(jqListItem);
					}

					// check for multiple selected items
					if (selectedSiblings && selectedSiblings.length > 0) {
						for ( i = selectedSiblings.length - 1; i >= 0; --i) {
							jqListItem.after(jQuery(selectedSiblings[i]));
						}
					}

					// finally check whether there are elements left in the list
					if (jqList.contents('li').length === 0) {
						// list is completely empty, so remove it
						jqList.remove();
					}

					// check whether the wrapping li is empty now
					if (wrappingLi.length > 0 && wrappingLi.contents().length === 0) {
						wrappingLi.remove();
					}

					// refresh the selection
					this.refreshSelection();
				}

				return false;
			}

			return true;
		},

		/**
		 * Refresh the current selection and set to focus to the current editable again
		 */
		refreshSelection: function () {
			Aloha.Selection.rangeObject.update();
			Aloha.Selection.rangeObject.select();
			Aloha.Selection.updateSelection();
		},

		/**
		 * Merge adjacent lists (of same type) into the first list
		 * @param jqList jQuery object of a list
		 * @param allTypes true if all types of lists may be merged, false if only same types may be merged
		 */
		mergeAdjacentLists: function (jqList, allTypes) {
			// first get the first previous sibling of same type
			var firstList = jqList.get(0), jqNextList;

			while (
				firstList.previousSibling
				&& firstList.previousSibling.nodeType === 1
				&& this.isMergable(firstList.previousSibling, firstList, allTypes)
			) {
				firstList = firstList.previousSibling;
			}

			jqList = jQuery(firstList);
			// now merge all adjacent lists into this one
			while (
				firstList.nextSibling
				&& (
					(
						firstList.nextSibling.nodeType === 1
						&& this.isMergable(firstList.nextSibling, firstList, allTypes)
					) || (
						firstList.nextSibling.nodeType === 3
						&& jQuery.trim(firstList.nextSibling.data).length === 0
					)
				)
			) {
				jqNextList = jQuery(firstList.nextSibling);
				if (firstList.nextSibling.nodeType == 1) {
					jqNextList.contents().appendTo(jqList);
				}
				jqNextList.remove();
			}
		},

		/**
		 * Check whether the given DOM element toCheck is mergeable into the DOM element mergeInto
		 * @param toCheck DOM element to check
		 * @param mergeInto DOM element into which toCheck shall be merged
		 * @param allTypes true if all types of lists may be merged, false if only same types may be merged
		 */
		isMergable: function (toCheck, mergeInto, allTypes) {
			if (allTypes) {
				return toCheck.nodeName.toLowerCase() == 'ul' || toCheck.nodeName.toLowerCase() == 'ol';
			} else {
				return toCheck.nodeName == mergeInto.nodeName;
			}
		}
	});

	/**
	 * 
	 */
	Engine.commands['insertorderedlist'] = {
		action: function(value, range) {
			ListPlugin.transformList(true);
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function() {
			// TODO
		},
		state: function() {
			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ul></ul>'))) {
					return false;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ol></ol>'))) {
					return true;
				}
			}

			return false;
		}
	};

	Engine.commands['insertunorderedlist'] = {
		action: function(value, range) {
			ListPlugin.transformList(false);
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function() {
			// TODO
		},
		state: function() {
			for ( i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
				effectiveMarkup = rangeObject.markupEffectiveAtStart[ i ];
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ul></ul>'))) {
					return true;
				}
				if (Aloha.Selection.standardTagNameComparator(effectiveMarkup, jQuery('<ol></ol>'))) {
					return false;
				}
			}

			return false;
		}
	};

	Engine.commands['indent'] = {
		action: function(value, range) {
			ListPlugin.indentList();
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function() {
			// TODO
		},
		state: function() {
			// TODO
			return false;
		}
	};

	Engine.commands['outdent'] = {
		action: function(value, range) {
			ListPlugin.outdentList();
			if (range && Aloha.Selection.rangeObject) {
				range.startContainer = Aloha.Selection.rangeObject.startContainer;
				range.startOffset = Aloha.Selection.rangeObject.startOffset;
				range.endContainer = Aloha.Selection.rangeObject.endContainer;
				range.endOffset = Aloha.Selection.rangeObject.endOffset;
			}
		},
		indeterm: function() {
			// TODO
		},
		state: function() {
			// TODO
			return false;
		}
	};

	/**
	 * A key handler that should be run as a keyup handler for the
	 * backspace and del keys. keyup fires after the browser has already
	 * performed the delete - this handler will perform a cleanup if
	 * necessary.
	 *
	 * Will work around an IE bug which breaks nested lists in the
	 * following situation, where [] is the selection, if backspace is
	 * pressed (same goes for the del key if the selection is at the end
	 * of the li that occurs before the selection):
	 *
	 * <ul>
	 *  <li>one</li>
	 *  <li><ul><li>two</li></ul></li>
	 * </ul>
	 * <p>[]</p>
	 *
	 * The browser behaviour, if one would presses backspace, results in
	 * the following:
	 *
	 * <ul>
	 *  <li>one</li>
	 *  <ul><li>two</li></ul>
	 * </ul>
	 *
	 * which is invalid HTML since the <ul>s are nested directly inside
	 * each other.
	 *
	 * Also, the following situation will cause the kind of invalid HTML
	 * as above.
	 * <ul>
	 *   <li>one</li>
	 *   <li><ul><li>two</li></ul></li>
	 *   <li>[]three</li>
	 * </ul>
	 *
	 * Also, the following situtation:
	 * <ul>
	 *   <li>one</li>
	 *   <li><ul><li>two</li></ul>
	 *       <p>[]three</p>
	 *       <li>four</li>
	 *   </li>
	 * </ul>
	 *
	 * And similar situations, some of which are not so easy to reproduce.
	 * 
	 * @param event a jQuery key event
	 * @return false if no action needed to be taken, true if cleanup has been performed
	 */
	function deleteWorkaroundHandler(event) {
		if (8/*backspace*/ != event.keyCode && 46/*del*/ != event.keyCode) {
			return false;
		}

		var rangeObj = Aloha.getSelection().getRangeAt(0);
		var startContainer = rangeObj.startContainer;

		//the hack is only relevant if after the deletion has been
		//performed we are inside a li of a nested list
		var $nestedList = jQuery(startContainer).closest('ul, ol');
		if ( ! $nestedList.length ) {
			return false;
		}
		var $parentList = $nestedList.parent().closest('ul, ol');
		if ( ! $parentList.length ) {
			return false;
		}

		var ranges = Aloha.getSelection().getAllRanges();

		var actionPerformed = false;
		$parentList.each(function(){
			actionPerformed = actionPerformed || fixListNesting(jQuery(this));
		});

		if (actionPerformed) {
			Aloha.getSelection().setRanges(ranges);
			for (var i = 0; i < ranges.length; i++) {
				ranges[i].detach();
			}
		}

		return actionPerformed;
	}

	/**
	 * If uls or ols are nested directly inside the given list (invalid
	 * HTML), they will be cleaned up by being appended to the preceding
	 * li.
	 */
	function fixListNesting($list) {
		var actionPerformed = false;
		$list.children('ul, ol').each(function(){
			Aloha.Log.debug("performing list-nesting cleanup");
			if ( ! jQuery(this).prev('li').append(this).length ) {
				//if there is no preceding li, create a new one and append to that
				jQuery(this).parent().prepend(document.createElement('li')).append(this);
			}
			actionPerformed = true;
		});
		return actionPerformed;
	}

	return ListPlugin;
});

define('image/nls/i18n',{
	"root":  {
		"size.natural": "Original size",
		"button.addimg.tooltip": "add image ref",
		"floatingmenu.tab.img": "Image",
		"floatingmenu.tab.formatting": "Formatting",
		"floatingmenu.tab.resize": "Resize",
		"floatingmenu.tab.crop": "Crop",
		"button.uploadimg.tooltip": "Upload img",
		"button.uploadimg.label": "Upload",
		"button.img.align.left.tooltip": "Left align",
		"button.img.align.right.tooltip": "Right Align",
		"button.img.align.none.tooltip": "No alignment",
		"field.img.title.label": "Title",
		"field.img.title.tooltip": "Title",
		"field.img.label": "URL",
		"field.img.tooltip": "Source",
		"border ": "Add Border to Image",
		"padding.increase ": "Increase Padding",
		"padding.decrease ": "Decrease Padding",
		"size.increase ": "Increase Size",
		"size.decrease ": "Decrease Size",
		"Resize": "Resize",
		"Crop": "Crop",
		"Reset": "Reset",
		"Accept": "Accept",
		"Cancel": "Cancel",
		"height": "Height",
		"width": "Width",
		"button.toggle.tooltip": "Toggle keep aspect ratio",
		"field.img.src.label": "Source",
		"field.img.src.tooltip": "Source"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/*global documents: true define: true */
/*!
 * Aloha Editor
 * Author & Copyright (c) 2011 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed under the terms of http://www.aloha-editor.com/license.html
 * 
 * Author : Nicolas Karageuzian - http://nka.me
 */
define('image/image-floatingMenu',[
	'jquery',
    'util/class',
	'i18n!image/nls/i18n',
	'i18n!aloha/nls/i18n',
	'ui/ui',
	'ui/scopes',
    'ui/button',
    'ui/toggleButton',
    'ui/port-helper-attribute-field'
],
function (
	jQuery,
	Class,
	i18n,
	i18nCore,
	Ui,
	Scopes,
	Button,
	ToggleButton,
	AttributeField
) {
	

	var $ = jQuery;
	var GENTICS = window.GENTICS;
	var Aloha = window.Aloha;

	/**
     * Toolbar elements for Image plugin
     *
     * @class MyClass
     */
	return Class.extend({
        /**
         * Empty constructor
         *
         * @method
         * @constructor
         */
		_constructor: function () {
 
		},

         /**
          * Initialize Floating menu buttons according to plugin config
          */
        init: function (plugin) {
			plugin.floatingMenuControl = this;
			this.plugin = plugin;

			Scopes.createScope(plugin.name, 'Aloha.empty');

			this._addUIInsertButton();
			this._addUIMetaButtons();
			this._addUIResetButton();
			this._addUIAlignButtons();
			this._addUIMarginButtons();
			this._addUICropButtons();
			this._addUIResizeButtons();
			this._addUIAspectRatioToggleButton();

//			 TODO fix the function and reenable this button 
//			this._addNaturalSizeButton();
		},

		/**
		 * Adds the aspect ratio toggle button to the floating menu
		 */
		_addUIAspectRatioToggleButton: function () {
			var plugin = this.plugin;

			this._imageCnrRatioButton = Ui.adopt("imageCnrRatio", ToggleButton, {
				tooltip: i18n.t('button.toggle.tooltip'),
				icon: 'aloha-icon-cnr-ratio',
				scope: plugin.name,
				click: function(){
					plugin.toggleKeepAspectRatio();
				}
			});

			// If the setting has been set to a number or false we need to activate the 
			// toggle button to indicate that the aspect ratio will be preserved.
			if (plugin.settings.fixedAspectRatio !== false) {
				this._imageCnrRatioButton.setState(true);
				plugin.keepAspectRatio = true;
			}
		},
		
		/**
		 * Adds the reset button to the floating menu for the given tab 
		 */
		_addUIResetButton: function () {
			var plugin = this.plugin;

			this._imageCnrResetButton = Ui.adopt("imageCnrReset", Button, {
				tooltip: i18n.t('Reset'),
				icon: 'aloha-icon-cnr-reset',
				scope: plugin.name,
				click: function(){
					plugin.reset();
				}
			});
		},
		
		/**
		 * Adds the insert button to the floating menu
		 */
		_addUIInsertButton: function () {
			var plugin = this.plugin;

			this._insertImageButton = Ui.adopt("insertImage", Button, {
				tooltip: i18n.t('button.addimg.tooltip'),
				icon: 'aloha-button aloha-image-insert',
				scope: 'Aloha.continuoustext',
				click: function(){
					plugin.insertImg();
				}
			});
		},

        /**
         * Adds the ui meta fields (search, title) to the floating menu. 
         */
		_addUIMetaButtons: function () {
			var plugin = this.plugin;
			
			this.imgSrcField = AttributeField({
				label: i18n.t('field.img.src.label'),
				labelClass: 'aloha-image-input-label',
				tooltip: i18n.t('field.img.src.tooltip'),
				name: 'imageSource',
				scope: plugin.name
			});
			this.imgSrcField.setTemplate( '<span><b>{name}</b><br/>{url}</span>' );
			this.imgSrcField.setObjectTypeFilter(plugin.objectTypeFilter);
			
			this.imgTitleField = AttributeField({
				label: i18n.t('field.img.title.label'),
				labelClass: 'aloha-image-input-label',
				tooltip: i18n.t('field.img.title.tooltip'),
				name: 'imageTitle',
				scope: plugin.name
			});
			this.imgTitleField.setObjectTypeFilter();
		},
		
		/**
		 * Adds the ui align buttons to the floating menu
		 */
		_addUIAlignButtons: function () {
			var plugin = this.plugin;
		
			this._imageAlignLeftButton = Ui.adopt("imageAlignLeft", Button, {
				tooltip: i18n.t('button.img.align.left.tooltip'),
				icon: 'aloha-img aloha-image-align-left',
				scope: plugin.name,
				click : function () {
					var el = jQuery(plugin.getPluginFocus());
					el.add(el.parent()).css('float', 'left');
				}
			});
			
			this._imageAlignRightButton = Ui.adopt("imageAlignRight", Button, {
				tooltip: i18n.t('button.img.align.right.tooltip'),
				icon: 'aloha-img aloha-image-align-right',
				scope: plugin.name,
				click : function () {
					var el = jQuery(plugin.getPluginFocus());
					el.add(el.parent()).css('float', 'right');
				}
			});

			this._imageAlignNoneButton = Ui.adopt("imageAlignNone", Button, {
				tooltip: i18n.t('button.img.align.none.tooltip'),
				icon: 'aloha-img aloha-image-align-none',
				scope: plugin.name,
				click : function () {
					var el = jQuery(plugin.getPluginFocus());
					el.add(el.parent()).css({
						'float': 'none',
						display: 'inline-block'
					});
				}
			});
		},
		
		/**
		 * Adds the ui margin buttons to the floating menu
		 */
		_addUIMarginButtons: function () {
			var plugin = this.plugin;

			this._imageIncPaddingButton = Ui.adopt("imageIncPadding", Button, {
				tooltip: i18n.t('padding.increase'),
				icon: 'aloha-img aloha-image-padding-increase',
				scope: plugin.name,
				click: function () {
					jQuery(plugin.getPluginFocus()).increase('padding');
				}
			});
			
			this._imageDecPaddingButton = Ui.adopt("imageDecPadding", Button, {
				tooltip: i18n.t('padding.decrease'),
				icon: 'aloha-img aloha-image-padding-decrease',
				scope: plugin.name,
				click: function () {
					jQuery(plugin.getPluginFocus()).decrease('padding');
				}
			});
		},
		
		/**
		 * Adds the crop buttons to the floating menu
		 */		
		_addUICropButtons: function () {
			var plugin = this.plugin;

			Scopes.createScope('Aloha.img', ['Aloha.global']);

			this._imageCropButton = Ui.adopt("imageCropButton", ToggleButton, {
				tooltip: i18n.t('Crop'),
				icon: 'aloha-icon-cnr-crop',
				scope: plugin.name,
				click: function () {
					if (this.getState()) {
						plugin.crop();
					} else {
						plugin.endCrop();
					}
				}
			});
		},

        /**
         * Adds the resize buttons to the floating menu
         */	
		_addUIResizeButtons: function () {
			var plugin = this.plugin;

			// Manual resize fields
			this.imgResizeHeightField = AttributeField({
				label:  i18n.t('height'),
				labelClass: 'aloha-image-input-label',
				name: "imageResizeHeight",
				width: 50,
				scope: plugin.name
			});
			this.imgResizeHeightField.maxValue = plugin.settings.maxHeight;
			this.imgResizeHeightField.minValue = plugin.settings.minHeight;
			
			this.imgResizeWidthField = AttributeField({
				label:  i18n.t('width'),				
				labelClass: 'aloha-image-input-label',
				name: "imageResizeWidth",
				width: 50,
				scope: plugin.name
			});
			this.imgResizeWidthField.maxValue = plugin.settings.maxWidth;
			this.imgResizeWidthField.minValue = plugin.settings.minWidth;
 		},

		/**
		 * Adds the natural size button to the floating menu
		 */
		/*
		  TODO currently deactivated see TODO at call site above.
		_addNaturalSizeButton: function () {
			var plugin = this.plugin;

			this._imageNaturalSizeButton = Ui.adopt("imageNaturalSize", Button, {
				icon: 'aloha-img aloha-image-size-natural',
				label: i18n.t('size.natural'),
				scope: plugin.name,
				click: function () {
					plugin.resetSize();
				}
			});
		},
		*/

		/**
		 * Sets the scope
		 */
		setScope: function () {
			Scopes.setScope(this.plugin.name);
		},

		/**
		 * Redraws UI
		 */
		doLayout: function () {
			// Implementation was removed while porting this plugin to
			// the jqueryui toolbar because it seems to be a hack that
			// is not needed with the new implementation.
		}
    });
});
	

define('image/vendor/jcrop/jquery.jcrop.min',["jquery"], function(jQuery){
/**
 * Jcrop v.0.9.8 (minimized)
 * (c) 2008 Kelly Hallman and DeepLiquid.com
 * More information: http://deepliquid.com/content/Jcrop.html
 * Released under MIT License - this header must remain with code
 */


(function($){$.Jcrop=function(obj,opt)
{var obj=obj,opt=opt;if(typeof(obj)!=='object')obj=$(obj)[0];if(typeof(opt)!=='object')opt={};if(!('trackDocument'in opt))
{opt.trackDocument=$.browser.msie?false:true;if($.browser.msie&&$.browser.version.split('.')[0]=='8')
opt.trackDocument=true;}
if(!('keySupport'in opt))
opt.keySupport=$.browser.msie?false:true;var defaults={trackDocument:false,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:.6,borderOpacity:.4,handleOpacity:.5,handlePad:5,handleSize:9,handleOffset:5,edgeMargin:14,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,boxWidth:0,boxHeight:0,boundary:8,animationDelay:20,swingSpeed:3,allowSelect:true,allowMove:true,allowResize:true,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){}};var options=defaults;setOptions(opt);var $origimg=$(obj);var $img=$origimg.clone().removeAttr('id').css({position:'absolute'});$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);;if(options.addClass)$div.addClass(options.addClass);var $img2=$('<img />').attr('src',$img.attr('src')).css('position','absolute').width(boundx).height(boundy);var $img_holder=$('<div />').width(pct(100)).height(pct(100)).css({zIndex:310,position:'absolute',overflow:'hidden'}).append($img2);var $hdl_holder=$('<div />').width(pct(100)).height(pct(100)).css('zIndex',320);var $sel=$('<div />').css({position:'absolute',zIndex:300}).insertBefore($img).append($img_holder,$hdl_holder);var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var xlimit,ylimit,xmin,ymin;var xscale,yscale,enabled=true;var docOffset=getPos($img),btndown,lastcurs,dimmed,animating,shift_down;var Coords=function()
{var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos)
{var pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];};function setCurrent(pos)
{var pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];};function getOffset()
{return[ox,oy];};function moveOffset(offset)
{var ox=offset[0],oy=offset[1];if(0>x1+ox)ox-=ox+x1;if(0>y1+oy)oy-=oy+y1;if(boundy<y2+oy)oy+=boundy-(y2+oy);if(boundx<x2+ox)ox+=boundx-(x2+ox);x1+=ox;x2+=ox;y1+=oy;y2+=oy;};function getCorner(ord)
{var c=getFixed();switch(ord)
{case'ne':return[c.x2,c.y];case'nw':return[c.x,c.y];case'se':return[c.x2,c.y2];case'sw':return[c.x,c.y2];}};function getFixed()
{if(!options.aspectRatio)return getRect();var aspect=options.aspectRatio,min_x=options.minSize[0]/xscale,min_y=options.minSize[1]/yscale,max_x=options.maxSize[0]/xscale,max_y=options.maxSize[1]/yscale,rw=x2-x1,rh=y2-y1,rwa=Math.abs(rw),rha=Math.abs(rh),real_ratio=rwa/rha,xx,yy;if(max_x==0){max_x=boundx*10}
if(max_y==0){max_y=boundy*10}
if(real_ratio<aspect)
{yy=y2;w=rha*aspect;xx=rw<0?x1-w:w+x1;if(xx<0)
{xx=0;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}
else if(xx>boundx)
{xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}
else
{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0)
{yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}
else if(yy>boundy)
{yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
if(xx>x1){if(xx-x1<min_x){xx=x1+min_x;}else if(xx-x1>max_x){xx=x1+max_x;}
if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xx<x1){if(x1-xx<min_x){xx=x1-min_x}else if(x1-xx>max_x){xx=x1-max_x;}
if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
return last=makeObj(flipCoords(x1,y1,xx,yy));};function rebound(p)
{if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[p[0],p[1]];};function flipCoords(x1,y1,x2,y2)
{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2<x1)
{xa=x2;xb=x1;}
if(y2<y1)
{ya=y2;yb=y1;}
return[Math.round(xa),Math.round(ya),Math.round(xb),Math.round(yb)];};function getRect()
{var xsize=x2-x1;var ysize=y2-y1;if(xlimit&&(Math.abs(xsize)>xlimit))
x2=(xsize>0)?(x1+xlimit):(x1-xlimit);if(ylimit&&(Math.abs(ysize)>ylimit))
y2=(ysize>0)?(y1+ylimit):(y1-ylimit);if(ymin&&(Math.abs(ysize)<ymin))
y2=(ysize>0)?(y1+ymin):(y1-ymin);if(xmin&&(Math.abs(xsize)<xmin))
x2=(xsize>0)?(x1+xmin):(x1-xmin);if(x1<0){x2-=x1;x1-=x1;}
if(y1<0){y2-=y1;y1-=y1;}
if(x2<0){x1-=x2;x2-=x2;}
if(y2<0){y1-=y2;y2-=y2;}
if(x2>boundx){var delta=x2-boundx;x1-=delta;x2-=delta;}
if(y2>boundy){var delta=y2-boundy;y1-=delta;y2-=delta;}
if(x1>boundx){var delta=x1-boundy;y2-=delta;y1-=delta;}
if(y1>boundy){var delta=y1-boundy;y2-=delta;y1-=delta;}
return makeObj(flipCoords(x1,y1,x2,y2));};function makeObj(a)
{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};};return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}();var Selection=function()
{var start,end,dragmode,awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;if(options.drawBorders){borders={top:insertBorder('hline').css('top',$.browser.msie?px(-1):px(0)),bottom:insertBorder('hline'),left:insertBorder('vline'),right:insertBorder('vline')};}
if(options.dragEdges){handle.t=insertDragbar('n');handle.b=insertDragbar('s');handle.r=insertDragbar('e');handle.l=insertDragbar('w');}
options.sideHandles&&createHandles(['n','s','e','w']);options.cornerHandles&&createHandles(['sw','nw','ne','se']);function insertBorder(type)
{var jq=$('<div />').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;};function dragDiv(ord,zi)
{var jq=$('<div />').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi});$hdl_holder.append(jq);return jq;};function insertHandle(ord)
{return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).addClass(cssClass('handle'));};function insertDragbar(ord)
{var s=options.handleSize,o=hhs,h=s,w=s,t=o,l=o;switch(ord)
{case'n':case's':w=pct(100);break;case'e':case'w':h=pct(100);break;}
return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});};function createHandles(li)
{for(i in li)handle[li[i]]=insertHandle(li[i]);};function moveHandles(c)
{var midvert=Math.round((c.h/2)-hhs),midhoriz=Math.round((c.w/2)-hhs),north=west=-hhs+1,east=c.w-hhs,south=c.h-hhs,x,y;'e'in handle&&handle.e.css({top:px(midvert),left:px(east)})&&handle.w.css({top:px(midvert)})&&handle.s.css({top:px(south),left:px(midhoriz)})&&handle.n.css({left:px(midhoriz)});'ne'in handle&&handle.ne.css({left:px(east)})&&handle.se.css({top:px(south),left:px(east)})&&handle.sw.css({top:px(south)});'b'in handle&&handle.b.css({top:px(south)})&&handle.r.css({left:px(east)});};function moveto(x,y)
{$img2.css({top:px(-y),left:px(-x)});$sel.css({top:px(y),left:px(x)});};function resize(w,h)
{$sel.width(w).height(h);};function refresh()
{var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();};function updateVisible()
{if(awake)return update();};function update()
{var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);options.drawBorders&&borders['right'].css({left:px(c.w-1)})&&borders['bottom'].css({top:px(c.h-1)});seehandles&&moveHandles(c);awake||show();options.onChange(unscale(c));};function show()
{$sel.show();$img.css('opacity',options.bgOpacity);awake=true;};function release()
{disableHandles();$sel.hide();$img.css('opacity',1);awake=false;};function showHandles()
{if(seehandles)
{moveHandles(Coords.getFixed());$hdl_holder.show();}};function enableHandles()
{seehandles=true;if(options.allowResize)
{moveHandles(Coords.getFixed());$hdl_holder.show();return true;}};function disableHandles()
{seehandles=false;$hdl_holder.hide();};function animMode(v)
{(animating=v)?disableHandles():enableHandles();};function done()
{animMode(false);refresh();};var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360})
$img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,done:done};}();var Tracker=function()
{var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;if(!trackDoc)
{$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);}
function toFront()
{$trk.css({zIndex:450});if(trackDoc)
{$(document).mousemove(trackMove).mouseup(trackUp);}}
function toBack()
{$trk.css({zIndex:290});if(trackDoc)
{$(document).unbind('mousemove',trackMove).unbind('mouseup',trackUp);}}
function trackMove(e)
{onMove(mouseAbs(e));};function trackUp(e)
{e.preventDefault();e.stopPropagation();if(btndown)
{btndown=false;onDone(mouseAbs(e));options.onSelect(unscale(Coords.getFixed()));toBack();onMove=function(){};onDone=function(){};}
return false;};function activateHandlers(move,done)
{btndown=true;onMove=move;onDone=done;toFront();return false;};function setCursor(t){$trk.css('cursor',t);};$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}();var KeyManager=function()
{var $keymgr=$('<input type="radio" />').css({position:'absolute',left:'-30px'}).keypress(parseKey).blur(onBlur),$keywrap=$('<div />').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
{if(options.keySupport)
{$keymgr.show();$keymgr.focus();}};function onBlur(e)
{$keymgr.hide();};function doNudge(e,x,y)
{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible();};e.preventDefault();e.stopPropagation();};function parseKey(e)
{if(e.ctrlKey)return true;shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode)
{case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:Selection.release();break;case 9:return true;}
return nothing(e);};if(options.keySupport)$keywrap.insertBefore($img);return{watchKeys:watchKeys};}();function px(n){return''+parseInt(n)+'px';};function pct(n){return''+parseInt(n)+'%';};function cssClass(cl){return options.baseClass+'-'+cl;};function getPos(obj)
{var pos=$(obj).offset();return[pos.left,pos.top];};function mouseAbs(e)
{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];};function myCursor(type)
{if(type!=lastcurs)
{Tracker.setCursor(type);lastcurs=type;}};function startDragMode(mode,pos)
{docOffset=getPos($img);Tracker.setCursor(mode=='move'?mode:mode+'-resize');if(mode=='move')
return Tracker.activateHandlers(createMover(pos),doneSelect);var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);};function dragmodeHandler(mode,f)
{return function(pos){if(!options.aspectRatio)switch(mode)
{case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}
else switch(mode)
{case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}
Coords.setCurrent(pos);Selection.update();};};function createMover(pos)
{var lloc=pos;KeyManager.watchKeys();return function(pos)
{Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};};function oppLockCorner(ord)
{switch(ord)
{case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';};};function createDragger(ord)
{return function(e){if(options.disabled)return false;if((ord=='move')&&!options.allowMove)return false;btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};};function presize($obj,w,h)
{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0)
{nw=w;nh=(w/$obj.width())*$obj.height();}
if((nh>h)&&h>0)
{nh=h;nw=(h/$obj.height())*$obj.width();}
xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);};function unscale(c)
{return{x:parseInt(c.x*xscale),y:parseInt(c.y*yscale),x2:parseInt(c.x2*xscale),y2:parseInt(c.y2*yscale),w:parseInt(c.w*xscale),h:parseInt(c.h*yscale)};};function doneSelect(pos)
{var c=Coords.getFixed();if(c.w>options.minSelect[0]&&c.h>options.minSelect[1])
{Selection.enableHandles();Selection.done();}
else
{Selection.release();}
Tracker.setCursor(options.allowSelect?'crosshair':'default');};function newSelection(e)
{if(options.disabled)return false;if(!options.allowSelect)return false;btndown=true;docOffset=getPos($img);Selection.disableHandles();myCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();Selection.update();e.stopPropagation();e.preventDefault();return false;};function selectDrag(pos)
{Coords.setCurrent(pos);Selection.update();};function newTracker()
{var trk=$('<div></div>').addClass(cssClass('tracker'));$.browser.msie&&trk.css({opacity:0,backgroundColor:'white'});return trk;};function animateTo(a)
{var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating)return;var animto=Coords.flipCoords(x1,y1,x2,y2);var c=Coords.getFixed();var animat=initcr=[c.x,c.y,c.x2,c.y2];var interv=options.animationDelay;var x=animat[0];var y=animat[1];var x2=animat[2];var y2=animat[3];var ix1=animto[0]-initcr[0];var iy1=animto[1]-initcr[1];var ix2=animto[2]-initcr[2];var iy2=animto[3]-initcr[3];var pcent=0;var velocity=options.swingSpeed;Selection.animMode(true);var animator=function()
{return function()
{pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent<100)animateStart();else Selection.done();if(pcent>=99.8)pcent=100;setSelectRaw(animat);};}();function animateStart()
{window.setTimeout(animator,interv);};animateStart();};function setSelect(rect)
{setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);};function setSelectRaw(l)
{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();};function setOptions(opt)
{if(typeof(opt)!='object')opt={};options=$.extend(options,opt);if(typeof(options.onChange)!=='function')
options.onChange=function(){};if(typeof(options.onSelect)!=='function')
options.onSelect=function(){};};function tellSelect()
{return unscale(Coords.getFixed());};function tellScaled()
{return Coords.getFixed();};function setOptionsNew(opt)
{setOptions(opt);interfaceUpdate();};function disableCrop()
{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');};function enableCrop()
{options.disabled=false;interfaceUpdate();};function cancelCrop()
{Selection.done();Tracker.activateHandlers(null,null);};function destroy()
{$div.remove();$origimg.show();};function interfaceUpdate(alt)
{options.allowResize?alt?Selection.enableOnly():Selection.enableHandles():Selection.disableHandles();Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');$div.css('backgroundColor',options.bgColor);if('setSelect'in options){setSelect(opt.setSelect);Selection.done();delete(options.setSelect);}
if('trueSize'in options){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if('outerImage'in options)
{$img.attr('src',options.outerImage);delete(options.outerImage);}
Selection.refresh();};$hdl_holder.hide();interfaceUpdate(true);var api={animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},release:Selection.release,destroy:destroy};$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options)
{function attachWhenDone(from)
{var loadsrc=options.useImg||from.src;var img=new Image();img.onload=function(){$.Jcrop(from,options);};img.src=loadsrc;};if(typeof(options)!=='object')options={};this.each(function()
{if($(this).data('Jcrop'))
{if(options=='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
else attachWhenDone(this);});return this;};})(jQuery);
});

/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
 * Licensed under the MIT License (LICENSE.txt).
 *
 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
 * Thanks to: Seamus Leahy for adding deltaX and deltaY
 *
 * Version: 3.0.6
 * 
 * Requires: 1.2.2+
 */
define('image/vendor/mousewheel/mousewheel',['jquery'],function(jQuery){
(function($) {

var types = ['DOMMouseScroll', 'mousewheel'];

if ($.event.fixHooks) {
    for ( var i=types.length; i; ) {
        $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
    }
}

$.event.special.mousewheel = {
    setup: function() {
        if ( this.addEventListener ) {
            for ( var i=types.length; i; ) {
                this.addEventListener( types[--i], handler, false );
            }
        } else {
            this.onmousewheel = handler;
        }
    },
    
    teardown: function() {
        if ( this.removeEventListener ) {
            for ( var i=types.length; i; ) {
                this.removeEventListener( types[--i], handler, false );
            }
        } else {
            this.onmousewheel = null;
        }
    }
};

$.fn.extend({
    mousewheel: function(fn) {
        return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
    },
    
    unmousewheel: function(fn) {
        return this.unbind("mousewheel", fn);
    }
});


function handler(event) {
    var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
    event = $.event.fix(orgEvent);
    event.type = "mousewheel";
    
    // Old school scrollwheel delta
    if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
    if ( orgEvent.detail     ) { delta = -orgEvent.detail/3; }
    
    // New school multidimensional scroll (touchpads) deltas
    deltaY = delta;
    
    // Gecko
    if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
        deltaY = 0;
        deltaX = -1*delta;
    }
    
    // Webkit
    if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
    if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
    
    // Add event and delta to the front of the arguments
    args.unshift(event, delta, deltaX, deltaY);
    
    return ($.event.dispatch || $.event.handle).apply(this, args);
}

})(jQuery);
});

/*global documents: true define: true*/
/*
* Aloha Image Plugin - Allow image manipulation in Aloha Editor
*
* Author & Copyright (c) 2013 Gentics Software GmbH
* aloha-sales@gentics.com
* Contributors
*		Johannes Schüth - http://jotschi.de
*		Nicolas karageuzian - http://nka.me/
*		Benjamin Athur Lupton - http://www.balupton.com/
*		Thomas Lete
*		Nils Dehl
*		Christopher Hlubek
*		Edward Tsech
*		Haymo Meran
*		Martin Schönberger
*
* Licensed under the terms of http://www.aloha-editor.org/license.php
*/

define('image/image-plugin',[
	// js
	'jquery',
	'aloha/plugin',
	'image/image-floatingMenu',
	'i18n!aloha/nls/i18n',
	'i18n!image/nls/i18n',
	'jqueryui',
	'image/vendor/jcrop/jquery.jcrop.min',
	'image/vendor/mousewheel/mousewheel'
], function AlohaImagePlugin(
	aQuery,
	Plugin,
	ImageFloatingMenu,
	i18nCore,
	i18n
){

	

	var jQuery = aQuery;
	var $ = aQuery;
	var GENTICS = window.GENTICS;
	var Aloha = window.Aloha;
	var resizing = false;

	var aspectRatioValue = false;
	var cropRatioValue = false;

	// Attributes manipulation utilities
	// Aloha team may want to factorize, it could be useful for other plugins
	// Prototypes
	String.prototype.toInteger = String.prototype.toInteger || function () {
		return parseInt(String(this).replace(/px$/, '') || 0, 10);
	};
	String.prototype.toFloat = String.prototype.toInteger || function () {
		return parseFloat(String(this).replace(/px$/, '') || 0, 10);
	};
	Number.prototype.toInteger = Number.prototype.toInteger || String.prototype.toInteger;
	Number.prototype.toFloat = Number.prototype.toFloat || String.prototype.toFloat;

	// Insert jQuery Prototypes
	jQuery.extend(true, jQuery.fn, {
		increase: jQuery.fn.increase || function (attr) {
			var	obj = jQuery(this), value, newValue;
			if (!obj.length) {
				return obj;
			}
			value = obj.css(attr).toFloat();
			newValue = Math.round((value || 1) * 1.2);
			// when value is 2, won't increase
			if (value === newValue) {
				newValue++;
			}
			obj.css(attr, newValue);
			return obj;
		},
		decrease: jQuery.fn.decrease || function (attr) {
			var	obj = jQuery(this), value, newValue;
			// Check
			if (!obj.length) {
				return obj;
			}
			// Calculate
			value = obj.css(attr).toFloat();
			newValue = Math.round((value || 0) * 0.8);
			// Apply
			if (value === newValue && newValue > 0) {
				// when value is 2, won't increase
				newValue--;
			}
			obj.css(attr, newValue);
			// Chain
			return obj;
		}
	});

	// Create and register Image Plugin
	return Plugin.create('image', {
		defaultSettings: {
			'maxWidth': 1600,
			'minWidth': 3,
			'maxHeight': 1200,
			'minHeight': 3,
			// This setting will manually correct values that are out of bounds
			'autoCorrectManualInput': true,
			// This setting will define a fixed aspect ratio for all resize actions
			'fixedAspectRatio' : false,
			// When enabled this setting will order the plugin to automatically resize images to given bounds
			'autoResize': false,
			//Image manipulation options - ONLY in default config section
			ui: {
				meta		: true, // If imageResizeWidth and imageResizeHeight are displayed, then you will want to set this to true, so that the width and height text fields are updated automatically.
				crop		: true, // If imageCropButton is displayed, then you have to enable this.
				resizable	: true	// Resizable ui-drag image
			},
			handles     : 'ne, se, sw, nw',   // set handles for resize

			/**
			 * Crop callback is triggered after the user clicked accept to accept his crop
			 * @param image jquery image object reference
			 * @param props cropping properties
			 */
			onCropped: function ($image, props) {
				Aloha.Log.info('Default onCropped invoked', $image, props);
			},

			/**
			 * Reset callback is triggered before the internal reset procedure is applied
			 * if this function returns true, then the reset has been handled by the callback
			 * which means that no other reset will be applied
			 * if false is returned the internal reset procedure will be applied
			 * @param image jquery image object reference
			 * @return true if a reset has been applied, false otherwise
			 */
			onReset: function ($image) {
				Aloha.Log.info('Default onReset invoked', $image);
				return false;
			},

			/**
			 * Example callback method which gets called while the resize process is being executed.
			 */
			onResize: function ($image) {
				Aloha.Log.info('Default onResize invoked', $image);
			},

			/**
			 * Resize callback is triggered after the internal resize procedure is applied.
			 */
			onResized: function ($image) {
				Aloha.Log.info('Default onResized invoked', $image);
			}
		},

		/**
		 * Internal callback hook which gets invoked when cropping has been finished
		 */
		_onCropped: function ($image, props) {

			$('body').trigger('aloha-image-cropped', [$image, props]);

			// After successful cropping, the aspect ratio value has to be recalculated
			if (this.keepAspectRatio) {
				aspectRatioValue = this.imageObj.width() / this.imageObj.height();
			}

			// Call the custom onCropped function
			this.onCropped($image, props);
		},

		/**
		 * Internal callback hook which gets invoked when resetting images
		 */
		_onReset: function ($image) {

			// No default behaviour defined besides event triggering
			$('body').trigger('aloha-image-reset', $image);

			// Call the custom reset function
			return this.onReset($image);
		},

		/**
		 * Internal callback hook which gets invoked while the image is being resized
		 */
		_onResize: function ($image) {

			// No default behaviour defined besides event triggering
			$('body').trigger('aloha-image-resize', $image);

			this._applyValuesToFields($image.width(), $image.height());

			// Call the custom resize function
			this.onResize($image);
		},

		/**
		 * Internal callback hook which gets invoked when the current resizing action has stopped
		 */
		_onResized: function ($image) {

			$('body').trigger('aloha-image-resized', $image);

			this._applyValuesToFields($image.width(), $image.height());

			// Call the custom resize function
			this.onResized($image);
		},

		/**
		 * The image that is currently edited
		 */
		imageObj: null,


		/**
		 * The Jcrop API reference
		 * this is needed to be able to destroy the cropping frame later on
		 * the variable is linked to the api object whilst cropping, or set to null otherwise
		 * strange, but done as documented http://deepliquid.com/content/Jcrop_API.html
		 */
		jcAPI: null,


		/**
		 * State variable for the aspect ratio toggle feature
		 */
		keepAspectRatio: false,

		/**
		 * This will contain an image's original properties to be able to undo previous settings
		 *
		 * when an image is clicked for the first time, a new object will be added to the array
		 * {
		 *		obj : [the image object reference],
		 *		src : [the original src url],
		 *		width : [initial width],
		 *		height : [initial height]
		 * }
		 *
		 * when an image is clicked the second time, the array will be checked for the image object
		 * reference, to prevent double entries
		 */
		restoreProps: [],

		/**
		 * the defined object types to be used for this instance
		 */
		objectTypeFilter: [],

		/**
		 * Plugin initialization method
		 */
		init: function () {

			var plugin = this;

			var imagePluginUrl = Aloha.getPluginUrl('image');

			if ( typeof this.settings.objectTypeFilter != 'undefined' ) {
				this.objectTypeFilter = this.settings.objectTypeFilter;
			}

			// Extend the default settings with the custom ones (done by default)
			plugin.config = plugin.defaultSettings;
			plugin.settings = jQuery.extend(true, plugin.defaultSettings, plugin.settings);

			// Determine the flag and the value of the aspect ratio depending on settings
			if ( typeof this.settings.fixedAspectRatio === 'number' ) {
				aspectRatioValue = cropRatioValue = this.settings.fixedAspectRatio;
				plugin.keepAspectRatio = true;
			} else {
				if ((plugin.settings.fixedAspectRatio) === true) {
					plugin.keepAspectRatio = true;
				} else {
					plugin.keepAspectRatio = false;
				}
			}

			plugin.initializeUI();
			plugin.bindInteractions();
			plugin.subscribeEvents();

		},

		/**
		* Create buttons
		*/
		initializeUI: function () {

			var that = this;

			this.ui = new ImageFloatingMenu();
			this.ui.init(this);
		},
		/**
		 * Bind plugin interactions
		 */
		bindInteractions: function () {
			var	plugin = this;

			if (plugin.settings.ui.resizable) {
				try {
					// this will disable mozillas image resizing facilities
					document.execCommand('enableObjectResizing', false, false);
				} catch (e) {
					Aloha.Log.info(e, 'Could not disable enableObjectResizing');
					// this is just for internet explorer, which will not support disabling enableObjectResizing
				}
			}

			if (plugin.settings.ui.meta) {
				// update image object when src changes
				plugin.ui.imgSrcField.addListener('keyup', function (event) {
					plugin.srcChange();
				});

				plugin.ui.imgSrcField.addListener('blur', function (event) {
					// TODO remove image or do something useful if the user leaves the
					// image without defining a valid image src
					var img = jQuery(plugin.ui.imgSrcField.getTargetObject());
					if (img.attr('src') === '') {
						img.remove();
					} // image removal when src field is blank
				});
			}

			// Override the default method by using the given one
			if (plugin.settings.onCropped && typeof plugin.settings.onCropped === "function") {
				plugin.onCropped = plugin.settings.onCropped;
			}

			// Override the default method by using the given one
			if (plugin.settings.onReset && typeof plugin.settings.onReset === "function") {
				plugin.onReset = plugin.settings.onReset;
			}

			// Override the default method by using the given one
			if (plugin.settings.onResized && typeof plugin.settings.onResized === "function") {
				plugin.onResized = plugin.settings.onResized;
			}

			// Override the default method by using the given one
			if (plugin.settings.onResize && typeof plugin.settings.onResize === "function") {
				plugin.onResize = this.settings.onResize;
			}

		},

		/**
		 * Subscribe to Aloha events and DragAndDropPlugin Event
		 */
		subscribeEvents: function () {
			var	plugin = this;
			var config = this.settings;

			jQuery('img').filter(config.globalselector).unbind();
			jQuery('img').filter(config.globalselector).click(function (event) {
				plugin.clickImage(event);
			});

			Aloha.bind('aloha-drop-files-in-editable', function (event, data) {
				var img, len = data.filesObjs.length, fileObj, config;

				while (--len >= 0) {
					fileObj = data.filesObjs[len];
					if (fileObj.file.type.match(/image\//)) {
						config = plugin.getEditableConfig(data.editable);
						// Prepare
						img = jQuery('<img/>');
						img.css({
							"max-width": that.maxWidth,
							"max-height": that.maxHeight
						});
						img.attr('id', fileObj.id);
						if (typeof fileObj.src === 'undefined') {
							img.attr('src', fileObj.data);
							//fileObj.src = fileObj.data ;
						} else {
							img.attr('src', fileObj.src);
						}
						GENTICS.Utils.Dom.insertIntoDOM(img, data.range, jQuery(Aloha.activeEditable.obj));
					}
				}

			});
			/*
			 * Add the event handler for selection change
			 */
			Aloha.bind('aloha-selection-changed', function (event, rangeObject, originalEvent) {
				var config, foundMarkup;

				if (originalEvent && originalEvent.target) {
					// Check if the element is currently being resized
					if (plugin.settings.ui.resizable && !jQuery(originalEvent.target).hasClass('ui-resizable-handle')) {
						plugin.endResize();
						plugin.imageObj = null;
						Aloha.trigger('aloha-image-unselected');
					}
				}

				if (Aloha.activeEditable !== null) {
					foundMarkup = plugin.findImgMarkup(rangeObject);
					config = plugin.getEditableConfig(Aloha.activeEditable.obj);

					if (typeof config !== 'undefined') {
						plugin.ui._insertImageButton.show();
					} else {
						plugin.ui._insertImageButton.hide();
						return;
					}

					// Enable image specific ui components if the element is an image
					if (foundMarkup) { // TODO : this is always null (below is dead code, moving it to clickImage)
						plugin.ui._insertImageButton.show();
						plugin.ui.setScope();
						if (plugin.settings.ui.meta) {
							plugin.ui.imgSrcField.setTargetObject(foundMarkup, 'src');
							plugin.ui.imgTitleField.setTargetObject(foundMarkup, 'title');
						}
						plugin.ui.imgSrcField.foreground();
						plugin.ui.imgSrcField.focus();
					} else {
						if (plugin.settings.ui.meta) {
							plugin.ui.imgSrcField.setTargetObject(null);
						}
					}
					// TODO this should not be necessary here!
					plugin.ui.doLayout();
				}

			});

			Aloha.bind('aloha-editable-created', function (event, editable) {
				try {
					// this disables mozillas image resizing facilities
					document.execCommand('enableObjectResizing', false, false);
				} catch (e) {
					Aloha.Log.info(e, 'Could not disable enableObjectResizing');
					// this is just for other browsers, which do not support disabling enableObjectResizing
				}

				// Inital click on images will be handled here
				// editable.obj.find('img').attr('_moz_resizing', false);
				// editable.obj.find('img').contentEditable(false);

				editable.obj.delegate('img', 'mouseup', function (event) {
					if (!resizing) {
						plugin.clickImage(event);
						event.stopPropagation();
					}
				});
			});

			plugin._subscribeToResizeFieldEvents();

		},


		/**
		 * Automatically resize the image to fit into defined bounds.
		 * @param doScaleUp if true, small images are scaled up to fit minimum size
		 */
		autoResize: function(doScaleUp) {
			// @todo handle ratio mismatches (eg 4:3 is set but image is 16:9 --> image needs to be cut)

			var that = this;

			var widthField = jQuery("#" + that.ui.imgResizeWidthField.getInputId());
			var heightField = jQuery("#" + that.ui.imgResizeHeightField.getInputId());

			var width = that.imageObj.width();
			var height = that.imageObj.height();
			var resize = false;

			// Only normalize the field values if the image exceeds the defined bounds
			if (width < that.settings.minWidth ||
				width > that.settings.maxWidth ||
				height < that.settings.minHeight ||
				height > that.settings.maxHeight) {
				resize = true;
			}

			var aspectRatio = width / height;

			if (resize) {
				if (width > that.settings.maxWidth) {
					width = that.settings.maxWidth;
					height = width / aspectRatio;
				}

				if (height > that.settings.maxHeight) {
					height = that.settings.maxHeight;
					width = height * aspectRatio;
				}

				if ((width < that.settings.minWidth) && doScaleUp) {
					width = that.settings.minWidth;
					height = width / aspectRatio;
				}

				if ((height < that.settings.minHeight) && doScaleUp) {
					height = that.settings.minHeight;
					width = width * aspectRatio;
				}

				that._applyValuesToFields(width, height);

				that.setSizeByFieldValue();
				return true;
			}
			return false;
		},

		/**
		 * Toggle the keep aspect ratio functionality
		 */
		toggleKeepAspectRatio: function() {

			this.keepAspectRatio = !this.keepAspectRatio;

			// while cropping: calculate the new aspect ratio value for the crop
			if (typeof this.jcAPI !== 'undefined' && this.jcAPI !== null) {
				var selection = this.jcAPI.tellSelect();

				if (!this.keepAspectRatio) {
					cropRatioValue = false;
				} else {
					if (typeof this.settings.fixedAspectRatio !== 'number') {
						if (selection.w / selection.h > 0) {
							cropRatioValue = selection.w / selection.h;
						} else {
							cropRatioValue = this.imageObj.width() / this.imageObj.height();
						}
					} else {
						cropRatioValue = this.settings.fixedAspectRatio;
					}
				}

				this.jcAPI.setOptions(this.keepAspectRatio ? { aspectRatio: cropRatioValue} : { aspectRatio: 0 });
				this.jcAPI.focus();

			// if not in cropping mode: calculate the new aspect ratio value for image resizing
			} else {
				this.endResize();
				if (!this.keepAspectRatio) {
					aspectRatioValue = false;
				} else {
					if ( typeof this.settings.fixedAspectRatio !== 'number' ) {
						aspectRatioValue = this.imageObj.width() / this.imageObj.height();
					} else {
						aspectRatioValue = this.settings.fixedAspectRatio;
					}
				}
				this.startResize();
			}
		},

		/**
		 * Bind interaction events that are invoked on the resize fields
		 */
		_subscribeToResizeFieldEvents: function () {
			var plugin = this;

			/**
			 * Handle the keyup event on the field
			 */
			function handleKeyUpEventOnField(e) {

				// Load the max/min from the data properties of this event
				var minValue = e.data.minValue;
				var maxValue = e.data.maxValue;
				var fieldName = e.data.fieldName;

				// Allow backspace and delete
				if (e.keyCode === 8 || e.keyCode === 46) {

					// Only resize if field values are ok
					if (plugin._updateFields(fieldName, $(this).val(), false)) {
						// Check if we are currently in cropping mode
						if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
							plugin.setCropAreaByFieldValue();
						} else {
							plugin.setSizeByFieldValue();
						}
					}
				// 0-9 keys
				} else if (e.keyCode <= 57 && e.keyCode >= 48 || e.keyCode <= 105 && e.keyCode >= 96 ) {

					// Only resize if field values are ok
					if (plugin._updateFields(fieldName, $(this).val(), false)) {
						// Check if we are currently in cropping mode
						if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
							plugin.setCropAreaByFieldValue();
						} else {
							plugin.setSizeByFieldValue();
						}
					}
				} else {
					var delta = 0;
					if (e.keyCode === 38 || e.keyCode === 107) {
						delta = +1;
					} else if (e.keyCode === 40 || e.keyCode === 109) {
						delta = -1;
					}
					// Handle key combinations
					if (e.shiftKey || e.metaKey || e.ctrlKey) {
						delta = delta * 10;
					}

					var isDecrement = false;
					if (delta < 0) {
						isDecrement = true;
					}
					var newValue = parseInt($(this).val(), 10) + delta;

					// Only resize if field values are ok
					if (plugin._updateFields(fieldName, newValue, isDecrement)) {
						// Check if we are currently in cropping mode
						if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
							plugin.setCropAreaByFieldValue();
						} else {
							plugin.setSizeByFieldValue();
						}
					}
				}

				e.preventDefault();
				return false;
			}

			/**
			 * Handle the mouse wheel event on the field
			 */
			function handleMouseWheelEventOnField(e, delta) {
				var minValue = e.data.minValue;
				var maxValue = e.data.maxValue;
				var fieldName = e.data.fieldName;

				// Handle key combinations
				if (e.shiftKey || e.metaKey || e.ctrlKey) {
					delta = delta * 10;
				}

				var newValue = parseInt($(this).val(), 10) + delta;
				var decrement = false;
				if (delta < 0) {
					decrement = true;
				}

				// Only resize if field values are ok
				if (plugin._updateFields(fieldName, newValue, decrement)) {
					// Check if we are currently in cropping mode
					if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
						plugin.setCropAreaByFieldValue();
					} else {
						plugin.setSizeByFieldValue();
					}
				}
				return false;
			}

			/**
			 * Handle mousewheel,keyup actions on both fields
			 */
			var $heightField = $('#' + plugin.ui.imgResizeHeightField.getInputId());
			var heightEventData = {fieldName: 'height', maxValue: plugin.ui.imgResizeHeightField.maxValue, minValue: plugin.ui.imgResizeHeightField.minValue };
			$heightField.live('keyup', heightEventData, handleKeyUpEventOnField);
			$heightField.live('mousewheel', heightEventData, handleMouseWheelEventOnField);

			var $widthField = $('#' + plugin.ui.imgResizeWidthField.getInputId());
			var widthEventData = {fieldName: 'width', maxValue: plugin.ui.imgResizeWidthField.maxValue, minValue: plugin.ui.imgResizeWidthField.minValue};
			$widthField.live('keyup', widthEventData, handleKeyUpEventOnField);
			$widthField.live('mousewheel', widthEventData, handleMouseWheelEventOnField);

		},

		/**
		 * Manually set the given size for the current image
		 */
		setSize: function (width, height) {

			var plugin = this;
			plugin.imageObj.width(width);
			plugin.imageObj.height(height);
			var $wrapper = plugin.imageObj.closest('.Aloha_Image_Resize');
			$wrapper.height(height);
			$wrapper.width(width);

			plugin._onResize(plugin.imageObj);
			plugin._onResized(plugin.imageObj);
		},

		/**
		 * This method will handle the mouseUp event on images (eg. within editables).
		 * It will if enabled activate the resizing action.
		 */
		clickImage: function (e) {

			var plugin = this;
			plugin.endResize(); // removes previous resize handler
			plugin.imageObj = jQuery(e.target);
			var currentImage = plugin.imageObj;

			// Ignore any images that are part of the ui (e.g. block edit and delete icons)
			if (currentImage.hasClass('aloha-ui')) {
				return;
			}

			plugin.ui.setScope();

			var editable = currentImage.closest('.aloha-editable');

			plugin.ui._imageCnrRatioButton.setState(this.keepAspectRatio);

			// Disabling the content editable. This will disable the resizeHandles in internet explorer
			// already done in resize on a smaller scope, this block next aloha-selection-change event
			// to be thrown
			// editable.contentEditable(false);

			// Store the current props of the image
			plugin.restoreProps.push({
				obj : plugin.imageObj,
				src : plugin.imageObj.attr('src'),
				width : plugin.imageObj.width(),
				height : plugin.imageObj.height()
			});

			// Update the resize input fields with the new width and height
			plugin._applyValuesToFields(plugin.imageObj.width(), plugin.imageObj.height());

			if (plugin.settings.ui.meta) {
				plugin.ui.imgSrcField.setTargetObject(plugin.imageObj, 'src');
				plugin.ui.imgTitleField.setTargetObject(plugin.imageObj, 'title');
			}
			Aloha.Selection.preventSelectionChanged();
			try {
				plugin.ui.imgSrcField.focus();
			} catch(e) {
				// FIXME for some reason execution breaks at this point
			}

			//to handle switching between images, aspect ratio is recalculated on click
			cropRatioValue = aspectRatioValue = plugin.imageObj.width() / plugin.imageObj.height();

			if (plugin.settings.ui.resizable) {
				plugin.startResize();
			}

			if (plugin.settings.autoResize) {
				plugin.autoResize(true);
			}
			Aloha.Selection.preventSelectionChangedFlag = false;
			Aloha.trigger('aloha-image-selected');
		},

		/**
		 * This method extracts determins if the range selection contains an image
		 *
		 * UNUSED as long as clickImage don't change the selection
		 * @see getPluginFocus instead
		 */
		findImgMarkup: function (range) {

			var plugin = this;
			var config = this.config;
			var result, targetObj;

			if (typeof range === 'undefined') {
				range = Aloha.Selection.getRangeObject();
			}

			targetObj = jQuery(range.startContainer);

			try {
				if (Aloha.activeEditable) {
					if ((typeof range.startContainer !== 'undefined' &&
						typeof range.startContainer.childNodes !== 'undefined' &&
						typeof range.startOffset !== 'undefined' &&
						typeof range.startContainer.childNodes[range.startOffset] !== 'undefined' &&
						range.startContainer.childNodes[range.startOffset].nodeName.toLowerCase() === 'img' &&
						range.startOffset + 1 === range.endOffset) ||
						(targetObj.hasClass('Aloha_Image_Resize')))
					{
						result = targetObj.find('img')[0];
						if (! result.css) {
							result.css = '';
						}

						if (! result.title) {
							result.title = '';
						}

						if (! result.src) {
							result.src = '';
						}
						return result;
					}
					else {
						return null;
					}
				}
			} catch (e) {
				Aloha.Log.debug(e, "Error finding img markup.");
			}
			return null;
		},
		/**
		 * Gets the plugin focus target
		 */
		getPluginFocus: function () {
			return this.imageObj;
		},

		/**
		 * Helper function that checks a field-input (of height or width), adjusts
		 * its value to conform to minimum/maximum values, and corrects the other field
		 * if the aspect ratio is to be kept
		 * @param primaryFieldName the field which is currently edited ('height' or 'width')
		 * @param newValue the value as it was entered into the primary field
		 * @param isDecrement true if value is being decreased (by key or mousewheel); prevents decreasing below minimum
		 * @return true if values are correct or have been corrected, and image can be resized
		 */
		_updateFields: function (primaryFieldName, newValue, isDecrement) {
			var plugin = this;

			var $primaryField = null;
			var $secondaryField = null;

			var adjustedAspectRatio = null;
			var primaryMin = null;
			var primaryMax = null;
			var secondaryMin = null;
			var secondaryMax = null;

			// If the aspect-ratio for cropping has been changed from the standard aspect-ratio,
			// it is used for updating the fields (if keeping ratio is activated)
			if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
				adjustedAspectRatio = cropRatioValue ? cropRatioValue : aspectRatioValue;
			} else {
				adjustedAspectRatio = aspectRatioValue;
			}

			// Depending on the field that is edited, primary and secondary values are set
			if (primaryFieldName == 'width') {
				$primaryField = $("#" + plugin.ui.imgResizeWidthField.getInputId());
				$secondaryField = $("#" + plugin.ui.imgResizeHeightField.getInputId());
				adjustedAspectRatio = ( 1 / adjustedAspectRatio );

				primaryMin = plugin.settings.minWidth;
				primaryMax = plugin.settings.maxWidth;
				secondaryMin = plugin.settings.minHeight;
				secondaryMax = plugin.settings.maxHeight;

			} else if (primaryFieldName == 'height') {
				$primaryField = $("#" + plugin.ui.imgResizeHeightField.getInputId());
				$secondaryField = $("#" + plugin.ui.imgResizeWidthField.getInputId());
				adjustedAspectRatio = adjustedAspectRatio;

				primaryMin = plugin.settings.minHeight;
				primaryMax = plugin.settings.maxHeight;
				secondaryMin = plugin.settings.minWidth;
				secondaryMax = plugin.settings.maxWidth;
			} else {
				// If primaryFieldName is neither width nor height, don't update the fields
				return false;
			}

			if (isNaN(newValue)) {
				$primaryField.css('background-color', 'red');
				// If the current value of the field can't be parsed it is not updated
				return false;
			}
			else {
				$primaryField.val(newValue);
			}

			var correctPrimary = true;

			if (newValue > primaryMax) {
				// Auto-correct out of bounds values
				if (plugin.settings.autoCorrectManualInput) {
					$primaryField.val(primaryMax);
					newValue = $primaryField.val();
					$primaryField.css('background-color', '');
					correctPrimary = true;
				// Just notify the user, do nothing
				} else {
					$primaryField.css('background-color', 'red');
					correctPrimary = false;
				}
			} else if (newValue < primaryMin) {
				// Don't let the user decrement values below minimum
				if (isDecrement) {
					$primaryField.val(primaryMin);
					newValue = $primaryField.val();
					correctPrimary = true;
				// Auto-correct out of bounds values
				} else if (plugin.settings.autoCorrectManualInput) {
					$primaryField.css('background-color', 'wheat');
					correctPrimary = false;
				// Just notify the user, do nothing
				} else {
					$primaryField.css('background-color', 'red');
					correctPrimary = false;
				}
			} else {
				$primaryField.css('background-color', '');
				correctPrimary = true;
			}

			var correctSecondary = true;

			// If keep aspect ratio is enabled, the field that is not edited is updated as well
			if ( plugin.keepAspectRatio ) {
				var secondary = Math.round(newValue * adjustedAspectRatio);

				if (secondary > secondaryMax) {
					// Auto-correct out of bounds values
					if (plugin.settings.autoCorrectManualInput) {
						$secondaryField.val(secondaryMax);
						$primaryField.val($secondaryField.val() / adjustedAspectRatio);
						newValue = $primaryField.val();
						$primaryField.css('background-color', '');
						$secondaryField.css('background-color', '');
						correctSecondary = true;
					// Just notify the user, do nothing
					} else {
						secondary.css('background-color', 'red');
						correctSecondary = false;
					}
				} else if (secondary < secondaryMin) {
					// Don't let the user decrement values below minimum
					if (isDecrement) {
						$secondaryField.val(secondaryMin);
						$primaryField.val($secondaryField.val() / adjustedAspectRatio);
						newValue = $primaryField.val();
						correctPrimary = true;
					// Auto-correct out of bounds values
					} else if (plugin.settings.autoCorrectManualInput) {
						$secondaryField.val(secondary);
						$secondaryField.css('background-color', 'wheat');
						correctSecondary = false;
					// Just notify the user, do nothing
					} else {
						$secondaryField.css('background-color', 'red');
						correctSecondary = false;
					}
				} else {
					$secondaryField.val(secondary);
					$secondaryField.css('background-color', '');
					correctSecondary = true;
				}
			}

			// Final check for value of secondary field, in case focus was changed
			if ($secondaryField.val() < secondaryMin) {
				correctSecondary = false;
			}

			// Success if values are correct or have been adjusted accordingly
			if (correctPrimary && correctSecondary) {
				return true;
			}
			return false;
		},

		/**
		 * Helper function that will set the new image size using the field values
		 */
		setSizeByFieldValue: function () {
			var width =  this.ui.imgResizeWidthField.getValue();
			var height = this.ui.imgResizeHeightField.getValue();
			this.setSize(width, height);
		},

		/**
		 * Helper function that sets field values to specified parameters and resets background of fields
		 * @param width new image-width in pixels
		 * @param height new image-height in pixels
		 */
		_applyValuesToFields: function (width, height) {
			this.ui.imgResizeWidthField.setValue(width);
			this.ui.imgResizeHeightField.setValue(height);

			this.ui.imgResizeWidthField.getInputJQuery().css('background-color', '');
			this.ui.imgResizeHeightField.getInputJQuery().css('background-color', '');
		},

		/**
		 * Helper function that will set the new crop area width and height using the field values
		 *
		 * note: because the _updateFields function does not consider the x and y of the croparea yet,
		 * the value of the secondary field shown after setting croparea with manual input may be incorrect;
		 * while this has no influence on the actual crop size, a future implementation should consider this
		 * when calculating the field values
		 */
		setCropAreaByFieldValue: function () {
			var currentCropArea = this.jcAPI.tellSelect();

			var width =  parseInt(this.ui.imgResizeWidthField.getValue(), 10);
			var height = parseInt(this.ui.imgResizeHeightField.getValue(), 10);

			var selection = [currentCropArea['x'], currentCropArea['y'], currentCropArea['x'] + width,currentCropArea['y'] + height];

			this.jcAPI.setSelect(selection);
			this._onCropSelect();
			this.jcAPI.enable();
		},

		/**
		* This method will insert a new image dom element into the dom tree
		*/
		insertImg: function () {
				var range = Aloha.Selection.getRangeObject(),
				config = this.getEditableConfig(Aloha.activeEditable.obj),
				imagePluginUrl = Aloha.getPluginUrl('image'),
				imagestyle, imagetag, newImg;

				if (range.isCollapsed()) {
					// TODO I would suggest to call the srcChange method. So all image src
					// changes are on one single point.
					imagestyle = "max-width: " + config.maxWidth + "; max-height: " + config.maxHeight;
					imagetag = '<img style="' + imagestyle + '" src="' + imagePluginUrl + '/img/blank.jpg" title="" />';
					newImg = jQuery(imagetag);
					// add the click selection handler
					//newImg.click( Aloha.Image.clickImage ); - Using delegate now
					GENTICS.Utils.Dom.insertIntoDOM(newImg, range, jQuery(Aloha.activeEditable.obj));

			} else {
				Aloha.Log.error('img cannot markup a selection');
				// TODO the desired behavior could be me the selected content is
				// replaced by an image.
				// TODO it should be editor's choice, with an NON-Ext Dialog instead of alert

			}
		},

		srcChange: function () {
			// TODO the src changed. I suggest :
			// 1. set an loading image (I suggest set src base64 enc) to show the user
			// we are trying to load an image
			// 2. start a request to get the image
			// 3a. the image is ok change the src
			// 3b. the image is not availbable show an error.
			 this.imageObj.attr('src', this.ui.imgSrcField.getValue()); // (the img tag)
//			 jQuery(img).attr('src', this.imgSrcField.getQueryValue()); // (the query value in the inputfield)
//			 this.imgSrcField.getItem(); // (optinal a selected resource item)
			// TODO additionally implement an srcChange Handler to let implementer
			// customize
		},

		/**
		 * Reposition the crop buttons below the crop area
		 */
		positionCropButtons: function() {

			var jt = jQuery('.jcrop-tracker:first'),
				off = jt.offset(),
				jtt = off.top,
				jtl = off.left,
				jth = jt.height(),
				jtw = jt.width();

			var oldLeft = 0,
				oldTop = 0;

			var btns = jQuery('#aloha-CropNResize-btns');

			// Hack to hide the buttons when the user just clicked into the image
			if (jtt === 0 && jtl === 0) {
				btns.hide();
			}

			// move the icons to the bottom right side
			jtt = parseInt(jtt + jth + 3, 10);
			jtl = parseInt(jtl + (jtw / 2) - (btns.width() / 2) + 10, 10);

			// comparison to old values hinders flickering bug in FF
			if (oldLeft != jtl || oldTop != jtt) {
				btns.offset({top: jtt, left: jtl});
			}

			oldLeft = jtl;
			oldTop = jtt;
		},

		/**
		 * Code imported from CropnResize Plugin
		 *
		 */
		initCropButtons: function() {
			var that = this,
				btns;

			jQuery('body').append(
				'<div id="aloha-CropNResize-btns" display="none">' +
					'<button class="cnr-crop-apply" title="' + i18n.t('Accept') + '"></button>' +
					'<button class="cnr-crop-cancel" title="' + i18n.t('Cancel') + '"></button>' +
				'</div>'
			);

			btns = jQuery('#aloha-CropNResize-btns');

			btns.find('.cnr-crop-apply').click(function () {
				that.acceptCrop();
			});

			btns.find('.cnr-crop-cancel').click(function () {
				that.endCrop();
			});

			this.interval = setInterval(function () {
				that.positionCropButtons();
			}, 10);
		},

		/**
		 * Destroy crop confirm and cancel buttons
		 */
		destroyCropButtons: function () {
			jQuery('#aloha-CropNResize-btns').remove();
			clearInterval(this.interval);
		},

		/**
		 * Helper function that will disable selectability of elements
		 */
		_disableSelection: function (el) {
			el.find('*').attr('unselectable', 'on')
					.css({
					'-moz-user-select':'none',
					'-webkit-user-select':'none',
					'user-select':'none'
					});
				/*
					.each(function() {
					this.onselectstart = function () { return false; };
					});
					*/

		},

		/**
		 * Initiate a crop action
		 */
		crop: function () {
			var plugin = this;
			var config = this.config;
			var ratio = plugin.keepAspectRatio ? aspectRatioValue : false;

			this.ui._imageCropButton.setState(true);

			plugin.initCropButtons();
			if (plugin.settings.ui.resizable) {
				plugin.endResize();
			}

			plugin.jcAPI = jQuery.Jcrop(plugin.imageObj, {
				onSelect : function () {
					plugin._onCropSelect();
					// ugly hack to keep scope :(
					window.setTimeout(function () {
						plugin.ui.setScope();
					}, 10);
				},
				aspectRatio: ratio,
				minSize  : [plugin.settings.minWidth, plugin.settings.minHeight]
			});

			plugin._disableSelection($('.jcrop-holder'));
			plugin._disableSelection($('#imageContainer'));
			plugin._disableSelection($('#aloha-CropNResize-btns'));
			$('body').trigger('aloha-image-crop-start', [plugin.imageObj]);
		},

		/**
		 * Internal on crop select method
		 */
		_onCropSelect: function () {
			var plugin = this;

			// Hide the crop buttons when the one of the handles is clicked
			jQuery('.jcrop-handle').mousedown(function () {
				jQuery('#aloha-CropNResize-btns').hide();
			});

			jQuery('.jcrop-tracker').mousedown(function () {
				jQuery('#aloha-CropNResize-btns').hide();
			});

			// Update the width and height field using the initial active crop area values
			if (typeof plugin.jcAPI !== 'undefined' && plugin.jcAPI !== null) {
				plugin.positionCropButtons();
				var currentCropArea = plugin.jcAPI.tellSelect();

				if (currentCropArea.h > 0 && currentCropArea.w > 0) {
					jQuery('#aloha-CropNResize-btns').fadeIn('slow');
				}

				plugin._applyValuesToFields(currentCropArea['w'], currentCropArea['h']);
			}

		},

		/**
		 * Terminates a crop
		 */
		endCrop: function () {
			if (this.jcAPI) {
				this.jcAPI.destroy();
				this.jcAPI = null;
			}

			this.destroyCropButtons();
			this.ui._imageCropButton.setState(false);

			if (this.settings.ui.resizable) {
				this.startResize();
			}

			$('body').trigger('aloha-image-crop-stop', [this.imageObj]);

			//after cropping, field values are set to (once again) contain image width/height
			this._applyValuesToFields(this.imageObj.width(), this.imageObj.height());
		},

		/**
		 * Accept the current cropping area and apply the crop
		 */
		acceptCrop: function () {
			this._onCropped(this.imageObj, this.jcAPI.tellSelect());
			this.endCrop();
		},

		/**
		 * This method will activate the jquery-ui resize functionality for the current image
		 */
		startResize: function () {
			var plugin = this;
			var currentImageObj = this.imageObj;
			var ratio = plugin.keepAspectRatio ? aspectRatioValue : false;

			currentImageObj = this.imageObj.css({
				height		: this.imageObj.height(),
				width		: this.imageObj.width(),
				position	: 'relative',
				'max-height': '',
				'max-width'	: ''
			});

			currentImageObj.resizable({
				maxHeight : plugin.settings.maxHeight,
				minHeight : plugin.settings.minHeight,
				maxWidth  : plugin.settings.maxWidth,
				minWidth  : plugin.settings.minWidth,
				aspectRatio : ratio,
				handles: plugin.settings.handles,
				grid : plugin.settings.grid,
				resize: function (event, ui) {
					resizing = true;
					plugin._onResize(plugin.imageObj);
				},
				stop : function (event, ui) {
					resizing = false;

					plugin._onResized(plugin.imageObj);

					// Workaround to finish cropping
					if (this.enableCrop) {
						window.setTimeout(function () {
							plugin.ui.setScope();
							that.done(event);
						}, 10);
					}
				}

			});

			currentImageObj.css('display', 'inline-block');

			// this will prevent the user from resizing an image
			// using IE's resize handles
			// however I could not manage to hide them completely
			jQuery('.ui-wrapper')
				.attr('contentEditable', false)
				.addClass('aloha-image-box-active Aloha_Image_Resize aloha')
				.css({
					position: 'relative',
					display: 'inline-block',
					'float': plugin.imageObj.css('float')
				})
				.bind('resizestart', function (e) {
					e.preventDefault();
				})
				.bind('mouseup', function (e) {
					e.originalEvent.stopSelectionUpdate = true;
				});
		},

		/**
		 * This method will end resizing and toggle buttons accordingly and remove all markup that has been added for cropping
		 */
		endResize: function () {
			// Find the nearest contenteditable and reenable it since resizing is finished
			if (this.imageObj) {
				var editable = this.imageObj.closest('.aloha-editable');
				//this.imageObj.contentEditable(true);
			}

			if (this.imageObj && this.imageObj.is(":ui-resizable")) {
				this.imageObj
					.resizable('destroy')
					.css({
						top	 : 0,
						left : 0
					});
			}
		},

		resetSize: function () {
			var	plugin = this,
				img = new Image();
			img.onload = function () {
				var myimage = plugin.getPluginFocus();
				if (plugin.settings.ui.resizable) {
					plugin.endResize();
				}
				jQuery(myimage).add(myimage.parent()).css({
						'width': img.width + 'px',
						'height': img.height + 'px',
						'max-width': '',
						'max-height': ''
					});
				if (plugin.settings.ui.resizable) {
					plugin.resize();
				}
			};
			img.src = plugin.getPluginFocus().attr('src');
		},

		/**
		 * Reset the image to its original properties
		 */
		reset: function () {
			var externalReset;

			if (this.settings.ui.crop) {
				this.endCrop();
			}

			if (this.settings.ui.resizable) {
				this.endResize();
			}

			externalReset = this._onReset(this.imageObj);

			// if the external reset procedure has already performed a reset, there is no need to apply an internal reset
			if (!externalReset) {
				for (var i = 0;i < this.restoreProps.length;i++) {

					// restore from restoreProps if there is a match
					if (this.imageObj.get(0) === this.restoreProps[i].obj.get(0)) {
						this.imageObj.attr('src', this.restoreProps[i].src);
						this.imageObj.width(this.restoreProps[i].width);
						this.imageObj.height(this.restoreProps[i].height);
						break;
					}
				}
			}

			// readjust inputfields to show correct height/width
			this._applyValuesToFields(this.imageObj.width(), this.imageObj.height());
		}
	});

});

/* highlighteditables-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('highlighteditables/highlighteditables-plugin',
['aloha', 'jquery', 'aloha/plugin'],
function(Aloha, jQuery, Plugin) {
	

	var
		GENTICS = window.GENTICS;

	return Plugin.create('highlighteditables', {

		/**
		 * default button configuration
		 */
		config: [ 'highlight' ],

		init: function () {

			// remember refernce to this class for callback
			var that = this,
				config;

			// highlight editables as long as the mouse is moving
			GENTICS.Utils.Position.addMouseMoveCallback(function () {
				var i,
					editable;

				for ( i = 0; i < Aloha.editables.length; i++) {
					editable = Aloha.editables[i];
					config = that.getEditableConfig( editable.obj );

					if ( !Aloha.activeEditable && !editable.isDisabled() && config == 'highlight' ) {
						editable.obj.addClass('aloha-editable-highlight');
					}
				}
			});

			// fade editable borders when mouse stops moving
			GENTICS.Utils.Position.addMouseStopCallback(function () {
				that.fade();
			});

			// mark active Editable with a css class
			Aloha.bind(
					"aloha-editable-activated",
					function (jEvent, aEvent) {
						that.fade();
					}
			);

		},
		/**
		 * fades all highlighted editables
		 */
		fade: function () {
			var
				i, editable,
				animateEnd = function () {
					jQuery(this).css('outline', '');
				};
			for ( i = 0; i < Aloha.editables.length; i++) {
				editable = Aloha.editables[i].obj;
				if (editable.hasClass('aloha-editable-highlight')) {
					// IE8 fix - hardcode 5px because editable.css('outlineWidth') sometimes causes a javascript error
					editable.css('outline', editable.css('outlineColor') + ' ' + editable.css('outlineStyle') + ' 5px')
						.removeClass('aloha-editable-highlight')
						.animate({
							outlineWidth : '0px'
						}, 300, 'swing', animateEnd);
				}
			}
		}

	});
});

/* dom-to-xhtml.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Provides public utility methods to convert DOM nodes to XHTML.
 */
define('dom-to-xhtml/dom-to-xhtml',[
	'jquery',
	'util/dom2',
	'util/misc',
	'util/browser',
	'aloha/ephemera',
	'aloha/console'
],
function(
	$,
	Dom,
	Misc,
	Browser,
	Ephemera,
	console
) {
	

	/**
	 * Elements that are to be serialized like <img /> and not like <img></img>
	 */
	var emptyElements = {
		"area": true,
		"base": true,
		"basefont": true,
		"br": true,
		"col": true,
		"frame": true,
		"hr": true,
		"img": true,
		"input": true,
		"isindex": true,
		"link": true,
		"meta": true,
		"param": true,
		"embed": true
	};

	/**
	 * Attributes that are to be serialized like checked="checked" for any attribute value.
	 */
	var booleanAttrs = {
		"checked": true,
		"compact": true,
		"declare": true,
		"defer": true,
		"disabled": true,
		"ismap": true,
		"multiple": true,
		"nohref": true,
		"noresize": true,
		"noshade": true,
		"nowrap": true,
		"readonly": true,
		"selected": true
	};

	/**
	 * Maps element names to a boolean that indicates whether IE7/IE8 doesn't recognize the element.
	 * This is necessary to repair the broken DOM structure caused by unrecognized elements.
	 * Contains some intial values to cover most common cases. If an
	 * element is serialized that is not present here, it will be
	 * examined (which may be costly) and added dynamically.
	 * See isUnrecognized().
	 */
	var isUnrecognizedMap = {
		"DIV": false,
		"SPAN": false,
		"UL": false,
		"OL": false,
		"LI": false,
		"TABLE": false,
		"TR": false,
		"TD": false,
		"TH": false,
		"I": false,
		"B": false,
		"EM": false,
		"STRONG": false,
		"A": false,
		"P": false
	};

	/**
	 * Encodes a string meant to be used wherever parsable character data occurs in XML.
	 * @param str
	 *        An unencoded piece of character data
	 * @return
	 *        The given string with & and < characters replaced with the corresponding HTML entity references.
	 */
	function encodePcdata(str) {
		return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
	}

	/**
	 * Encodes a string meant to be used between double-quoted attribute values.
	 *
	 * @param str
	 *        An unencoded attribute value
	 * @return
	 *        The given string with & < and " characters replaced with the corresponding HTML entity references.
	 */
	function encodeDqAttrValue(str) {
		return encodePcdata(str).replace(/"/g, '&quot;');
	}

	/**
	 * Serializes the attributes of the given element.
	 *
	 * Attributes that have the empty string as value will not appear in the string at all.
	 *
	 * @param element
	 *        An element to serialize the attributes of
	 * @param ephemera
	 *        Describes attributes that should be skipped.
	 *        See Ehpemera.ephemera().
	 * @return
	 *        A string made up of name="value" for each attribute of the
	 *        given element, separated by space. The string will have a leading space.
	 */
	function makeAttrString(element, ephemera) {
		var attrs = Dom.attrs(element);
		var str = "";
		var i, len;
		// Dom.attrs() doesn't support some boolean attributes on IE7
		// which we have to compensate for.
		if (Browser.ie7) {
			for (var bool in booleanAttrs) {
				if (booleanAttrs.hasOwnProperty(bool) && element[bool]) {
					// We don't want to add duplicate attributes
					for (i = 0, len = attrs.length; i < len; i++) {
						if (bool === attrs[i][0].toLowerCase()) {
							attrs.splice(i, 1);
							break;
						}
					}
					attrs.push([bool, bool]);
				}
			}
		}
		for (i = 0, len = attrs.length; i < len; i++) {
			// The XHTML spec says attributes are lowercase
			var attr  = attrs[i];
			var name  = attr[0].toLowerCase();
			var value = attr[1];

			if (ephemera && Ephemera.isAttrEphemeral(element, name, ephemera.attrMap || {}, ephemera.attrRxs || {})) {
				continue;
			}

			//TODO it's only a boolean attribute if the element is in an HTML namespace
			var isBool = booleanAttrs[name];

			if (!isBool && "" === value) {
				// I don't think it is ever an error to make an
				// attribute not appear if its string value is empty.
				continue;
			}

			// For boolean attributes, the mere existence of the attribute means it is true.
			str += " " + name + '="' + encodeDqAttrValue((isBool ? name : value)) + '"';
		}
		return str;
	}

	/**
	 * IE8 turns the following
	 * <book id="x">{content}</book>
	 * into
	 * <book id="x"></book>{content}</book><//book>
	 * This seems to occur with any element IE doesn't recognize.
	 *
	 * @param element
	 *        An element node.
	 * @return
	 *        true if the given element isn't recognized by IE and
	 *        causes a broken DOM structure as outlined above.
	 */
	function isUnrecognized(element) {
		var name = element.nodeName;
		var unrecognized = isUnrecognizedMap[name];
		if (null != unrecognized) {
			return unrecognized;
		}
		var closingName = "/" + element.nodeName;
		var sibling = element.nextSibling;
		unrecognized = false;
		while (null != sibling) {
			if (closingName == sibling.nodeName) {
				unrecognized = true;
				break;
			}
			sibling = sibling.nextSibling;
		}
		isUnrecognizedMap[name] = unrecognized;
		return unrecognized;
	}

	/**
	 * Serializes the children of the given element into an XHTML string.
	 *
	 * The same as serializeElement() except it only serializes the children.
	 * The start and end tag of the given element will not appear in the resulting XHTML.
	 *
	 * @see serializeElement()
	 */
	function serializeChildren(element, child, unrecognized, ephemera, xhtml) {
		while (null != child) {
			if (1 === child.nodeType && unrecognized && "/" + element.nodeName == child.nodeName) {
				child = child.nextSibling;
				break;
			} else if (1 === child.nodeType && isUnrecognized(child)) {
				child = serializeElement(child, child.nextSibling, true, ephemera, xhtml);
			} else {
				serialize(child, ephemera, xhtml);
				child = child.nextSibling;
			}
		}
		return child;
	}

	/**
	 * Serializes an element into an XHTML string.
	 *
	 * @param element
	 *        An element to serialize.
	 * @param child
	 *        The first child of the given element. This will usually be
	 *        element.firstChild. On IE this may be element.nextSibling because
	 *        of the broken DOM structure IE sometimes generates.
	 * @param unrecognized
	 *        Whether the given element is unrecognized on IE. If IE doesn't
	 *        recognize the element, it will create a broken DOM structure
	 *        which has to be compensated for. See isUnrecognized() for more.
	 * @param ephemera
	 *        Describes content that should not be serialized.
	 *        Only attrMap and attrRxs are supported at the moment.
	 *        See Ephemera.ephemera().
	 * @param xhtml
	 *        An array which receives the serialized element and whic, if joined,
	 *        will yield the XHTML string.
	 * @return
	 *        null if all siblings of the given child have been processed as children
	 *        of the given element, or otherwise the first sibling of child that is not considered
	 *        a child of the given element.
	 */
	function serializeElement(element, child, unrecognized, ephemera, xhtml) {
		// TODO: we should only lowercase element names if they are in an HTML namespace
		var elementName = element.nodeName.toLowerCase();
		// This is a hack around an IE bug which strips the namespace prefix
		// of element.nodeName if it occurs inside an contentEditable=true.
		if (element.scopeName && 'HTML' != element.scopeName && -1 === elementName.indexOf(':')) {
			elementName = element.scopeName.toLowerCase() + ':' + elementName;
		}
		if (!unrecognized && null == child && emptyElements[elementName]) {
			xhtml.push('<' + elementName + makeAttrString(element, ephemera) + '/>');
		} else {
			xhtml.push('<' + elementName + makeAttrString(element, ephemera) + '>');
			child = serializeChildren(element, child, unrecognized,  ephemera, xhtml);
			xhtml.push('</' + elementName + '>');
		}
		return child;
	}

	/**
	 * Serializes a DOM node into a XHTML string.
	 *
	 * @param node
	 *        A DOM node to serialize.
	 * @param ephemera
	 *        Describes content that should not be serialized.
	 *        Only attrMap and attrRxs are supported at the moment.
	 *        See Ephemera.ephemera().
	 * @param xhtml
	 *        An array that will receive snippets of XHTML,
	 *        which if joined will yield the XHTML string.
	 */
	function serialize(node, ephemera, xhtml) {
		var nodeType = node.nodeType;
		if (1 === nodeType) {
			serializeElement(node, node.firstChild, isUnrecognized(node), ephemera, xhtml);
		} else if (3 === node.nodeType) {
			xhtml.push(encodePcdata(node.nodeValue));
		} else if (8 === node.nodeType) {
			xhtml.push('<' + '!--' + node.nodeValue + '-->');
		} else {
			console.warn('Unknown node type encountered during serialization, ignoring it:'
						 + ' type=' + node.nodeType
						 + ' name=' + node.nodeName
						 + ' value=' + node.nodeValue);
		}
	}
	
	return {
		/**
		 * Serializes a number of DOM nodes in an array-like object to an XHTML string.
		 *
		 * The XHTML of the nodes in the given array-like object will be concatenated.
		 *
		 * @param nodes
		 *        An array or jQuery object or another array-like object to serialize.
		 * @param ephemera
		 *        Describes content that should not be serialized.
		 *        Only attrMap and attrRxs are supported at the moment.
		 *        See Ephemera.ephemera().
		 * @return
		 *        The serialized XHTML String representing the given DOM nodes in the given array-like object.
		 *        The result may look like an XML fragment with multiple top-level elements and text nodes.
		 * @see nodeToXhtml()
		 */
		contentsToXhtml: function(element, ephemera) {
			var xhtml = [];
			serializeChildren(element, element.firstChild, false, ephemera, xhtml);
			return xhtml.join("");
		},

		/**
		 * Serializes a DOM node to an XHTML string.
		 *
		 * Beware that the serialization method will generate XHTML as
		 * close as possible to the DOM tree represented by the given
		 * node. The result will only be valid XHTML if the DOM tree
		 * doesn't violate any contained-in rules.
		 *
		 * Element attributes with an empty string as value will not
		 * appear in the serialized output.
		 *
		 * Element attribute names are case-insensitive in HTML5, so
		 * they may come out in mixed-case depending on what the browser
		 * provides.
		 *
		 * When iterating over the DOM, CDATA sections are comment nodes
		 * on some browsers (Chrome) and not there at all on others (IE).
		 * This is the same as what comes out from element.innerHTML.
		 *
		 * IE8 bug: comments will sometimes be silently stripped inside
		 * contentEditable=true. Conditional includes don't work inside
		 * contentEditable=true. See the tests for more information.
		 *
		 * IE8 bug: a title element will not be serialized correctly
		 * unless it occurs in the head of a HTML document, even if it occurs
		 * in a non-HTML namespace (maybe it works with a prefix).
		 * This will probably also apply for other HTML elements that
		 * occur in the header.
		 *
		 * IE8 bug: unrecognized elements in the HTML scope will cause
		 * broken DOM structure (some HTML5 elements that are not yet
		 * implemented in IE for example). Some effort was made to fix a
		 * broken DOM structure, if it is encountered. There is one case
		 * which results in an unrecoverably broken DOM structure, which
		 * is an unrecognized element not preceded by some text. See the
		 * tests for further information.
		 *
		 * IE8 bug: whitespace is not reliably preserved when the style
		 * white-space:pre (or similar) is used. See the tests for
		 * further information. Whitespace inside <pre> elements will
		 * be preserved, but \n characters will become \r characters.
		 *
		 * IE7 bug: URLs in href and src attributes of a and img
		 * elements will be absolutized (including hostname and
		 * protocol) if they are given as a relative path.
		 *
		 * IE bug: Namespace support inside contentEditable=true is a
		 * bit shaky on IE. Don't use it if possible. See the tests to
		 * get an idea of what seems to work. Make namespace prefixes
		 * and element names all lower-case, as they are always
		 * lower-cased, even if the element doesn't occur in an HTML
		 * namespace. Don't use default namespaces, use prefixes (except
		 * for an HTML namespace).
		 *
		 * @param node
		 *        A DOM node to serialize
		 * @param ephemera
		 *        Describes content that should not be serialized.
		 *        Only attrMap and attrRxs are supported at the moment.
		 *        See Ephemera.ephemera().
		 * @return
		 *        The serialized XHTML string represnting the given DOM node.
		 */
		nodeToXhtml: function(node, ephemera) {
			var xhtml = [];
			serialize(node, ephemera, xhtml);
			return xhtml.join("");
		}
	};
});

/* dom-to-xhtml-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * The dom-to-xhtml plugin extends the serialization method of the
 * Aloha.Editable.getContent() instance method to generate valid XHTML
 * (in so far as the DOM of the editables itself is valid).
 * Attributes described by Ehpemera.ephemera() will be removed.
 */
define('dom-to-xhtml/dom-to-xhtml-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'aloha/ephemera',
	'dom-to-xhtml/dom-to-xhtml'
], function (
	Aloha,
	$,
	Plugin,
	Ephemera,
	domToXhtml
) {
	

	return Plugin.create('dom-to-xhtml', {
		/**
		 * Called by the plugin-manager on intialization.
		 *
		 * @Override
		 */
		init: function () {
			var that = this;
			Aloha.Editable.setContentSerializer(function(editableElement) {
				if ( !that.settings.editables && !that.settings.config ) {
					return domToXhtml.contentsToXhtml(editableElement);
				}

				if ( that.settings.editables &&
					that.settings.editables['#'+$(editableElement).attr('id')] == 'dom-to-xhtml' ) {
					return domToXhtml.contentsToXhtml(editableElement, Ephemera.ephemera());
				} else if ( that.settings.config &&
					that.settings.config == 'dom-to-xhtml' &&
					!that.settings.editables['#'+$(editableElement).attr('id')] ) {
					return domToXhtml.contentsToXhtml(editableElement, Ephemera.ephemera());
				} else {
					return $(editableElement).html();
				}
			});
		}
	});
});

/*!
* Aloha Editor
* Author & Copyright (c) 2012-2013 Gentics Software GmbH
* aloha-sales@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*
* @overview
* Utility functions for content handling.
*/
define('contenthandler/contenthandler-utils',['jquery'], function ($) {
	

	/**
	 * Checks whether the markup describes a paragraph that is propped by
	 * a <br> tag but is otherwise empty.
	 *
	 * Will return true for:
	 *
	 * <p id="foo"><br class="bar" /></p>
	 *
	 * as well as:
	 *
	 * <p><br></p>
	 *
	 * @param {string} html Markup
	 * @return {boolean} True if html describes a propped paragraph.
	 */
	function isProppedParagraph(html) {
		var trimmed = $.trim(html);
		if (!trimmed) {
			return false;
		}
		var node = $('<div>' + trimmed + '</div>')[0];
		var containsSingleP = node.firstChild === node.lastChild
		                   && 'p' === node.firstChild.nodeName.toLowerCase();
		if (containsSingleP) {
			var kids = node.firstChild.children;
			return (kids && 1 === kids.length &&
					'br' === kids[0].nodeName.toLowerCase());
		}
		return false;
	}

	function wrapContent(content) {
		if (typeof content === 'string') {
			return $('<div>' + content + '</div>');
		}
		if (content instanceof $) {
			return $('<div>').append(content);
		}
		return null;
	}

	return {
		wrapContent: wrapContent,
		isProppedParagraph: isProppedParagraph
	};
});

/* wordcontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('contenthandler/wordcontenthandler',[
	'jquery',
	'aloha',
	'aloha/contenthandlermanager',
	'contenthandler/contenthandler-utils',
	'util/dom'
], function (
	$,
	Aloha,
	Manager,
	Utils,
	Dom
) {
	

	var jQuery = $;

	/**
	 * Matches the string "mso".
	 *
	 * @type {RexExp}
	 * @const
	 */
	var MSO = /mso/;

	/**
	 * Matches string starting with "#".
	 *
	 * @type {RexExp}
	 * @const
	 */
	var HASH_HREF = /^#(.*)/;

	/**
	 * Checks whether the given node is empty, ignoring white spaces.
	 *
	 * @param {jQuery.<HTMLElement>} $node
	 * @return {boolean} True if $node is empty.
	 */
	function isEmpty($node) {
		switch ($node[0].nodeName.toLowerCase()) {
		case 'table':
			return 0 === $node.find('tbody,tr').length;
		case 'tbody':
			return 0 === $node.find('tr').length;
		case 'tr':
			return 0 === $node.find('td,th').length;
		default:
			return '' === $.trim($node.text());
		}
	}

	/**
	 * Checks whether the given content element can be assumed to originate
	 * from Microsoft Word.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 * @return True if the content is determined to originate from an
	 *         office document.
	 */
	function isWordContent($content) {
		// Because reading the html of the content is way faster than iterating
		// its entire node tree, therefore we attempt this first.
		if (0 === $content.length || !MSO.test($content[0].outerHTML)) {
			return false;
		}
		var $nodes = $content.find('*');
		var i;
		var style;
		var classNames;
		// Because if "mso" is found somewhere in the style or class names then
		// the content originated form MS Word.
		for (i = 0; i < $nodes.length; i++) {
			style = $nodes.eq(i).attr('style');
			if (style && style.toLowerCase().indexOf('mso') >= 0) {
				return true;
			}
			classNames = $nodes.eq(i).attr('class');
			if (classNames && classNames.toLowerCase().indexOf('mso') >= 0) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Cleanup MS Word HTML.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 */
	function clean($content) {
		var $nodes = $content.find('*');
		var nodeName;
		var $node;
		var href;
		var i;
		for (i = 0; i < $nodes.length; i++) {
			$node = $nodes.eq(i);
			nodeName = $node[0].nodeName.toLowerCase();

			if ('a' === nodeName) {

				// Because when a href starts with #, it's the link to an
				// anchor and should be removed.
				href = $node.attr('href');
				if (href && HASH_HREF.test($.trim(href))) {
					$node.contents().unwrap();
				}
			} else if ('div' === nodeName || 'span' === nodeName) {

				// Because footnotes for example are wrapped in divs and should
				// be unwrap.
				$node.contents().unwrap();
			} else if ('td' !== nodeName && isEmpty($node)) {

				// Because any empty element (like spaces wrapped in spans) are
				// not needed, except table cells.
				$node.contents().unwrap();
			}
		}
	}

	/**
	 * Transform Title and Subtitle from MS Word.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 */
	function transformTitles($content) {
		$content.find('p.MsoTitle').each(function () {
			Aloha.Markup.transformDomObject($(this), 'h1');
		});
		$content.find('p.MsoSubtitle').each(function () {
			Aloha.Markup.transformDomObject($(this), 'h2');
		});
	}

	var WordContentHandler = Manager.createHandler({

		/**
		 * Handle content pasted from Word or Open/Libre Office.
		 *
		 * Tries to detect content pasted from office document and transforms
		 * into clean HTML.
		 *
		 * @param {jQuery.<HTMLElement>|string} content
		 * @return {string} Clean HTML
		 */
		handleContent: function (content) {
			var $content = Utils.wrapContent(content);
			if (!$content) {
				return content;
			}
			if (isWordContent($content)) {
				this.transformWordContent($content);
			}
			return $content.html();
		},

		/**
		 * Check whether the given list span (first span in a paragraph which shall be a list item) belongs to an ordered list
		 * @param listSpan
		 * @return true for ordered lists, false for unordered
		 */
		isOrderedList: function(listSpan) {
			// when the span has fontFamily "Wingdings" it is an unordered list
			if (listSpan.css('fontFamily') == 'Wingdings' || listSpan.css('fontFamily') == 'Symbol') {
				return false;
			}
			// otherwise check for a number, letter or '(' as first character
			return listSpan.text().match(/^([0-9]{1,3}\.)|([0-9]{1,3}\)|([a-zA-Z]{1,5}\.)|([a-zA-Z]{1,5}\)))$/) ? true : false;
		},

		/**
		 * Transform lists pasted from word
		 * @param content
		 */
		transformListsFromWord: function (content) {
			var that = this,
				negateDetectionFilter, detectionFilter, spans,
				paragraphs, bulletClass, listElementClass;

			// this will be the class to mark paragraphs that will be transformed to lists
			listElementClass = 'aloha-list-element';
			bulletClass = 'aloha-list-bullet';

			// first step is to find all paragraphs which will be converted into list elements and mark them by adding the class 'aloha-list-element'
			detectionFilter = 'p.MsoListParagraphCxSpFirst,p.MsoListParagraphCxSpMiddle,p.MsoListParagraphCxSpLast,p.MsoListParagraph,p span';
			paragraphs = content.find(detectionFilter);
			paragraphs.each(function() {
				var jqElem = jQuery(this),
					fontFamily = jqElem.css('font-family') || '',
					msoList = jqElem.css('mso-list') || '',
					style = jqElem.attr('style') || '';

				// detect special classes
				if (jqElem.hasClass('MsoListParagraphCxSpFirst') || jqElem.hasClass('MsoListParagraph')) {
					jqElem.addClass(listElementClass);
				} else if (fontFamily.indexOf('Symbol') >= 0) {
					jqElem.closest('p').addClass(listElementClass);
				} else if (fontFamily.indexOf('Wingdings') >= 0) {
					jqElem.closest('p').addClass(listElementClass);
				} else if (msoList !== '') {
					jqElem.closest('p').addClass(listElementClass);
				} else if (style.indexOf('mso-list') >= 0) {
					jqElem.closest('p').addClass(listElementClass);
				}
			});

			// now we search for paragraphs with three levels of nested spans, where the innermost span contains nothing but &nbsp;
			detectionFilter = 'p span span span';
			spans = content.find(detectionFilter);
			spans.each(function() {
				var jqElem = jQuery(this),
				    innerText = jQuery.trim(jqElem.text()).replace(/&nbsp;/g, ''),
					outerText;
				
				if (innerText.length === 0) {
					// check whether the outermost of the three spans contains nothing more than numbering
					outerText = jQuery.trim(jqElem.parent().parent().text()).replace(/&nbsp;/g, '');

					// patterns for list numbering
					// 1.
					// 1)
					// (1)
					// a.
					// a)
					// I.
					// i.
					// o § (or any other single character)
					if (outerText.match(/^([0-9]{1,3}\.)|([0-9]{1,3}\))|([a-zA-Z]{1,5}\.)|([a-zA-Z]{1,5}\))|(.)$/)) {
						jqElem.closest('p').addClass(listElementClass);
						jqElem.parent().parent().addClass(bulletClass);
					}
				}
			});

			// no detect all marked paragraphs and transform into lists
			detectionFilter = 'p.' + listElementClass;
			// We also have to include font because if IE9
			negateDetectionFilter = ':not(' + detectionFilter + ', font)';
			paragraphs = content.find(detectionFilter);

			if (paragraphs.length > 0) {
				paragraphs.each(function() {
					var jqElem = jQuery(this),
						jqNewLi, jqList, ordered, firstSpan, following, lists, margin, nestLevel;

					jqElem.removeClass(listElementClass);
					// first remove all font tags
					jqElem.find('font').each(function() {
						jQuery(this).contents().unwrap();
					});

					// initialize the nestlevel and the margin (we will try to detect nested
					// lists by comparing the left margin)
					nestLevel = [];
					margin = parseFloat(jqElem.css('marginLeft'));
					// Fix for not found margin on level 0
					if (isNaN(margin)) {
						margin = 0;
					}
					// this array will hold all ul/ol elements
					lists = [];
					// get all following list elements
					following = jqElem.nextUntil(negateDetectionFilter);

					// get the first span in the element
					firstSpan = jQuery(jqElem.find('span.' + bulletClass));
					if (firstSpan.length === 0) {
						firstSpan = jqElem.find('span').eq(0);
					}
					// use the span to detect whether the list shall be ordered or unordered
					ordered = that.isOrderedList(firstSpan);
					// finally remove the span (numbers, bullets are rendered by the browser)
					firstSpan.remove();

					// create the list element
					jqList = jQuery(ordered ? '<ol></ol>' : '<ul></ul>');
					lists.push(jqList);

					// add a new list item
					jqNewLi = jQuery('<li></li>');
					// add the li into the list
					jqList.append(jqNewLi);
					// append the contents of the old dom element to the li
					jqElem.contents().appendTo(jqNewLi);
					// replace the old dom element with the new list
					jqElem.replaceWith(jqList);

					// now proceed all following list elements
					following.each(function() {
						var jqElem = jQuery(this),
							newMargin, jqNewList;
						
						if (jqElem.is('font')) {
							//Fix for IE9
							return;
						}

						// remove all font tags
						jqElem.find('font').each(function() {
							jQuery(this).contents().unwrap();
						});
						// check the new margin
						newMargin = parseFloat(jqElem.css('marginLeft'));
						// Fix for not found margin on level 0
						if (isNaN(newMargin)) {
							newMargin = 0;
						}
						
						// get the first span
						firstSpan = jQuery(jqElem.find('span.' + bulletClass));
						if (firstSpan.length === 0) {
							firstSpan = jqElem.find('span').eq(0);
						}
						// ... and use it to detect ordered/unordered list elements (this
						// information will only be used at the start of a new list anyway)
						ordered = that.isOrderedList(firstSpan);
						// remove the span
						firstSpan.remove();

						// check for nested lists by comparing the margins
						if (newMargin > margin) {
							// create a new list
							jqNewList = jQuery(ordered ? '<ol></ol>' : '<ul></ul>');
							// append the new list to the last list item of the prior list
							jqList.children(':last').append(jqNewList);

							// store the list and increase the nest level
							jqList = jqNewList;
							lists.push(jqList);
							nestLevel.push(newMargin);
							margin = newMargin;
						} else if (newMargin < margin && nestLevel.length > 0) {
							while(nestLevel.length > 0 && nestLevel[nestLevel.length - 1] > newMargin) {
								nestLevel.pop();
								lists.pop();
							}
							// end nested list and append element to outer list
							jqList = lists[lists.length - 1];
							margin = newMargin;
						}

						// create a list item
						jqNewLi = jQuery('<li></li>');
						// add the li into the list
						jqList.append(jqNewLi);
						// append the contents of the old dom element to the li
						jqElem.contents().appendTo(jqNewLi);
						// remove the old dom element
						jqElem.remove();
					});
				});
			}
		},
		
		/**
		 * Remove paragraph numbering from TOC feature
		 * @param content
		*/
		removeParagraphNumbering: function( content ) {
			var detectionFilter = 'h1,h2,h3,h4,h5,h6',
				paragraphs = content.find(detectionFilter);
			
			if (paragraphs.length > 0) {
				paragraphs.each(function() {
					var jqElem = jQuery(this),
						spans = jqElem.find('span'),
						links = jqElem.find('a');
				
					// remove TOC numbering
					spans.each(function() {
						if ( jQuery.trim(jQuery(this).text()).match(/^([\.\(]?[\d\D][\.\(]?){1,4}$/) ) {
							jQuery(this).remove();
						}
					})
				
					// remove TOC anchor links
					links.each(function() {
						// no href, so it's an anchor
						if ( typeof jQuery(this).attr('href') === 'undefined' ) {
							jQuery(this).contents().unwrap();
						}
					});
				
				});
			}
		},

		
		/**
		 * Transform TOC
		 * @param content
		*/
		transformToc: function( content ) {
			var detectionFilter = '[class*=MsoToc]',
				paragraphs = content.find(detectionFilter);

			paragraphs.each(function() {
				var jqElem = jQuery(this),
					spans = jqElem.find('span'),
					links = jqElem.find('a');

				// a table of contents entry looks like
				// 1. Title text ... 5
				// we get rid of the "... 5" part which repesents the page number
				spans.each(function() {
					if ( jQuery(this).attr('style') && jQuery(this).attr('style').search('mso-hide') > -1 ) {
						jQuery(this).remove();
					}
					jQuery(this).contents().unwrap();
				});

				// remove the anchor link of the toc item
				links.each(function() {
					jQuery(this).contents().unwrap();
				});
			});
		},

		/**
		 * This is the main transformation method
		 * @param {jQuery.<HTMLElement>} $content
		 */
		transformWordContent: function ($content) {
			this.transformToc($content);
			this.removeParagraphNumbering($content);
			this.transformListsFromWord($content);
			transformTitles($content);
			clean($content);
		}
	});
	
	return WordContentHandler;
});

/* genericcontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('contenthandler/genericcontenthandler',[
	'jquery',
	'aloha',
	'aloha/contenthandlermanager',
	'contenthandler/contenthandler-utils'
], function (
	$,
	Aloha,
	Manager,
	Utils
) {
	

	/**
	 * Tags used for semantic formatting
	 * @type {Array.<String>}
	 * @see GenericContentHandler#transformFormattings
	 */
	var formattingTags = ['strong', 'em', 's', 'u', 'strike'];

	/**
	 * Checks whether the markup describes a paragraph that is propped by
	 * a <br> tag but is otherwise empty.
	 * 
	 * Will return true for:
	 *
	 * <p id="foo"><br class="bar" /></p>
	 *
	 * as well as:
	 *
	 * <p><br></p>
	 *
	 * @param {string} html Markup
	 * @return {boolean} True if html describes a propped paragraph.
	 */
	function isProppedParagraph(html) {
		var trimmed = $.trim(html);
		if (!trimmed) {
			return false;
		}
		var node = $('<div>' + trimmed + '</div>')[0];
		var containsSingleP = node.firstChild === node.lastChild
		    && 'p' === node.firstChild.nodeName.toLowerCase();
		if (containsSingleP) {
			var kids = node.firstChild.children;
			return (kids && 1 === kids.length &&
					'br' === kids[0].nodeName.toLowerCase());
		}
		return false;
	}


	/**
	 * Transforms all tables in the given content to make them ready to for
	 * use with Aloha's table handling.
	 *
	 * Cleans tables of their unwanted attributes.
	 * Normalizes table cells.
	 *
	 * @param {jQuery.<HTMLElement>} $content
	 */
	function prepareTables($content) {
		// Because Aloha does not provide a way for the editor to
		// manipulate borders, cellspacing, cellpadding in tables.
		// @todo what about width, height?
		$content.find('table')
			.removeAttr('cellpadding')
			.removeAttr('cellspacing')
			.removeAttr('border')
			.removeAttr('border-top')
			.removeAttr('border-bottom')
			.removeAttr('border-left')
			.removeAttr('border-right');

		$content.find('td').each(function () {
			var td = this;

			// Because cells with a single empty <p> are rendered to appear
			// like empty cells, it simplifies the handeling of cells to
			// normalize these table cells to contain actual white space
			// instead.
			if (isProppedParagraph(td.innerHTML)) {
				td.innerHTML = '&nbsp;';
			}

			// Because a single <p> wrapping the contents of a <td> is
			// initially superfluous and should be stripped out.
			var $p = $('>p', td);
			if (1 === $p.length) {
				$p.contents().unwrap();
			}
		});

		// Because Aloha does not provide a means for editors to manipulate
		// these properties.
		$content.find('table,th,td,tr')
			.removeAttr('width')
			.removeAttr('height')
			.removeAttr('valign');

		// Because Aloha table handling simply does not regard colgroups.
		// @TODO Use sanitize.js?
		$content.find('colgroup').remove();
	}

	/**
	 * Return true if the nodeType is allowed in the settings,
	 * Aloha.settings.contentHandler.allows.elements
	 * 
	 * @param {String} nodeType	The tag name of the element to evaluate
	 * 
	 * @return {Boolean}
	 */
	function isAllowedNodeName(nodeType){
		return !!(
			Aloha.settings.contentHandler
			&& Aloha.settings.contentHandler.allows
			&& Aloha.settings.contentHandler.allows.elements
			&& ($.inArray(
		              nodeType.toLowerCase(), 
				      Aloha.settings.contentHandler.allows.elements
				         ) !== -1
			   )
		);
	}

	var GenericContentHandler = Manager.createHandler({

		/**
		 * Transforms pasted content to make it safe and ready to be used in
		 * Aloha Editables.
		 *
		 * @param {jQuery.<HTMLElement>|string} content
		 * @return {string} Clean HTML
		 */
		handleContent: function (content) {
			var $content = Utils.wrapContent(content);
			if (!$content) {
				return content;
			}

			// If an aloha-block is found inside the pasted content, no modify
			// should be made in the pasted content because it can be assumed
			// this is content deliberately placed by Aloha and should not be
			// cleaned.
			if ($content.find('.aloha-block').length) {
				return $content.html();
			}

			prepareTables($content);
			this.cleanLists($content);
			this.removeComments($content);
			this.unwrapTags($content);
			this.removeStyles($content);
			this.removeNamespacedElements($content);
			//this.transformLinks($content);

			var transformFormatting = true;

			if (Aloha.settings.contentHandler
				&& Aloha.settings.contentHandler.handler
				&& Aloha.settings.contentHandler.handler.generic
				&& typeof Aloha.settings.contentHandler.handler.generic.transformFormattings !== 'undefinded'
				&& !Aloha.settings.contentHandler.handler.generic.transformFormattings ) {
				transformFormatting = false;
			}

			if (transformFormatting) {
			    this.transformFormattings($content);
			}

			return $content.html();
		},

		/**
		 * Cleans lists.
		 * The only allowed children of ol or ul elements are li's. Everything
		 * else will be removed.
		 *
		 * See http://validator.w3.org/check with following invalid markup for
		 * example:
		 * <!DOCTYPE html><head><title></title></head><ul><li>ok</li><ol></ol></ul>
		 *
		 * @param {jQuery.<HTMLElement>} $content
		 */
		cleanLists: function ($content) {
			$content.find('ul,ol').find('>:not(li)').remove();
		},

		/**
		 * Transform formattings
		 * @param content
		 */
		transformFormattings: function ( content ) {
			// find all formattings we will transform
			// @todo this makes troubles -- don't change semantics! at least in this way...

			var selectors = [],
				i
			;

			for (i = 0; i < formattingTags.length; i++) {
				if (!isAllowedNodeName(formattingTags[i])) {
					selectors.push(formattingTags[i]);
				}
			}

			content.find(selectors.join(',')).each(function () {
				if (this.nodeName === 'STRONG') {
					// transform strong to b
					Aloha.Markup.transformDomObject($(this), 'b');
				} else if (this.nodeName === 'EM') {
					// transform em to i
					Aloha.Markup.transformDomObject($(this), 'i');
				} else if (this.nodeName === 'S' || this.nodeName == 'STRIKE') {
					// transform s and strike to del
					Aloha.Markup.transformDomObject($(this), 'del');
				} else if (this.nodeName === 'U') {
					// transform u?
					$(this).contents().unwrap();
				}
			});
		},

		/**
		 * Transform links
		 * @param content
		 */
		transformLinks: function ( content ) {
			// find all links and remove the links without href (will be destination anchors from word table of contents)
			// aloha is not supporting anchors at the moment -- maybe rewrite anchors in headings to "invisible"
			// in the test document there are anchors for whole paragraphs --> the whole P appear as link
			content.find('a').each(function () {
				if ( typeof $(this).attr('href') === 'undefined' ) {
					$(this).contents().unwrap();
				}
			});
		},

		/**
		 * Remove all comments
		 * @param content
		 */
		removeComments: function ( content ) {
			var that = this;

			// ok, remove all comments
			content.contents().each(function () {
				if (this.nodeType === 8) {
					$(this).remove();
				} else {
					// do recursion
					that.removeComments($(this));
				}
			});
		},

		/**
		 * Remove some unwanted tags from content pasted
		 * @param content
		 */
		unwrapTags: function ( content ) {
			var that = this;

			// Note: we exclude all elements (they will be spans) here, that have the class aloha-wai-lang
			// TODO find a better solution for this (e.g. invent a more generic aloha class for all elements, that are
			// somehow maintained by aloha, and are therefore allowed)
			content.find('span,font,div').not('.aloha-wai-lang').each(function () {
				if (this.nodeName == 'DIV') {
					// safari and chrome cleanup for plain text paste with working linebreaks
					if (this.innerHTML === '<br>') {
						$(this).contents().unwrap();
					} else {
						$( Aloha.Markup.transformDomObject($(this), 'p').append('<br>') ).contents().unwrap();
					}
				} else {
					$(this).contents().unwrap();
				}
			});
		},

		/**
		 * Remove styles
		 * @param content
		 */
		removeStyles: function ( content ) {
			var that = this;

			// completely remove style tags
			content.children('style').filter(function () {
				return this.contentEditable !== 'false';
			}).remove();

			// remove style attributes and classes
			content.children().filter(function () {
				return this.contentEditable !== 'false';
			}).each(function () {
				$(this).removeAttr('style').removeClass();
				that.removeStyles($(this));
			});
		},

		/**
		 * Remove all elements which are in different namespaces
		 * @param content
		 */
		removeNamespacedElements: function ($content) {
			// get all elements
			$content.find('*').each(function () {
				// try to determine the namespace prefix ('prefix' works for W3C
				// compliant browsers, 'scopeName' for IE)

				var nsPrefix = this.prefix ? this.prefix
						: (this.scopeName ? this.scopeName : undefined);
				// when the prefix is set (and different from 'HTML'), we remove the
				// element
				if ((nsPrefix && nsPrefix !== 'HTML') || this.nodeName.indexOf(':') >= 0 ) {
					var $this = $(this), $contents = $this.contents();
					if ($contents.length) {
						// the element has contents, so unwrap the contents
						$contents.unwrap();
					} else {
						// the element is empty, so remove it
						$this.remove();
					}
				}
			});
		}
	});

	return GenericContentHandler;
});

/**
 * Copyright (c) 2010 by Gabriel Birke
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the 'Software'), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

function Sanitize(){
  var options;
  options = arguments[0] || {};
  this.jQuery = arguments[1] || {};
  this.config = {}
  this.config.elements = options.elements ? options.elements : [];
  this.config.attributes = options.attributes ? options.attributes : {};
  this.config.attributes[Sanitize.ALL] = this.config.attributes[Sanitize.ALL] ? this.config.attributes[Sanitize.ALL] : [];
  this.config.allow_comments = options.allow_comments ? options.allow_comments : false;
  this.allowed_elements = {};
  this.config.protocols = options.protocols ? options.protocols : {};
  this.config.add_attributes = options.add_attributes ? options.add_attributes  : {};
  this.dom = options.dom ? options.dom : document;
	for (var i = 0, len = this.config.elements.length; i < len; i++) {
		this.allowed_elements[this.config.elements[i]] = true;
	}
  this.config.remove_element_contents = {};
  this.config.remove_all_contents = false;
  if(options.remove_contents) {

     if (options.remove_contents instanceof Array) {
		  var elementContents = this.config.remove_element_contents;
		  for (var i = 0, len = options.remove_contents.length; i < len; i++) {
			  elementContents[options.remove_contents[i]] = true;
		  }
	  }
	  else {
		  this.config.remove_all_contents = true;
	  }
  }
  this.transformers = options.transformers ? options.transformers : [];

  // filters might let the sanitizer stop clean elements (and their children)
  this.filters = options.filters ? options.filters : [];
}

Sanitize.REGEX_PROTOCOL = /^([A-Za-z0-9\+\-\.\&\;\*\s]*?)(?:\:|&*0*58|&*x0*3a)/i
Sanitize.RELATIVE = '__relative__'; // emulate Ruby symbol with string constant

Sanitize.prototype.clean_node = function(container) {
  var fragment = this.dom.createDocumentFragment();
  this.current_element = fragment;
  this.whitelist_nodes = [];

  

  /**
   * Utility function to check if an element exists in an array
   */
  function _array_index(needle, haystack) {
	  var i,
	      len = haystack.length
	  for (i = 0; i < len; i++) {
		  if (haystack[i] === needle)
			  return i;
	  }
	  return -1;
  }

	function _merge_arrays_uniq() {
		var result = [],
		    uniq_hash = {},
			i,
		    len = arguments.length,
		    arg;
		for (i = 0; i < len; i++) {
			arg = arguments[i];
			if (!arg || !arg.length)
				continue;
			for (var j = 0, argLen = arg.length; j < argLen; j++) {
				if (uniq_hash[arg[j]])
					continue;
				uniq_hash[arg[j]] = true;
				result.push(arg[j]);
			}
		}
		return result;
	}
  
  /**
   * Clean function that checks the different node types and cleans them up accordingly
   * @param elem DOM Node to clean
   */
  function _clean(elem) {
    var clone,
	    i,
	    len = this.filters.length;

    // check whether the elem passes all of the filters
	  for (i = 0; i < len; i++) {
		  if (!this.filters[i](elem)) {
			  clone = elem.cloneNode(true);
			  this.current_element.appendChild(clone);
			  return;
		  }
	  }

    switch(elem.nodeType) {
      // Element
      case 1:
        _clean_element.call(this, elem)
        break;
      // Text
      case 3:
        var clone = elem.cloneNode(false);
        this.current_element.appendChild(clone);
        break;
      // Entity-Reference (normally not used)
      case 5:
        var clone = elem.cloneNode(false);
        this.current_element.appendChild(clone);
        break;
      // Comment
      case 8:
        if(this.config.allow_comments) {
          var clone = elem.cloneNode(false);
          this.current_element.appendChild(clone);
        }
      default:
        //console.log("unknown node type", elem.nodeType)
    }
 
  }
  
  function _clean_element(elem) {
    var i, len, parent_element, name, allowed_attributes, attr, attr_name, attr_node, protocols, del, attr_ok;
    var transform = _transform_element.call(this, elem);
    var jQuery = this.jQuery;
    var isIE7 = jQuery.browser.msie && jQuery.browser.version === "7.0";
    
    elem = transform.node;
    name = elem.nodeName.toLowerCase();
    
    // check if element itself is allowed
    parent_element = this.current_element;
    if (this.allowed_elements[name] || transform.whitelist) {
        this.current_element = this.dom.createElement(elem.nodeName);
        parent_element.appendChild(this.current_element);

      // clean attributes
      allowed_attributes = _merge_arrays_uniq(
        this.config.attributes[name],
        this.config.attributes['__ALL__'],
        transform.attr_whitelist
      );
	    len = allowed_attributes.length;
	    for (i = 0; i < len; i++) {
		    attr_name = allowed_attributes[i];
		    attr = elem.attributes[attr_name];
		    if (attr) {
			    attr_ok = true;
			    // Check protocol attributes for valid protocol
			    if (this.config.protocols[name] && this.config.protocols[name][attr_name]) {
				    protocols = this.config.protocols[name][attr_name];
				    del = attr.nodeValue.toLowerCase().match(Sanitize.REGEX_PROTOCOL);
				    if (del) {
					    attr_ok = (_array_index(del[1], protocols) != -1);
				    }
				    else {
					    attr_ok = (_array_index(Sanitize.RELATIVE, protocols) != -1);
				    }
			    }
			    if (attr_ok) {
				    // sanitize does not work in IE7. It tries to set the style attribute via setAttributeNode() and this is know to not work in IE7
				    // (see http://www.it-blogger.com/2007-06-22/microsofts-internetexplorer-und-mitglied-nicht-gefunden/ as a reference)
				    if (!isIE7 || (isIE7 && "style" !== attr_name)) {
					    this.current_element.setAttribute(attr_name, attr.nodeValue)
				    }
			    }
		    }
      }

      // Add attributes
      if(this.config.add_attributes[name]) {
        for(attr_name in this.config.add_attributes[name]) {
        	// sanitize does not work in IE7. It tries to set the style attribute via setAttributeNode() and this is know to not work in IE7
			// (see http://www.it-blogger.com/2007-06-22/microsofts-internetexplorer-und-mitglied-nicht-gefunden/ as a reference)
        	if(!isIE7 || (isIE7 && "style" !== attr_name)) {
	          this.current_element.setAttribute(attr_name, this.config.add_attributes[name][attr_name]);
        	}
        }
      }
    } // End checking if element is allowed
    // If this node is in the dynamic whitelist array (built at runtime by
    // transformers), let it live with all of its attributes intact.
    else if(_array_index(elem, this.whitelist_nodes) != -1) {
      this.current_element = elem.cloneNode(true);
      // Remove child nodes, they will be sanitiazied and added by other code
	    var childNodesLength = this.current_element.childNodes.length;
	    while(childNodesLength > 0) {
            this.current_element.removeChild(this.current_element.firstChild);
      }
      parent_element.appendChild(this.current_element);
    }

    // iterate over child nodes
    if(!this.config.remove_all_contents && !this.config.remove_element_contents[name]) {
	    var childNodes = elem.childNodes,
		    len = childNodes.length;
	    for (i = 0; i < len; i++) {
		    _clean.call(this, childNodes[i]);
	    }
    }

    // some versions of IE don't support normalize.
    if(this.current_element.normalize) {
      this.current_element.normalize();
    }
    this.current_element = parent_element;
  } // END clean_element function
  
  function _transform_element(node) {
    var output = {
      attr_whitelist:[],
      node: node,
      whitelist: false
    };
    var transform,
	    i,
	    j,
	    transLength = this.transformers.length,
	    len;
	for (i = 0; i < transLength; i++) {
      transform = this.transformers[i]({
        allowed_elements: this.allowed_elements,
        config: this.config,
        node: node,
        node_name: node.nodeName.toLowerCase(),
        whitelist_nodes: this.whitelist_nodes,
        dom: this.dom
      });
      if(transform == null) 
        continue;
      else if(typeof transform == 'object') {
        if(transform.whitelist_nodes && transform.whitelist_nodes instanceof Array) {
	      len = transform.whitelist_nodes.length
	      for (j = 0; j < len; j++) {
            if(_array_index(transform.whitelist_nodes[j], this.whitelist_nodes) == -1) {
              this.whitelist_nodes.push(transform.whitelist_nodes[j]);
            }
          }
        }
        output.whitelist = transform.whitelist ? true : false;
        if(transform.attr_whitelist) {
          output.attr_whitelist = _merge_arrays_uniq(output.attr_whitelist, transform.attr_whitelist);
        }
        output.node = transform.node ? transform.node : output.node;
      }
      else {
        throw new Error("transformer output must be an object or null");
      }
    }
    return output;
  }


  var childNodes = container.childNodes,
      i;
  for (i = 0, len = childNodes.length; i < len; i++) {
    _clean.call(this, childNodes[i]);
  }
  
  if(fragment.normalize) {
    fragment.normalize();
  }
  
  return fragment;
  
};
define("vendor/sanitize", function(){});

/* sanitizecontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('contenthandler/sanitizecontenthandler',[
	'aloha/core',
	'jquery',
	'aloha/contenthandlermanager',
	'aloha/plugin',
	'aloha/console',
	'vendor/sanitize'
],
function( Aloha, jQuery, ContentHandlerManager, Plugin, console ) {
	
	
	var sanitize;
	
	// predefined set of sanitize options if no dynamic or custom config is used
	if( !Aloha.defaults.sanitize ) {
		Aloha.defaults.sanitize = {}
	}

	// very restricted sanitize config
	Aloha.defaults.sanitize.restricted = {
		elements: [ 'b', 'em', 'i', 'strong', 'u', 'del', 'p', 'span', 'div', 'br' ]
	}

	// sanitize  config allowing a bit more (no tables)
	Aloha.defaults.sanitize.basic = {
		elements: [
			'a', 'abbr', 'b', 'blockquote', 'br', 'cite', 'code', 'dd', 'del', 'dl', 'dt', 'em',
			'i', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong', 'sub',
			'sup', 'u', 'ul' ],

		attributes: {
			'a' : ['href'],
			'blockquote' : ['cite'],
			'q' : ['cite'],
			'abbr': ['title']
		},

		//add_attributes: {
			//  'a': {'rel': 'nofollow'}
		//},

		protocols: {
			'a' : {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']},
			'blockquote' : {'cite': ['http', 'https', '__relative__']},
			'q' : {'cite': ['http', 'https', '__relative__']}
		}
	}

	// relaxed sanitize config allows also tables
	Aloha.defaults.sanitize.relaxed = {
		elements: [
			'a', 'abbr', 'b', 'blockquote', 'br', 'caption', 'cite', 'code', 'col',
			'colgroup', 'dd', 'del', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
			'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong',
			'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u',
			'ul', 'span', 'hr', 'object', 'div'
		],

		attributes: {
			'a': ['href', 'title', 'id', 'class', 'target', 'data-gentics-aloha-repository', 'data-gentics-aloha-object-id'],
			'div': ['id','class','style'],
			'abbr': ['title'],
			'blockquote': ['cite'],
			'br': ['class'],
			'col': ['span', 'width'],
			'colgroup': ['span', 'width'],
			'img': ['align', 'alt', 'height', 'src', 'title', 'width', 'class', 'data-caption', 'data-align', 'data-width', 'data-original-image'],
			'ol': ['start', 'type'],
			'p': ['class', 'style', 'id'],
			'q': ['cite'],
			'table': ['summary', 'width'],
									// For IE7 it matters the uppercase 'S' in rowSpan, colSpan
			'td': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'width'],
			'th': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'scope', 'width'],
			'ul': ['type'],
			'span': ['class','style','lang','xml:lang','role']
		},

		protocols: {
			'a': {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']},
			'blockquote': {'cite': ['http', 'https', '__relative__']},
			'img': {'src' : ['http', 'https', '__relative__']},
			'q': {'cite': ['http', 'https', '__relative__']}
		}
	}

	function initSanitize (configAllows) {
		var 
			filter = [ 'restricted', 'basic', 'relaxed' ],
			config = Aloha.defaults.supports; // @TODO: needs to be implemented into all plugins

		// @TODO think about Aloha.settings.contentHandler.sanitize name/options
		if (Aloha.settings.contentHandler.sanitize &&
			jQuery.inArray(Aloha.settings.contentHandler.sanitize, filter) > -1) {
			config = Aloha.defaults.sanitize[Aloha.settings.contentHandler.sanitize];
		} else {
			// use relaxed filter by default
			config = Aloha.defaults.sanitize.relaxed;
		}

		// @TODO move to Aloha.settings.contentHandler.sanitize.allows ?
		if (Aloha.settings.contentHandler.allows) {
			config = Aloha.settings.contentHandler.allows;
		}

		if (configAllows) {
			config = configAllows;
		}

		// add a filter to stop cleaning elements with contentEditable "false"
		config.filters = [function( elem ) {
			return elem.contentEditable != "false";
		}];
		sanitize = new Sanitize( config, jQuery );
	}

	var SanitizeContentHandler = ContentHandlerManager.createHandler({
		/**
		 * Handle the content from eg. paste action and sanitize the html
		 * @param content
		 */
		handleContent: function (content, options, editable)  {
			if (!editable) {
				return content;
			}

			var sanitizeConfig;
			var contentHandlerConfig;

			if (Aloha.settings.contentHandler &&
			    Aloha.settings.contentHandler.handler &&
			    Aloha.settings.contentHandler.handler.sanitize) {
				// individual sanitize config per editable -- should support merging of configs from other plugins ...
				if (Aloha.settings.contentHandler.handler.sanitize) {
					contentHandlerConfig = Aloha.settings.contentHandler.handler.sanitize;
				}
				var containerId = contentHandlerConfig['#' + editable.getId()];
				if (typeof containerId !== 'undefined') {
					sanitizeConfig = contentHandlerConfig;
				} else {
					var containerClasses = editable.obj.attr('class').split(' ');
					for (var i = 0; i < containerClasses.length; i++) {
						if (typeof contentHandlerConfig['.' + containerClasses[i]] !== 'undefined') {
							sanitizeConfig = contentHandlerConfig['.' + containerClasses[i]];
						}
					}
				}
			}

			if ( typeof sanitize === 'undefined' || typeof sanitizeConfig !== 'undefined') {
				initSanitize(sanitizeConfig);
			}

			if (typeof content === 'string'){
				content = jQuery('<div>' + content + '</div>').get(0);
			} else if (content instanceof jQuery) {
				content = jQuery('<div>').append(content).get(0);
			}

			return jQuery('<div>').append(sanitize.clean_node(content)).html();
		}
	});

	return SanitizeContentHandler;
});

/*!
 * Aloha Editor
 * Author & Copyright (c) 2010-2013 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed unter the terms of http://www.aloha-editor.com/license.html
 *
 * @overview
 * Prepares block-level elements in contents of editables that are initialized
 * for editing ('initEditing'), or when exporting contents of editable for
 * saving ('getContents').
 */
define('contenthandler/blockelementcontenthandler',[
	'jquery',
	'aloha/core',
	'aloha/contenthandlermanager',
	'contenthandler/contenthandler-utils',
	'util/functions',
	'util/html',
	'util/arrays'

], function (
	$,
	Aloha,
	ContentHandlerManager,
	Utils,
	Functions,
	Html,
	Arrays
) {
	

	var blocksSelector = Html.BLOCKLEVEL_ELEMENTS.join();
	var nonVoidBlocksSelector = Arrays.subtract(
			Html.BLOCKLEVEL_ELEMENTS,
			Html.VOID_ELEMENTS
		);

	var NOT_ALOHA_BLOCK_FILTER = ':not(.aloha-block)';

	var isNotIgnorableWhitespace =
			Functions.complement(Html.isIgnorableWhitespace);

	/**
	 * Removes the <br> tag that is at the end of the given container.
	 * Invisible white spaces are ignored.
	 *
	 * @param {number} i Index of element in its collection. (Unused)
	 * @param {HTMLElement} element The container in which to remove the <br>.
	 */
	function removeTrailingBr(i, element) {
		var node = Html.findNodeRight(
			element.lastChild,
			isNotIgnorableWhitespace
		);
		if (node && 'br' === node.nodeName.toLowerCase()) {
			$(node).remove();
		}
	}

	/**
	 * Prepares this content for editing
	 *
	 * @param {number} i Index of element in its collection. (Unused)
	 * @param {HTMLElement} element
	 */
	function prepareForEditing(i, element) {
		var $element = $(element);

		$element.filter(nonVoidBlocksSelector).remove();

		if ($.browser.msie) {
			// Because even though content edited by Aloha Editor is no longer
			// exported with propping <br>'s that are annotated with
			// "aloha-end-br" classes,  this clean-up still needs to be done for
			// content that was edited using legacy Aloha Editor.
			$element.filter('br.aloha-end-br').remove();

			// Because IE's Trident engine goes against W3C's HTML specification
			// by rendering empty block-level elements with height if they are
			// contentEditable.  Propping <br> elements therefore result in 2
			// lines being displayed rather than 1 (which was the intention of
			// having the propping <br> element is).  Because these empty
			// content editable block-level elements are not rendered invisibly
			// in IE, we can remove the propping <br> in otherwise empty
			// block-level elements.
			$element.filter(blocksSelector).each(removeTrailingBr);
		}

		$element.children(NOT_ALOHA_BLOCK_FILTER).each(prepareForEditing);
	}

	/**
	 * Prepares the content for editing in IE versions older than version 8.
	 *
	 * Ensure that all empty blocklevel elements must contain a zero-width
	 * whitespace.
	 *
	 * @param {number} i Unused
	 * @param {HTMLElement} element
	 */
	function prepareEditingInOldIE(i, element) {
		var $element = $(element);
		$element.filter(nonVoidBlocksSelector).append('\u200b');
		$element.children(NOT_ALOHA_BLOCK_FILTER).each(prepareEditingInOldIE);
	}

	/**
	 * For a given DOM element, will make sure that it, and every one of its
	 * child nodes, which is a block-level element ends with a <br> node.
	 *
	 * This ensures that a block is rendered visibly (with atleast one character
	 * height).
	 *
	 * @param {number} i Unused
	 * @param {HTMLElement} element
	 */
	function propBlockElements(i, element) {
		var $element = $(element);
		$element.filter(nonVoidBlocksSelector).filter(':empty').append('<br/>');
		$element.children(NOT_ALOHA_BLOCK_FILTER).each(propBlockElements);
	}

	return ContentHandlerManager.createHandler({
		handleContent: function handleBlockLevelContent(content, options) {
			if (!options) {
				return content;
			}
			var $content = Utils.wrapContent(content);
			if (!$content) {
				return content;
			}
			switch (options.command) {
			case 'initEditable':
				$content.children(NOT_ALOHA_BLOCK_FILTER)
				        .each(prepareForEditing);

				if ($.browser.msie && $.browser.version <= 7) {
					$content.children(NOT_ALOHA_BLOCK_FILTER)
					        .each(prepareEditingInOldIE);
				}
				break;
			case 'getContents':
				$content.children(NOT_ALOHA_BLOCK_FILTER)
				        .each(propBlockElements);
				break;
			}
			return $content.html();
		}
	});
});

/*global define: true */

/*!
* Aloha Editor
* Author & Copyright (c) 2010-2012 Gentics Software GmbH
* aloha-sales@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*/

/**
 * @name contenthandler
 */
define('contenthandler/contenthandler-plugin',[
	'jquery',
	'aloha/plugin',
	'aloha/contenthandlermanager',
	'contenthandler/wordcontenthandler',
	'contenthandler/genericcontenthandler',
	'contenthandler/sanitizecontenthandler',
	'contenthandler/blockelementcontenthandler'
], function (
	$,
	Plugin,
	ContentHandlerManager,
	WordContentHandler,
	GenericContentHandler,
	SanitizeContentHandler,
	BlockelementContentHandler
) {
	

	/**
	 * The default content handlers which will always be loaded with Aloha
	 * when the contenthandler plugin is initialized.
	 *
	 * @object<string, ContentHandler>
	 * @const
	 */
	var DEFAULT_HANDLERS = {
		word: WordContentHandler,
		generic: GenericContentHandler,
		sanitize: SanitizeContentHandler,
		blockelement: BlockelementContentHandler
	};

	var ContentHandler = Plugin.create('contenthandler', {

		/**
		 * Will simply register the default content handlers.
		 *
		 * @override
		 */
		init: function () {
			var handler;
			for (handler in DEFAULT_HANDLERS) {
				if (DEFAULT_HANDLERS.hasOwnProperty(handler)) {
					ContentHandlerManager.register(handler,
							DEFAULT_HANDLERS[handler]);
				}
			}
		}
	});

	return ContentHandler;
});

define('characterpicker/nls/i18n',{
	"root":  {
		"button.addcharacter.tooltip": "pick special characters"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* characterpicker-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */

define('characterpicker/characterpicker-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'ui/ui',
	'ui/button',
	'ui/floating',
	'PubSub',
	'i18n!characterpicker/nls/i18n'
], function (
	Aloha,
	$,
	Plugin,
	Ui,
	Button,
	Floating,
	PubSub,
	i18n
) {
	

	var $DOCUMENT = $(document);
	var $WINDOW = $(window);

	/**
	 * Tracks the range at the point at which the editor opens the character
	 * picker.
	 *
	 * @type {Range}
	 */
	var rangeAtOpen;

	/**
	 * A cache of all the overlay configurations. If all editables have the same
	 * configuration, only a single overlay will be created that will be used by
	 * all editables.
	 *
	 * @type {object<string, Overlay>}
	 */
	var configs = {};

	/**
	 * Checks whether the character picker overlay is visible.
	 *
	 * @param {Overlay} overlay
	 * @return {boolean} True if the overlay is visible.
	 */
	function isOverlayVisible(overlay) {
		return overlay.$element.css('display') === 'table';
	}

	/**
	 * Prepares the overlay to close when a click event is triggered on the body
	 * document.
	 *
	 * @param {Overlay} overlay
	 */
	function hideOnBodyClick(overlay) {
		overlay.$element.click(function ($event) {
			$event.stopPropagation();
		});

		$('body').click(function ($event) {
			// Because click events on the overlay ui should not cause it to
			// hide itself.
			if (!overlay._overlayActive
					|| ($event.target === overlay.$element[0])
					|| $($event.target).is('.aloha-icon-characterpicker')
					|| $($event.target).find('.aloha-icon-characterpicker').length) {
				return;
			}
			overlay.hide();
		});
	}

	/**
	 * Prepares the given overlay to close when the ESC button is clicked.
	 *
	 * @param {Overlay} overlay
	 */
	function hideOnEsc(overlay) {
		$DOCUMENT.keyup(function ($event) {
			if ((27 === $event.keyCode) && isOverlayVisible(overlay)) {
				overlay.hide();
			}
		});
	}

	/**
	 * Helper function that takes the computed style-property of one element and
	 * applies it to another one, depending on the browser implementation.
	 *
	 * @param {HTMLElement} source The element of which the style element is
	 *                             taken.
	 * @param {jQuery.<HTMLElement>} target Where the style will be applied.
	 * @param {string} styleProp The css property which shall be copied.
	 */
	function copyStyle(source, $target, styleProp) {
		// TODO: Move to strings.js
		var camelize = function (str) {
			return str.replace(/\-(\w)/g, function (str, letter) {
				return letter.toUpperCase();
			});
		};

		var style;

		if (source.currentStyle) {
			style = source.currentStyle[camelize(styleProp)];
		} else if (document.defaultView
		        && document.defaultView.getComputedStyle) {
			style = document.defaultView
			                .getComputedStyle(source, null)
			                .getPropertyValue(styleProp);
		} else {
			style = source.style[camelize(styleProp)];
		}

		if (style) {
			$target.css(styleProp, style);
		}
	}

	/**
	 * Enables navigation through the character table with the arrow keys and
	 * select one with the enter key.
	 *
	 * @param {Overlay} overlay
	 * @param {function} onSelect Function to invoke when Enter is pressed.
	 */
	function cursorMovements(overlay, onSelect) {
		var movements = {
			// ←┘
			13: function select($current) {
				overlay.hide();
				onSelect($current.text());
			},
			// ←
			37: function left($current) {
				var $prev = $current.prev();
				if ($prev.length) {
					$prev.addClass('focused');
					$current.removeClass('focused');
				}
			},
			// ↑
			38: function up($current) {
				var $prevRow = $current.parent().prev();
				if ($prevRow.length) {
					var $prev = $(
						$prevRow.children()[$current.index()]
					).addClass('focused');
					if ($prev.length) {
						$current.removeClass('focused');
					}
				}
			},
			// →
			39: function right($current) {
				var $next = $current.next().addClass('focused');
				if ($next.length) {
					$current.removeClass('focused');
				}
			},
			// ↓
			40: function down($current) {
				var $nextRow = $current.parent().next();
				if ($nextRow.length) {
					var $next = $(
						$nextRow.children()[$current.index()]
					).addClass('focused');
					if ($next.length) {
						$current.removeClass('focused');
					}
				}
			}
		};

		$DOCUMENT.keydown(function ($event) {
			$event.stopPropagation();
			if (movements[$event.keyCode] && isOverlayVisible(overlay)) {
				movements[$event.keyCode](overlay.$element.find('.focused'));
				return false;
			}
		});
	}

	/**
	 * Generates a map of the given list character on the overlay.
	 *
	 * @param {Overlay} overlay
	 * @param {String} characters
	 */
	function generateCharacterTable(overlay, characters) {
		var textarea = document.createElement('textarea');
		textarea.innerHTML = characters;

		var list = $.grep(textarea.value.split(' '), function (chr) {
			return '' !== chr;
		});

		var table = ['<tr>'];
		var i = 0;
		var chr;
		while ((chr = list[i])) {
			// New row every 15 characters
			if (0 !== i && (0 === (i % 15))) {
				table.push('</tr><tr>');
			}
			table.push('<td unselectable="on">' + chr + '</td>');
			i++;
		}
		table.push('</tr>');

		overlay.$tbody.empty().append(table.join(''));

		overlay.$element.delegate('td', 'mouseover', function () {
			overlay.$element.find('.focused').removeClass('focused');
			$(this).addClass('focused');
		}).delegate('td', 'mouseout', function () {
			$(this).removeClass('focused');
		}).delegate('td', 'click', function () {
			overlay.$element.hide();
			overlay.onSelect($(this).text());
		});
	}

	/**
	 * Calculates the offset at which to position the overlay element.
	 *
	 * @param {jQuery.<HTMLElement>} $element A DOM element around which to
	 *                                        calculate the offset.
	 */
	function calculateOffset($element) {
		var offset = $element.offset();
		if ('fixed' === Floating.POSITION_STYLE) {
			offset.top -= $WINDOW.scrollTop();
			offset.left -= $WINDOW.scrollLeft();
		}
		return offset;
	}

	/**
	 * Inserts the selected character, at the editor's selection.
	 *
	 * @param {String} character
	 */
	function onSelectCharacter(character) {
		if (Aloha.activeEditable) {
			rangeAtOpen.select();
			Aloha.execCommand('insertHTML', false, character);

			// Because after the character was inserted, move the selection
			// forward.
			rangeAtOpen.endContainer = rangeAtOpen.startContainer;
			rangeAtOpen.endOffset = ++rangeAtOpen.startOffset;
			rangeAtOpen.select();
		}
	}

	/**
	 * The Character Picker Overlay.
	 *
	 * @param {function} onSelect
	 * @type {Overlay}
	 */
	function Overlay(onSelect) {
		var overlay = this;

		overlay.$element = $('<table class="aloha-character-picker-overlay" ' +
			'unselectable="on" role="dialog"><tbody></tbody></table>');

		// Because if mousedown bubbles up, there won't be an activeEditable.
		// FIXME: The above needs to be better explained.
		overlay.$element.mousedown(function ($event) {
			return false;
		});

		overlay.onSelect = onSelect;
		overlay.$tbody = overlay.$element.find('tbody');
		overlay.$element.appendTo($('body'));

		hideOnBodyClick(overlay);
		hideOnEsc(overlay);
		cursorMovements(overlay, onSelect);

		Aloha.bind('aloha-editable-deactivated', function () {
			overlay.hide();
		});
	}

	Overlay.prototype = {

		/**
		 * Shows the character overlay at the insert button's position.
		 *
		 * @param {jQuery.<HTMLElement>} $insert Insert button.
		 */
		show: function ($insert) {
			var overlay = this;

			// Because the overlay needs to be reposition relative its button.
			overlay.$element
			       .css(calculateOffset($insert))
			       .css('position', Floating.POSITION_STYLE)
			       .show()
			       .find('.focused')
			       .removeClass('focused');

			overlay.$element
			       .find('td')
			       .eq(0)
			       .addClass('focused');

			overlay._overlayActive = true;
		},

		/**
		 * Hides the character overlay.
		 */
		hide: function () {
			this.$element.hide();
			this._overlayActive = false;
		}
	};

	/**
	 * Generates an character picker overlay for the given editable.
	 *
	 * Because each editable may have its own configuration and therefore may
	 * have its own overlay.
	 *
	 * @param {CharacterPicker} characterpicker
	 * @param {Aloha.Editable} editable
	 * @return {Overlay|null} The generated character picker overlay, or null
	 *                        of the editable is not configured for the
	 *                        character picker.
	 */
	function generateOverlay(characterpicker, editable) {
		var config = characterpicker.getEditableConfig(editable.obj);
		if (!config) {
			return null;
		}
		var characters = $.isArray(config) ? config.join(' ') : config;
		var overlay = configs[characters];
		if (!overlay) {
			overlay = new Overlay(onSelectCharacter);
			generateCharacterTable(overlay, characters);
			configs[characters] = overlay;
		}
		return overlay;
	}

	/**
	 * @type {Plugin}
	 */
	var CharacterPicker =  Plugin.create('characterpicker', {

		settings: {},

		config: '&#38; &#34; &#162; &#8364; &#163; &#165; &#169; &#174; &#8482; &#8240; &#181; &#183; &#8226; &#8230; &#8242; &#8243; &#167; &#182; &#223; &#8249; &#8250; &#171; &#187; &#8216; &#8217; &#8220; &#8221; &#8218; &#8222; &#60; &#62; &#8804; &#8805; &#8211; &#8212; &#175; &#8254; &#164; &#166; &#168; &#161; &#191; &#710; &#732; &#176; &#8722; &#177; &#247; &#8260; &#215; &#185; &#178; &#179; &#188; &#189; &#190; &#402; &#8747; &#8721; &#8734; &#8730; &#8764; &#8773; &#8776; &#8800; &#8801; &#8712; &#8713; &#8715; &#8719; &#8743; &#8744; &#172; &#8745; &#8746; &#8706; &#8704; &#8707; &#8709; &#8711; &#8727; &#8733; &#8736; &#180; &#184; &#170; &#186; &#8224; &#8225; &#192; &#193; &#194; &#195; &#196; &#197; &#198; &#199; &#200; &#201; &#202; &#203; &#204; &#205; &#206; &#207; &#208; &#209; &#210; &#211; &#212; &#213; &#214; &#216; &#338; &#352; &#217; &#218; &#219; &#220; &#221; &#376; &#222; &#224; &#225; &#226; &#227; &#228; &#229; &#230; &#231; &#232; &#233; &#234; &#235; &#236; &#237; &#238; &#239; &#240; &#241; &#242; &#243; &#244; &#245; &#246; &#248; &#339; &#353; &#249; &#250; &#251; &#252; &#253; &#254; &#255; &#913; &#914; &#915; &#916; &#917; &#918; &#919; &#920; &#921; &#922; &#923; &#924; &#925; &#926; &#927; &#928; &#929; &#931; &#932; &#933; &#934; &#935; &#936; &#937; &#945; &#946; &#947; &#948; &#949; &#950; &#951; &#952; &#953; &#954; &#955; &#956; &#957; &#958; &#959; &#960; &#961; &#962; &#963; &#964; &#965; &#966; &#967; &#968; &#969; &#8501; &#982; &#8476; &#977; &#978; &#8472; &#8465; &#8592; &#8593; &#8594; &#8595; &#8596; &#8629; &#8656; &#8657; &#8658; &#8659; &#8660; &#8756; &#8834; &#8835; &#8836; &#8838; &#8839; &#8853; &#8855; &#8869; &#8901; &#8968; &#8969; &#8970; &#8971; &#9001; &#9002; &#9674; &#9824; &#9827; &#9829; &#9830;',

		_constructor: function () {
			this._super('characterpicker');
		},

		init: function () {
			var characterpicker = this;

			if (Aloha.settings.plugins
					&& Aloha.settings.plugins.characterpicker) {
				characterpicker.settings
						= Aloha.settings.plugins.characterpicker;
			}

			var button = Ui.adopt('characterPicker', Button, {
				tooltip: i18n.t('button.addcharacter.tooltip'),
				icon: 'aloha-icon-characterpicker',
				scope: 'Aloha.continuoustext',
				click: function () {
					if (characterpicker.overlay) {
						rangeAtOpen = Aloha.Selection.rangeObject;

						var from = rangeAtOpen.startContainer.parentNode;
						var $to = characterpicker.overlay.$element;

						copyStyle(from, $to, 'font-family');
						copyStyle(from, $to, 'font-weight');
						copyStyle(from, $to, 'font-style');

						characterpicker.overlay.show(this.element);
					}
				}
			});

			/**
			 * Pre-generates overlays so that they will be ready when the editor
			 * click on an editable.
			 *
			 * @param {number} editableIndex
			 */
			function pregenerateOverlays(editableIndex) {
				if (editableIndex < Aloha.editables.length) {
					generateOverlay(characterpicker,
							Aloha.editables[editableIndex]);
					setTimeout(function () {
						pregenerateOverlays(editableIndex + 1);
					}, 100);
				}
			}

			// FIXME: ... but why?
			setTimeout(function () {
				pregenerateOverlays(0);
			}, 100);

			Aloha.bind('aloha-editable-activated', function ($event, data) {
				characterpicker.overlay =
						generateOverlay(characterpicker, data.editable);
				if (characterpicker.overlay) {
					button.show();
				} else {
					button.hide();
				}
			});

			PubSub.sub('aloha.floating.changed', function (message) {
				if (characterpicker.overlay) {
					characterpicker.overlay.$element.css(
						calculateOffset(button.element)
					);
				}
			});
		}

	});

	return CharacterPicker;
});

/* inserthtml.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('commands/inserthtml',
['aloha/core', 'jquery', 'aloha/command', 'aloha/selection', 'util/dom', 'aloha/contenthandlermanager', 'aloha/console'],
function(Aloha, jQuery, command, selection, dom, ContentHandlerManager, console) {
	

	// Exported commands
	command.register( 'inserthtml', {
		action: function(value, range) {
			var 
				$editable = jQuery(dom.getEditingHostOf(range.startContainer)),
				cac = range.commonAncestorContainer,
				i,
				selectedRange,
				domNodes = [];
			
			/**
			 * Paste the given object into the current selection.
			 * If inserting fails (because the object is not allowed to be inserted), unwrap the contents and try with that.
			 * @param object object to be pasted
			 */
			function pasteElement(object) {
				var $object = jQuery(object),
					contents;

				// try to insert the element into the DOM with limit the editable host
				// this fails when an element is not allowed to be inserted
				if (!dom.insertIntoDOM($object, range, $editable, false)) {
					
					// if that is not possible, we unwrap the content and insert every child element
					 contents = $object.contents();

					// when a block level element was unwrapped, we at least insert a break
					if (dom.isBlockLevelElement(object) || dom.isListElement(object)) {
						pasteElement(jQuery('<br/>').get(0));
					}

					// and now all children (starting from the back)
					for ( i = contents.length - 1; i >= 0; --i) {
						pasteElement(contents[i]);
					}
				}
			};

			// apply content handler to cleanup inserted data
			//if (typeof Aloha.settings.contentHandler.insertHtml === 'undefined') {
			// just use all registerd content handler or specity Aloha.defaults.contentHandler.insertHtml manually?
			//	Aloha.settings.contentHandler.insertHtml = Aloha.defaults.contentHandler.insertHtml;
			//}
			value = ContentHandlerManager.handleContent( value, { contenthandler: Aloha.settings.contentHandler.insertHtml } );

			// allowed values are string or jQuery objects
			// add value to a container div
			if ( typeof value === 'string' ){
				value = jQuery( '<div>' + value + '</div>' );
			} else if ( value instanceof jQuery ) {
				value = jQuery( '<div>' ).append(value);
			} else {
				throw "INVALID_VALUE_ERR";
			}
			
			// get contents of container div
			domNodes = value.contents();
			
			// check if range starts an ends in same editable host
//			if ( !(dom.inSameEditingHost(range.startContainer, range.endContainer)) ) {
//				throw "INVALID_RANGE_ERR";
//			}
			
			// delete currently selected contents
			dom.removeRange(range);
			
			for ( i = domNodes.length - 1; i >= 0; --i) {
				// insert the elements
				pasteElement(domNodes[i]);
			}

			// Call collapse() on the context object's Selection,
			// with last child's parent as the first argument and one plus its index as the second.
			if (domNodes.length > 0) {
				range = dom.setCursorAfter(domNodes.get(domNodes.length - 1));
			} else {
				// if nothing was pasted, just reselect the old range
				range.select();
			}

			dom.doCleanup({merge:true, removeempty: true}, range, cac);
			//In some cases selecting the range does not work properly 
			//e.g. when pasting from word in an h2 after the first character in IE
			//in these cases we should fail gracefully.
			//TODO check why the selection is failing
			try {
				range.select();
			} catch (e) {
				console.warn('Error:',e);
			}

		}
	});

});

/* commands-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('commands/commands-plugin',
['aloha/command', './inserthtml'],
function( command, commands ) {
	

		
});

define('block/block-utils',[
	'util/dom',
	'util/dom2',
	'util/html'
], function (
	DomLegacy,
	Dom,
	Html
) {
	

	/**
	 * Check if a white space span should be removed
	 *
	 * @param {DOMElement} node where the white space span has been inserted
	 * @returns {Boolean}
	 */
	function isObsoleteLandingNode(node) {
		return 'SPAN' === node.nodeName
		    && (node.childNodes.length === 0 || node.innerHTML === '&nbsp;');
	}

	function createLandingElement() {
		var node = document.createElement('span');
		node.appendChild(document.createTextNode('\u00A0'));
		return node;
	}

	function isVisibleNode(node) {
		return (Html.isBlock(node) || Dom.isTextNode(node))
		    && !Html.isUnrenderedNode(node);
	}

	function skipNodeForward(node) {
		return Dom.forward(node.lastChild || node);
	}

	/**
	 * Pads the given block element with landing areas at the beginning and end
	 * of the block. This allows the editor to place the caret next to an inline
	 * block elements.
	 *
	 * @param {jQuery<DOMElement>} $block
	 */
	function pad($block) {
		var previous = Dom.findBackward(
			Dom.backward($block[0]),
			isVisibleNode,
			function(node) {
				return Html.isBlock(node) || DomLegacy.isEditingHost(node);
			}
		);
		if (!previous) {
			$block.before(createLandingElement());
		}
		var next = Dom.findForward(
			skipNodeForward($block[0]),
			isVisibleNode,
			function (node) {
				return Html.isBlock(node) || DomLegacy.isEditingHost(node) || (
					node.previousSibling
					&&
					DomLegacy.isEditingHost(node.previousSibling)
				);
			}
		);
		if (!next) {
			$block.after(createLandingElement());
		}
	}

	/**
	 * Removes the landing nodes inserted by the pad() function.
	 *
	 * @param {jQuery<DOMElement>} $block
	 */
	function unpad($block) {
		var previous = $block[0].previousSibling;
		var next = $block[0].nextSibling;
		if (previous && isObsoleteLandingNode(previous)) {
			previous.parentNode.removeChild(previous);
		}
		if (next && isObsoleteLandingNode(next)) {
			next.parentNode.removeChild(next);
		}
	}

	/**
	 * Tests whether the given element is contained in an editable for
	 * which the block dragdrop feature is enabled.
	 *
	 * @param {!jQuery} $element
	 *        The element that may or may not be contained in an editable.
	 * @return {Boolean}
	 *        True, unless the given $element is contained in an
	 *        editable for which the dragdrop feature has been disabled.
	 */
	function isDragdropEnabledForElement($element) {
		var $editable = $element.closest('.aloha-editable');
		if ($editable.length) {
			return !!$editable.data('block-dragdrop');
		}
		// no editable specified, let's make drag & drop enabled by default.
		return true;
	}

    /**
     * Get the parent editable of the block $block
     *
     * @param {jQuery Element} $block
     * @returns {jQuery Element}
     */
    function getEditableByBlock($block) {
        return $block.parents('.aloha-editable').filter(':first');
    }

    /**
     * Get table inside the block or null if this block is not for a table
     *
     * @param $block
     * @returns {jQuery Element} jQuery table or null if this block
     * is not for a Table
     */
    function getTableByBlock($block) {
        return isTable($block)? $block.find('table').filter(':first') : null;
    }

    /**
     * Check if a block element is a table.
     *
     * @param {jQuery Element} $blockElement
     * @returns {Boolean} true if it is a table, false otherwise
     */
    function isTable($blockElement) {
        return $blockElement.hasClass('aloha-table-wrapper');
    }

	return {
		pad: pad,
		unpad: unpad,
		isDragdropEnabledForElement: isDragdropEnabledForElement,
        isTable: isTable,
        getEditableByBlock: getEditableByBlock,
        getTableByBlock: getTableByBlock
	};
});

/*global window:true, define:true, document:true */
/* blockmanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/blockmanager',[
	'aloha',
	'jquery',
	'ui/scopes',
	'aloha/observable',
	'aloha/registry',
	'util/class',
	'util/strings',
	'util/maps',
    'block/block-utils',
    'table/table-plugin'
], function (
	Aloha,
	$,
	Scopes,
	Observable,
	Registry,
	Class,
	Strings,
	Maps,
    BlockUitls,
    Table
) {
	

	var jQuery = $;

	var GENTICS = window.GENTICS;

	/**
	 * This is the block manager, which is the central entity for maintaining the lifecycle of blocks.
	 *
	 * @name block.blockmanager
	 * @class Block manager singleton
	 */
	var BlockManager = new (Class.extend(Observable,
	/** @lends block.blockmanager */
	{

		/**
		 * @name block.blockmanager#block-selection-change
		 * @event
		 * @param {Array} selectedBlocks Array of AbstractBlock objects, containing selected blocks. The first element in the array is the innermost block, and the other elements are the parent blocks.
		 */

		/**
		 * @name block.blockmanager#block-delete
		 * @event fired directly before a block is deleted
		 * @param {AbstractBlock} the block to be deleted
		 */

		/**
		 * Default settings for blocks
		 */
		defaults: {
			'aloha-block-type': 'DefaultBlock'
		},

		/**
		 * Registry of block types
		 * @type Registry
		 */
		blockTypes: null,

		/**
		 * Registry of blocks
		 * @type Registry
		 */
		blocks: null,

		/**
		 * Array of currently highlighted blocks
		 * @type Array
		 */
		_highlightedBlocks: null,

		/**
		 * Reference to the currently active block, if any
		 * @type AbstractBlock
		 */
		_activeBlock: null,

		/**
		 * Flag that stores the drag & drop state
		 * @type boolean 
		 */
		_dragdropEnabled: true,

		/**************************
		 * SECTION: Initialization
		 **************************/
		/**
		 * Constructor. called immediately.
		 *
		 * @constructor
		 */
		_constructor: function () {
			Scopes.createScope('Aloha.Block');
			this.blockTypes = new Registry();
			this.blocks = new Registry();
			this._highlightedBlocks = {};
		},

		/**
		 * Register initial event handlers. Called from block-plugin when plugin
		 * is loaded.
		 *
		 * @private
		 */
		registerEventHandlers: function () {
			var that = this;
			this._registerEventHandlersForDeactivatingAlohaBlock();
			this._registerEventHandlersForDeterminingCurrentlyActiveBlock();
			this._registerEventHandlersForBlockDeletion();
			this._registerEventHandlersForCutCopyPaste();
			this._registerEventHandlersForEditableActivated();

			// TODO: not sure if we still need the code below. it is somehow related to caret handling
			Aloha.bind('aloha-selection-changed', function (evt, selection, originalEvent) {
				// the following line is needed to de-select blocks when navigating over them using the mouse cursors.
				// We only want to execute it though, if we are not inside a block, as it would otherwise
				// directly deselect the block we just selected. This is just a hotfix and not the final solution yet.
				if (selection && jQuery(selection.getCommonAncestorContainer()).parents('.aloha-block').length > 0) {
					return;
				}
				that._deactivateHighlightedBlocks();
			});
		},

		/**
		 * Register the event handlers which deactivate Aloha blocks when the
		 * user clicks outside a block.
		 */
		_registerEventHandlersForDeactivatingAlohaBlock: function () {
			var that = this;
			jQuery(document).bind('click', function (event) {
				if (Maps.isEmpty(that._highlightedBlocks)) {
					return;
				}
				if (jQuery(event.target)
				        .closest('.aloha-ui,.aloha-block-do-not-deactivate,.aloha-block')
				        .length > 0) {
					// A ui element has been clicked; ignore this event.
					return;
				}
				that._deactivateHighlightedBlocks();
			});
		},

		/**
		 * Register the event handler which listens to block-selection-change, and
		 * sets _activeBlock accordingly.
		 */
		_registerEventHandlersForDeterminingCurrentlyActiveBlock: function () {
			var that = this;
			this.bind('block-selection-change', function (highlightedBlocks) {
				if (highlightedBlocks.length > 0) {
					that._activeBlock = highlightedBlocks[0];
				} else {
					that._activeBlock = null;
				}
			});
		},

		/**
		 * Implementation of block deletions, both when the block is the only selected element,
		 * and when the block is part of a bigger selection which should be deleted.
		 */
		_registerEventHandlersForBlockDeletion: function () {
			var that = this;

			// This case executes in:
			// - Chrome
			// - Firefox
			// - IE9
			// - IE7+8 for inline blocks and for block-level blocks which are part of a bigger selection
			// it does NOT execute in the following cases:
			// - IE7+8 for block-level blocks which are NOT part of a bigger selection. This case is handled separately below.
			Aloha.bind('aloha-command-will-execute', function (e, data) {
				var commandId = data.commandId;

				// Internet Explorer *magically* sets the range to the "Body" object after deselecting everything. yeah :-D
				var selection = Aloha.getSelection(),
				    rangeCount = selection.getRangeCount(),
				    range = selection.getRangeAt(0),
				    rangeEndContainer = range.endContainer,
				    rangeStartContainer = range.startContainer,

				    onlyBlockSelected = (rangeCount === 0) || // Firefox / Chrome
					       (rangeCount === 1 && rangeEndContainer === rangeStartContainer && rangeEndContainer === jQuery('body')[0]) || // Internet explorer: Inline Elements
					       (rangeCount === 1 && rangeEndContainer === rangeStartContainer && range.startOffset + 1 === range.endOffset); // Internet explorer: Block level elements

				if (that._activeBlock && (commandId === 'delete' || commandId === 'forwarddelete') && onlyBlockSelected) {
					// Deletion when a block is currently selected

					// In this case, the default command shall not be executed.
					data.preventDefault = true;
					that._activeBlock.destroy();
				} else if (!that._activeBlock && (commandId === 'delete' || commandId === 'forwarddelete') && rangeCount === 1 && range.collapsed === false) {
					// Deletion when a block is inside a bigger selection currently
					// In this case, we check if we find an aloha-block. If yes, we delete it right away as the browser does not delete it correctly by default
					var traverseSelectionTree;
					traverseSelectionTree = function (selectionTree) {
						var el;
						for (var i = 0, l = selectionTree.length; i < l; i++) {
							el = selectionTree[i];
							if (el.domobj.nodeType === 1) { // DOM node
								var $el = jQuery(el.domobj);
								if (el.selection === 'full' && $el.is('.aloha-block')) {
									$el.remove();
								} else {
									traverseSelectionTree(el.children);
								}
							}
						}
					};
					traverseSelectionTree(Aloha.Selection.getSelectionTree());
				}
			});

			// - IE7/8 Workaround
			// - deletion of blocks inside block collection
			jQuery(window.document).keydown(function (e) {
				// Ignore events originating from the UI
				if ($(e.target).closest('.aloha-ui').length > 0) {
					return true;
				}

				// If a block is active AND DEL or BACKSPACE key pressed, AND we are not inside a nested editable (FIX for IE7/8)
				if (that._activeBlock && (e.which === 46 || e.which === 8) && that._activeBlock._isInsideNestedEditable === false) {
					// ...and active block is INSIDE editable

					// BROWSER QUIRK WORKAROUND
					// - IE7+IE8 for block-level blocks which are NOT part of a bigger selection.
					if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) <= 8 && that._activeBlock.$element.parents('.aloha-editable,.aloha-block').first().hasClass('aloha-editable')) {
						that._activeBlock.destroy();
						e.preventDefault();
						return false;
					} else if (that._activeBlock.shouldDestroy()) {
						// .. in this case, the block should be destroyed because it
						// is part of a block collection.

						that._activeBlock.destroy();
						e.preventDefault();
						return false;
					}
				}
			});
		},

		/**
		 * Implementation of cut/copy; selecting the currently active block.
		 *
		 * When pasting, the blockcontenthandler is triggered. This takes care of the pasting process.
		 */
		_registerEventHandlersForCutCopyPaste: function () {
			var that = this,
				currentlyCopying = false,
				currentlyCutting = false,
				selectionBeforeCopying = null;

			jQuery(window.document).keydown(function (e) {
				if (Aloha.activeEditable && that._activeBlock) {
					// when both an editable and a block are active, they must
					// be nested into each other.
					// if the editable is nested inside the block, we will not expand the
					// selection onto the whole block, because the user wants to copy/cut content
					// in the editable
					if (jQuery.contains(that._activeBlock.$element.get(0), Aloha.activeEditable.obj.get(0))) {
						return;
					}
				}

				// IF: Ctrl/Command C pressed -- COPY
				if (that._activeBlock && (e.ctrlKey || e.metaKey) && e.which === 67) {
					currentlyCopying = true;
					//selectionBeforeCopying = new GENTICS.Utils.RangeObject(true);
					that._activeBlock.$element.attr('data-aloha-block-copy-only-block', 'true');
					GENTICS.Utils.Dom.selectDomNode(that._activeBlock.$element[0]);
				}

				// IF: Ctrl/Command X pressed -- CUT
				if (that._activeBlock && (e.ctrlKey || e.metaKey) && e.which === 88) {
					currentlyCutting = true;
					//selectionBeforeCopying = new GENTICS.Utils.RangeObject(true);
					that._activeBlock.$element.attr('data-aloha-block-copy-only-block', 'true');
					GENTICS.Utils.Dom.selectDomNode(that._activeBlock.$element[0]);
				}
			});
			jQuery(window.document).keyup(function (e) {
				// IF: Release of ctrl / command C
				if (!currentlyCutting && currentlyCopying && (e.which === 67 || e.which === 18 || e.which === 91)) {
					currentlyCopying = false;
					that._activeBlock.$element.removeAttr('data-aloha-block-copy-only-block');
					if (selectionBeforeCopying) {
						//selectionBeforeCopying.select();
						selectionBeforeCopying = null;
					}
				}
				// IF: Release of ctrl / command X
				if (currentlyCutting  && (e.which === 67 || e.which === 18 || e.which === 88)) {
					currentlyCutting = false;
				}
			});
		},

		/**
		 * When editables are activated (e.g. by moving the focus with Tab or Shift-Tab or programmatically),
		 * we need to activate the enclosing blocks. 
		 */
		_registerEventHandlersForEditableActivated: function () {
			var that = this;
			Aloha.bind('aloha-editable-activated', function (event, arg) {
				if (arg.editable) {
					var block = that.getBlock(arg.editable.obj.closest('.aloha-block'));
					if (block) {
						block.activate();
					}
				}
			});
		},

		/**
		 * Initialize Block Level Drag/Drop for all editables. We need to do this
		 * inside the Block Manager, as we want all editables to become possible
		 * drop targets for block-level aloha blocks.
		 */
		initializeBlockLevelDragDrop: function () {
			var that = this;
			jQuery.each(Aloha.editables, function (i, editable) {
				editable.obj.data("block-dragdrop", that._dragdropEnabled);
				that.createBlockLevelSortableForEditableOrBlockCollection(editable.obj);
			});
			Aloha.bind('aloha-editable-created', function (e, editable) {
				editable.obj.data("block-dragdrop", that._dragdropEnabled);
				that.createBlockLevelSortableForEditableOrBlockCollection(editable.obj);
			});
		},

		/**
		 * We make editables or block collections sortable using jQuery UI here, if we
		 * did not do this before.
		 *
		 * This is an internal method a user should never call!
		 */
		createBlockLevelSortableForEditableOrBlockCollection: function ($editableOrBlockCollection) {
			var that = this;

			if (!$editableOrBlockCollection.hasClass('aloha-block-blocklevel-sortable')) {

				// We only want to make "block-level" aloha blocks sortable. According to the docs,
				// sortable.cancel should have a CSS selector and if this matches, the element is only
				// a drop target but NOT draggable. However, passing :not(.aloha-block) does not work somehow :-(
				//
				// Thus, we implemented the following alternative:
				// Every "block-level" aloha block drag handle gets a new CSS class, and we only select this as
				// drag handle. As only "block-level" aloha blocks have this CSS class, this will also only make
				// aloha blocks draggable.
				$editableOrBlockCollection.addClass("aloha-block-blocklevel-sortable").sortable({
					revert: 100,
					handle: ".aloha-block-draghandle-blocklevel",
					connectWith: ".aloha-block-blocklevel-sortable.aloha-block-dropzone", // we want to be able to drag an element to other editables
					disabled: !that._dragdropEnabled, // if drag & drop is disabled, sortable should also be disabled
					start: function (event, ui) {
						// check if the block's parent is a dropzone
						ui.item.data("block-sort-allowed", (ui.item.parents(".aloha-block-dropzone").length > 0));
					},
					change: function (event, ui) {
						ui.item.data("block-sort-allowed", (ui.placeholder.parents(".aloha-block-dropzone").length > 0));
					},
					stop: function (event, ui) {
						var $blockItem = ui.item;
						if (!$blockItem.data("block-sort-allowed")) {
							jQuery(this).sortable("cancel");
						}
						$blockItem.removeData("block-sort-allowed");

						var $table = BlockUitls.getTableByBlock($blockItem);
						if ($table !== null) {
							var actualParentEditable = Aloha.getEditableById(
								BlockUitls.getEditableByBlock($blockItem)
								          .attr('id'));
							Table.getTableFromRegistry($table.get(0))
							     .parentEditable = actualParentEditable;
						}
					}
				});

				// Hack for Internet Explorer 8:
				// If you first click inside an editable, and THEN want to drag a block-level block,
				// it sometimes occurs that the *whole editable* is selected and should be dragged away.
				// This breaks dragging of Aloha Blocks.
				// Bugfix: We disable the "ondragstart" event on every editable.
				// However, as the "ondragstart" is also fired when a nested (inline) editable is moved using drag/drop,
				// we need to allow this case.
				$editableOrBlockCollection.get(0).ondragstart = function (e, ui) {
					if (!ui || !ui.helper || !ui.helper.is('.aloha-block')) {
						// You tried to move something else than an aloha block
						return false;
					}
				};
			}
		},

		/**
		 * Turn the dragdrop feature globally on or off.
		 *
		 * Will only affect editables created after this call is made.
		 *
		 * @param {boolean} state
		 */
		setDragDropState: function (state) {
			this._dragdropEnabled = state;
		},

		/**
		 * Test whether the dragdrop feature is globally enabled.
		 *
		 * @return {boolean}
		 */
		getDragDropState: function () {
			return this._dragdropEnabled;
		},

		/**************************
		 * SECTION: Blockify / Block Access
		 **************************/

		/**
		 * Register the given block type
		 *
		 * @param {String} Identifier
		 * @param {Class} A class that extends block.block.AbstractBlock
		 * @api
		 */
		registerBlockType: function (identifier, blockType) {
			Scopes.createScope('Aloha.Block.' + identifier, 'Aloha.Block');
			this.blockTypes.register(identifier, blockType);
		},

		/**
		 * Blockify a given element with the instance defaults
		 * Directly called when one does jQuery.alohaBlock(instanceDefaults)
		 *
		 * @private
		 */
		_blockify: function (element, instanceDefaults) {
			var that = this,
				$element = jQuery(element),
				BlockPlugin = Aloha.require('block/block-plugin'),
				tagName = $element[0].tagName.toLowerCase(),
				attributes,
				block;

			if (jQuery.inArray(tagName, BlockPlugin.settings.rootTags) === -1) {
				Aloha.Log.error('block/blockmanager', 'Blocks can only be created from [' +
					BlockPlugin.settings.rootTags.join(', ') +
					'] element. You passed ' + tagName + '.');
				return;
			}

			// TODO: check if object is already Block-ified

			attributes = this.getConfig($element, instanceDefaults);

			if (!this.blockTypes.has(attributes['aloha-block-type'])) {
				Aloha.Log.error('block/blockmanager', 'Block Type ' + attributes['aloha-block-type'] + ' not found!');
				return;
			}

			block = new (this.blockTypes.get(attributes['aloha-block-type']))($element, attributes);
			block.$element.addClass('aloha-block-' + attributes['aloha-block-type']);
//			jQuery.each(attributes, function (k, v) {
//				// We use the private API here, as we need to be able to set internal properties as well, and we do not want to trigger renering.
//				block._setAttribute(k, v);
//			});

			// Register block
			this.blocks.register(block.getId(), block);
		},

		/**
		 * Unblockify the given element
		 * 
		 * @private
		 */
		_unblockify: function (element) {
			var block = this.getBlock(element);
			if (block) {
				block.unblock();
			}
		},

		/**
		 * Merges the config from different places, and return the merged config.
		 *
		 * @param {Object} jQuery Object
		 * @param {Object} default configuration
		 *
		 * @private
		 */
		getConfig: function (blockElement, instanceDefaults) {
			var
				data          = {},
				dataCamelCase = null,
				$clone        = null;

			if (blockElement.length > 0) {
				// Clone the element before getting the data to fix an IE7 crash.
				// We use the native cloneNode function, because jQuerys clone()
				// executes scripts also, what we don't want.
				// But since that one doesn't copy the the abritary jQuery data, we
				// manually merge both datas together into a new object.
				$clone = $(blockElement[0].cloneNode(false));
				// Merge the "data-" attributes from the clone and take the
				// arbitrary data from the original jQuery object
				dataCamelCase = $.extend({}, $.data(blockElement[0]), $clone.data());
				$clone.removeData();

				// jQuery.data() returns data attributes with names like
				// data-some-attr as dataSomeAttr which has to be reversed
				// so that they can be merged with this.defaults and
				// instanceDefaults which are expected to be in
				// data-some-attr form.
				for (var key in dataCamelCase) {
					if (dataCamelCase.hasOwnProperty(key)) {
						data[Strings.camelCaseToDashes(key)] = dataCamelCase[key];
					}
				}
			}

			return jQuery.extend(
				{},
				this.defaults,
				instanceDefaults,
				data
			);
		},

		/**
		 * Get a Block instance by id or DOM node. The DOM node can be either
		 * the DOM node of the wrapping element ($_element), the jQuery object of it,
		 * or the ID string.
		 *
		 * @param {String|DOMNode} idOrDomNode
		 * @return {block.block.AbstractBlock} Block instance
		 * @api
		 */
		getBlock: function (idOrDomNode) {
			var id, domNode;
			if (typeof idOrDomNode === 'object') {
				domNode = jQuery(idOrDomNode);
				if (domNode.hasClass('aloha-block-inner')) {
					// We are at the inner block wrapper, so we have to go up one level,
					// to find the block itself
					domNode = domNode.parent();
				}
				id = domNode.attr('id');
			} else {
				id = idOrDomNode;
			}

			return this.blocks.get(id);
		},

		/**
		 * Unregister (e.g. remove) the given block. Do not call directly,
		 * instead use .destroy() on the block.
		 *
		 * @param {Object|String} blockOrBlockId Block or block id
		 */
		_unregisterBlock: function (blockOrBlockId) {
			var id;
			if (typeof blockOrBlockId === 'object') {
				id = blockOrBlockId.getId();
			} else {
				id = blockOrBlockId;
			}
			this.blocks.unregister(id);
		},


		/**************************
		 * Internal helpers
		 **************************/

		/**
		 * Deactivate all highlighted blocks
		 *
		 * @private
		 */
		_deactivateHighlightedBlocks: function () {
			jQuery.each(jQuery.extend({}, this._highlightedBlocks), function (id) {
				var block = BlockManager.getBlock(id);
				if (block) {
					block.deactivate();
				}
			});
		},

		/**
		 * Get all highlighted blocks indexed by block id
		 *
		 * @return {Object}
		 */
		_getHighlightedBlocks: function () {
			var _highlightedBlocks = {};
			jQuery.each(this.blocks.getEntries(), function (blockId, block) {
				if (block.isActive()) {
					_highlightedBlocks[blockId] = block;
				}
			});
			return _highlightedBlocks;
		},

		_setHighlighted: function (block) {
			this._highlightedBlocks[block.id] = true;
		},

		_setUnhighlighted: function (block) {
			delete this._highlightedBlocks[block.id];
		}
	}))();

	Aloha.Block = Aloha.Block || {};
	Aloha.Block.BlockManager = BlockManager;

	return BlockManager;
});

/* editormanager.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/editormanager',
['aloha/registry'],
function(Registry) {
	

	/**
	 * The Editor Manager maps data types to editor classes.
	 *
	 * @name block.editormanager
	 * @class Editor manager singleton
	 */
	return new (Registry.extend(
	/** @lends block.editormanager */
	{
		/**
		 * Create an editor from the given definition. Acts as a factory method
		 * for editors.
		 *
		 * @param {Object} definition
		 */
		createEditor: function(definition) {
			if (!this.has(definition.type)) {
				throw 'Editor for type "' + definition.type + '" not found.';
			}
			var Editor = this.get(definition.type);
			return new Editor(definition);
		}
	}))();
});
/* sidebarattributeeditor.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/sidebarattributeeditor',[ 'jquery', 'block/blockmanager', 'aloha/sidebar', 'block/editormanager', 'util/class'],
	function (jQuery, BlockManager, Sidebar, EditorManager, Class) {
	

	/**
	 * The Sidebar Attribute Editor is the connector which listens on Aloha Blocks and
	 * updates the sidebar accordingly. It builds the editors for the sidebar, initializes
	 * them with initial values and updates the block attributes as soon as a value changes.
	 *
	 * @name block.sidebarattributeeditor
	 * @class Sidebar attribute editor singleton
	 */
	return new (Class.extend(
	/** @lends block.sidebarattributeeditor */
	{

		_sidebar: null,

		/**
		 * Initialize the sidebar attribute editor and bind events
		 */
		init: function() {
			this._sidebar = Sidebar.right.show();

			BlockManager.bind('block-selection-change', this._onBlockSelectionChange, this);
		},

		/**
		 * @param {Array} selectedBlocks
		 */
		_onBlockSelectionChange: function(selectedBlocks) {
			var that = this;
			if (!this._sidebar) {
				return;
			}
			// TODO: Clearing the whole sidebar might not be what we want; instead we might only want
			// to clear certain panels.
			// that._sidebar.container.find('.aloha-sidebar-panels').children().remove();
			// that._sidebar.panels = {};

			jQuery.each(selectedBlocks, function() {
				var schema = this.getSchema(),
					block = this,
					editors = [];

				if (!schema) {
					// If no schema returned, we do not want to add panels.
					return;
				}

				that._sidebar.addPanel({
					title: block.getTitle(),
					expanded: true,
					onInit: function() {
						var $form = jQuery('<form />');
						$form.submit(function() {
							// Disable form submission
							return false;
						});
						jQuery.each(schema, function(attributeName, definition) {
							var editor = EditorManager.createEditor(definition);

							// Editor -> Block binding
							editor.bind('change', function(value) {
								block.attr(attributeName, value);
							});

							// Block -> Editor binding
							block.bind('change', function() {
								editor.setValue(block.attr(attributeName));
							})

							$form.append(editor.render());

							// Set initial value Block -> Editor
							editor.setValue(block.attr(attributeName));

							editors.push(editor);
						});
						this.setContent($form);
					},

					deactivate: function() {
						// On deactivating the panel, we need to tell each editor to deactivate itself,
						// so it can throw another change event if the value has been modified.
						jQuery.each(editors, function(index, editor) {
							editor._deactivate();
						});

						// This code is from the superclass
						this.isActive = false;
						// TODO check if this is needed in current block implementation
						// this.content.parent('li').hide();
						this.effectiveElement = null;
					}
				});
			});
		}
	}))();
});

/* block.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Module which contains the base class for Blocks, and a Default/Debug block.
 *
 * @name block.block
 * @namespace block/block
 */
define('block/block',[
	'aloha',
	'jquery',
	'block/blockmanager',
	'aloha/observable',
	'ui/scopes',
	'util/class',
	'PubSub',
	'block/block-utils',
	'util/html'
], function(
	Aloha,
	jQuery,
	BlockManager,
	Observable,
	Scopes,
	Class,
	PubSub,
	BlockUtils,
    Html
){
	

	var GENTICS = window.GENTICS;

	/**
	 * An aloha block has the following special properties, being readable through the
	 * "attr" function:
	 * - aloha-block-type -- TYPE of the AlohaBlock as registered by the BlockManager
	 *
	 * @name block.block.AbstractBlock
	 * @class An abstract block that must be used as a base class for custom blocks
	 */
	var AbstractBlock = Class.extend(Observable,
	/** @lends block.block.AbstractBlock */
	{

		/**
		 * Event which is triggered if the block attributes change.
		 *
		 * @name block.block.AbstractBlock#change
		 * @event
		 */

		/**
		 * Title of the block, used to display the name in the sidebar editor.
		 *
		 * @type String
		 * @api
		 */
		title: null,

		/**
		 * Id of the underlying $element, used to identify the block.
		 * @type String
		 */
		id: null,

		/**
		 * The wrapping element of the block.
		 * @type jQuery
		 * @api
		 */
		$element: null,

		/**
		 * if TRUE, the rendering is currently taking place. Used to prevent recursion
		 * errors.
		 * @type Boolean
		 */
		_currentlyRendering: false,

		/**
		 * set to TRUE once the block is fully initialized.
		 *
		 * @type Boolean
		 */
		_initialized: false,

		/**
		 * Set to TRUE if the last click activated a *nested editable*.
		 * If FALSE; the block itself is selected.
		 * This is needed when a block is deleted in IE7/8.
		 */
		_isInsideNestedEditable: false,

		/**************************
		 * SECTION: Initialization and Lifecycle
		 **************************/

		/**
		 * Initialize the basic block. Do not call directly; instead use jQuery(...).alohaBlock() to
		 * create new blocks.
		 *
		 * This function shall only be called through the BlockManager. See BlockManager::_blockify().
		 *
		 * When a block is fully initialized, an "aloha.block.initialized"
		 * message is published.
		 *
		 * @param {jQuery} $element Element that declares the block
		 * @param {Object} attributes that shall be set to the block
		 * @constructor
		 */
		_constructor: function ($element, attributes) {
			var that = this;

			this.$element = $element;

			if ($element.attr('id')) {
				this.id = $element.attr('id');
			} else {
				this.id = GENTICS.Utils.guid();
				$element.attr('id', this.id);
			}

			$element.contentEditable(false);

			$element.addClass('aloha-block');

			if (this.isDraggable()) {
				// Remove default drag/drop behavior of the browser
				$element.find('img').attr('draggable', 'false');

				try {
					$element.find('a').attr('draggable', 'false');
				} catch(e) {
					// If we get in here, it is most likely an issue with IE 10 in documentmode 7
					// and IE10 compatibility mode. It maybe happens in older versions too.
					// Error: Member not found
					// https://connect.microsoft.com/IE/feedback/details/774078
					// http://bugs.jquery.com/ticket/12577
					// Our fallback solution:
					$element.find('a').each(function() {
						this.setAttribute('draggable', 'false');
					});
				}
			}

			// set the attributes
			jQuery.each(attributes, function (k, v) {
				that._setAttribute(k, v);
			});

			// While the event handler is defined here, it is connected to the DOM element inside "_connectThisBlockToDomElement"
			this._onElementClickHandler = function (event) {
				// We only activate ourselves if we are the innermost aloha-block.
				// If we are not the innermost aloha-block, we get highlighted (but not activated) automatically
				// by the innermost block.
				if (jQuery(event.target).closest('.aloha-block').get(0) === that.$element.get(0)) {
					that._fixScrollPositionBugsInIE();
					that.activate(event.target, event);
				}
			};

			// Register event handlers on the block
			this._connectThisBlockToDomElement($element, function () {
				PubSub.pub('aloha.block.initialized', {block: that});
			});

			// This is executed when a block is selected through caret handling
			// TODO!
			//Aloha.bind('aloha-block-selected', function(event,obj) {
			//	if (that.$element.get(0) === obj) {
			//		that.activate();
			//	}
			//});

			// Only for inline element.
			// It is not possible to insert text after or before a Block span
			// when after or before the Block there is not elements
			if (Html.isInlineFormattable($element[0])) {
				if ($element.closest('.aloha-editable-active').length > 0) {
					BlockUtils.pad(that.$element);
				}

				Aloha.bind('aloha-editable-activated', function ($event, data) {
					if (data.editable) {
						var $block = data.editable.obj.find('#' + that.id);
						if ($block.length !== 0) {
							BlockUtils.pad(that.$element);
						}
					}
				});

				Aloha.bind('aloha-editable-deactivated', function ($event, data) {
					if (data.editable) {
						var $block = data.editable.obj.find('#' + that.id);
						if ($block.length !== 0) {
							BlockUtils.unpad(that.$element);
						}
					}
				});
			}

			this._initialized = true;
		},

		/**
		 * Is set inside the constructor to the event handler function
		 * which should be executed when the element is clicked.
		 *
		 * NOTE: Purely internal, "this" is not available inside this method!
		 */
		_onElementClickHandler: null,

		/**
		 * We need to tell Aloha that we handle the event already;
		 * else a selection of a nested editable will *not* select
		 * the block.
		 *
		 * This callback is bound to the mousedown, focus and dblclick events.
		 *
		 * NOTE: Purely internal, "this" is not available inside this method!
		 */
		_preventSelectionChangedEventHandler: function ($event) {
			if (('dblclick' !== $event.type)
					&& !jQuery($event.target).is('.aloha-editable')) {
				Aloha.Selection.preventSelectionChanged();
			}
		},

		/**
		 * This method connects this block object to the passed DOM element.
		 * In detail, this method does the following:
		 *
		 * - if this.$element is already set, remove all block event handlers
		 * - sets this.$element = jQuery(newElement)
		 * - initialize event listeners on this.$element
		 * - call init()
		 *
		 * The method is called in two contexts: First, when a block is constructed
		 * to initialize the event listeners etc. Second, it is ALSO called when
		 * a block inside a nested block with editable in between is detected
		 * as inconsistent.
		 */
		_connectThisBlockToDomElement: function(newElement, callback) {
			var that = this;
			var $newElement = jQuery(newElement);
			this._disconnectFromDomElement();
			this.$element = $newElement;

			this.$element.bind('click', this._onElementClickHandler);
			this.$element.bind('mousedown', this._preventSelectionChangedEventHandler);
			this.$element.bind('focus', this._preventSelectionChangedEventHandler);
			this.$element.bind('dblclick', this._preventSelectionChangedEventHandler);

			this.init(this.$element, function() {
				// WORKAROUND against loading order dependencies. If we have
				// nested Blocks inside each other (with no editables in between)
				// it could be that the *inner* block is initialized *before* the outer one.
				//
				// However, the inner block needs to know whether it shall render drag handles or not,
				// and this depends on whether it is inside an editable or a block.
				//
				// In order to fix this case, we delay the the drag-handle-rendering (and all the other
				// post-processing) to the next JavaScript Run Loop using a small timeout.
				window.setTimeout(function() {
					that._postProcessElementIfNeeded();
					if (callback) {
						callback();
					}
				}, 5);
			});
		},

		/**
		 * Disconnect the block from the DOM element
		 */
		_disconnectFromDomElement: function() {
			if (this.$element) {
				this.$element.unbind('click', this._onElementClickHandler);
				this.$element.unbind('mousedown', this._preventSelectionChangedEventHandler);
				this.$element.unbind('focus', this._preventSelectionChangedEventHandler);
				this.$element.unbind('dblclick', this._preventSelectionChangedEventHandler);
			}
		},

		/**
		 * IE HACK: Our beloved Internet Explorer sometimes scrolls to the top
		 * of the page when activating an aloha block, and on numerous other occasions
		 * like when an <span> block is moved via drag/drop.
		 *
		 * We can detect this and scroll right back; although this will flicker
		 * a little (but still a lot better than before)
		 */
		_fixScrollPositionBugsInIE: function() {
			var scrollPositionBefore = jQuery(window).scrollTop();
			window.setTimeout(function() {
				if (jQuery(window).scrollTop() !== scrollPositionBefore) {
					jQuery(window).scrollTop(scrollPositionBefore);
				}
			}, 10);
		},
		/**
		 * Template method to initialize the block. Can be used to set attributes
		 * on the block, depending on the block contents. You will most probably
		 * use $element and this.attr() inside this function.
		 *
		 * !!! This method can be called *multiple times*, as it is called each time
		 * when $element has been disconnected from the DOM (which can happen because of various reasons)
		 * and the block needs to re-initialize. So make sure this method can be called *MULTIPLE TIMES*
		 * and always returns predictable results. This method must be idempotent, same as update().
		 *
		 * Furthermore, always when this method is finished, you need to call postProcessFn() afterwards.
		 * This function adds drag handles and other controls if necessary.
		 *
		 * @param {jQuery} $element a shortcut to the block's DOM element (this.$element) for easy processing
		 * @param {Function} postProcessFn this function MUST be called at all times the $element has been updated; as it adds drag/drop/delete/... handles if necessary
		 * @api
		 */
		init: function($element, postProcessFn) {
			postProcessFn();
		},

		/**
		 * Callback which is executed when somebody triggers destroy().
		 *
		 * This only allows destruction if the block is *inside* an aloha-editable and *not* inside an aloha-block.
		 *
		 * @return {Boolean} true of destruction should happen, false otherwise
		 */
		shouldDestroy: function() {
			var $closest = this.$element.parent().closest('.aloha-block,.aloha-editable,.aloha-block-collection');
			if ($closest.hasClass('aloha-block-collection') && this.$element[0].tagName.toLowerCase() === 'div') {
				return true;
			} else {
				return $closest.hasClass('aloha-editable');
			}
		},

		/**
		 * Destroy this block instance completely. Removes the element from the DOM,
		 * unregisters it, and triggers a block-delete event on the BlockManager.
		 *
		 * @param {Boolean} force TRUE if you want to force deletion, despite shouldDestroy() returning false.
		 * @api
		 */
		destroy: function(force) {
			if (!this.shouldDestroy() && force !== true) return;

			var that = this;
			var newRange = new GENTICS.Utils.RangeObject();

			newRange.startContainer = newRange.endContainer = this.$element.parent()[0];
			newRange.startOffset = newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(this.$element[0]);

			BlockManager.trigger('block-delete', this);
			this.free();

			var isInlineElement = this.$element[0].tagName.toLowerCase() === 'span';

			this.$element.fadeOut('fast', function() {
				that.$element.remove();
				BlockManager.trigger('block-selection-change', []);
				window.setTimeout(function() {
					if (isInlineElement) {
						newRange.select();
					}
				}, 5);
			});
		},

		/**
		 * Remove this block, but leave the original DOM element
		 */
		unblock: function () {
			// TODO set old value of contentEditable
			// TODO set old values for draggable attributes

			// deactivate
			this.deactivate();
			// remove handlers
			this._disconnectFromDomElement();
			// remove block class
			this.$element.removeClass('aloha-block');
			// remove block handles
			this.$element.children('.aloha-block-handle').remove();
			// unregister the block
			this.free();
		},

		/**
		 * Free internal state associated with this block.
		 *
		 * Should be called when a block is not used any more to prevent
		 * memory leaks.
		 *
		 * Any invokations of instance methods after this method has
		 * been called will result in undefined behaviour.
		 *
		 * @api
		 */
		free: function () {
			BlockManager._unregisterBlock(this);
			this.unbindAll();
		},

		/**************************
		 * SECTION: Getters and Helpers
		 **************************/

		/**
		 * Get the id of the block
		 * @returns {String}
		 */
		getId: function() {
			return this.id;
		},

		/**
		 * Get a schema of attributes which shall be rendered / edited
		 * in the sidebar.
		 *
		 * @api
		 * @returns {Object}
		 */
		getSchema: function() {
			return null;
		},

		/**
		 * Template Method which should return the block title. Needed for editing sidebar.
		 * By default, the block title is returned.
		 *
		 * @api
		 */
		getTitle: function() {
			return this.title;
		},

		/**
		 * Returns true if the block is draggable because it is inside an aloha-editable, false otherwise.
		 *
		 * You cannot depend on this method's result during the *init* phase of the Aloha Block, as the
		 * outer block might not be initialized at that point yet. Thus, do not call this method inside init().
		 *
		 * @return Boolean
		 */
		isDraggable: function() {
			if (this.$element[0].tagName.toLowerCase() === 'div' && this.$element.parents('.aloha-editable,.aloha-block,.aloha-block-collection').first().hasClass('aloha-block-collection')) {
				// Here, we are inside an aloha-block-collection, and thus also need to be draggable.
				return true;
			}
			return this.$element.parents('.aloha-editable,.aloha-block').first().hasClass('aloha-editable');
		},

		/**************************
		 * SECTION: Activation / Deactivation
		 **************************/

		/**
		 * activates the block
		 * will select the block's contents, highlight it, update the floating menu and update the sidebar (if needed).
		 *
		 * When calling programmatically, do not set eventTarget or event arguments.
		 * @api
		 */
		activate: function(eventTarget, event) {
			var highlightedBlocks = [];

			// Deactivate currently highlighted blocks
			jQuery.each(BlockManager._getHighlightedBlocks(), function() {
				this.deactivate();
			});

			// Activate current block
			if (this.$element.attr('data-block-skip-scope') !== 'true') {
				Scopes.setScope('Aloha.Block.' + this.attr('aloha-block-type'));
			}
			this.$element.addClass('aloha-block-active');
			this._highlight();
			highlightedBlocks.push(this);

			// Highlight parent blocks
			this.$element.parents('.aloha-block').each(function() {
				var block = BlockManager.getBlock(this);
				if (block) {
					block._highlight();
					highlightedBlocks.push(block);
				}
			});

			// Browsers do not remove the cursor, so we enforce it when an aditable is clicked.
			// However, when the user clicked inside a nested editable, we will not remove the cursor (as the user wants to start typing then)
			// small HACK: we also do not deactivate if we are inside an aloha-table-cell-editable.
			if (jQuery(eventTarget).closest('.aloha-editable,.aloha-block,.aloha-table-cell-editable,.aloha-table-cell_active').first().hasClass('aloha-block')) {
				this._isInsideNestedEditable = false;
				Aloha.getSelection().removeAllRanges();
			} else {
				this._isInsideNestedEditable = true;
				if (event) {
					// We now update the selection, as you clicked *inside* an editable inside the block
					Aloha.Selection.updateSelection(event);
				}
			}
			// Trigger block activate & selection change events.
			BlockManager.trigger('block-activate', highlightedBlocks);
			BlockManager.trigger('block-selection-change', highlightedBlocks);
		},

		/**
		 * Deactive the block
		 */
		deactivate: function() {
			var that = this;
			var deactivatedBlocks = [this];
			this._unhighlight();
			this.$element.parents('.aloha-block').each(function() {
				deactivatedBlocks.push(this);
				that._unhighlight();
			});

			this.$element.removeClass('aloha-block-active');

			// Trigger block deactivate & selection change events.
			BlockManager.trigger('block-deactivate', deactivatedBlocks);
			BlockManager.trigger('block-selection-change', []);
		},

		/**
		 * @returns {Boolean} True if this block is active
		 */
		isActive: function() {
			return this.$element.hasClass('aloha-block-active');
		},

		/**
		 * Internal helper which sets a block as highlighted, because the block itself
		 * or a child block has been activated.
		 */
		_highlight: function() {
			this.$element.addClass('aloha-block-highlighted');
			BlockManager._setHighlighted(this);
		},

		/**
		 * Internal helper which sets a block as un-highlighted.
		 */
		_unhighlight: function() {
			this.$element.removeClass('aloha-block-highlighted');
			BlockManager._setUnhighlighted(this);
		},

		/**************************
		 * SECTION: Block Rendering
		 **************************/

		/**
		 * Internal _update method, which needs to be called internally if a property
		 * changed. This is just a wrapper around update().
		 */
		_update: function() {
			var that = this;
			if (this._currentlyRendering) return;
			if (!this._initialized) return;

			this._currentlyRendering = true;

			this.update(this.$element, function() {
				that._postProcessElementIfNeeded();
			});

			this._currentlyRendering = false;
		},

		/**
		 * Template method to render contents of the block, must be implemented by specific block type.
		 * $element can be augumented by additional DOM elements like drag/drop handles. If you do
		 * any jQuery selection, you need to ignore all results which have a "aloha-block-handle" class
		 * set.
		 *
		 * Furthermore, always when you update $element, you need to call postProcessFn() afterwards.
		 * This function adds drag handles and other controls if necessary.
		 *
		 * This method should *only* be called from the internal _update method.
		 *
		 * @param {jQuery} $element a shortcut to the block's DOM element (this.$element) for easy processing
		 * @param {Function} postProcessFn this function MUST be called at all times the $element has been updated; as it adds drag/drop/delete/... handles if necessary
		 *
		 * @api
		 */
		update: function($element, postProcessFn) {
			postProcessFn();
		},


		/**
		 * Post processor, being called to augument the Block Element's DOM by drag handles etc.
		 *
		 * This method must be idempotent. I.e. it must produce the same results
		 * when called once or twice.
		 */
		_postProcessElementIfNeeded: function() {
			this.createEditablesIfNeeded();
			this._checkThatNestedBlocksAreStillConsistent();
			this._makeNestedBlockCollectionsSortable();

			this.renderBlockHandlesIfNeeded();
			if (this.isDraggable() && this.$element[0].tagName.toLowerCase() === 'span') {
				this._setupDragDropForInlineElements();
				this._disableUglyInternetExplorerDragHandles();
			} else if (this.isDraggable() && this.$element[0].tagName.toLowerCase() === 'div') {
				this._setupDragDropForBlockElements();
				this._disableUglyInternetExplorerDragHandles();
			}
			this._hideDragHandlesIfDragDropDisabled();
			this._attachDropzoneHighlightEvents();
		},

		/**
		 * Due to indeterminate initialization order of nested blocks,
		 * it can happen that blockifying a parent block deconnects $element inside
		 * child blocks.
		 *
		 * This is the case we detect here; and if it happens, we reconnect the
		 * block to its currently visible DOM element.
		 */
		_checkThatNestedBlocksAreStillConsistent: function() {
			this.$element.find('.aloha-block').each(function() {
				var block = BlockManager.getBlock(this);
				if (block && block.$element[0] !== this) {
					block._connectThisBlockToDomElement(this);
				}
			});
		},

		/**
		 * If a nested element is marked as "aloha-block-collection",
		 * we want to make it sortable, by calling the appropriate Block Manager
		 * function.
		 */
		_makeNestedBlockCollectionsSortable: function() {
			var that = this;
			this.$element.find('.aloha-block-collection').each(function() {
				var $blockCollection = jQuery(this);
				if ($blockCollection.closest('.aloha-block').get(0) === that.$element.get(0)) {
					// We are only responsible for one-level-down Block Collections, not
					// for nested ones.
					BlockManager.createBlockLevelSortableForEditableOrBlockCollection($blockCollection);
				}
			})
		},

		/**
		 * Helper which disables the ugly IE drag handles. They are still shown, but at
		 * least they do not work anymore
		 */
		_disableUglyInternetExplorerDragHandles: function() {
			if (jQuery.browser.msie) {
				this.$element.get( 0 ).onresizestart = function ( e ) { return false; };
				this.$element.get( 0 ).oncontrolselect = function ( e ) { return false; };
				// We do NOT abort the "ondragstart" event as it is required for drag/drop.
				this.$element.get( 0 ).onmovestart = function ( e ) { return false; };
				// We do NOT abort the "onselectstart" event because this would disable selection in nested editables
			}
		},

        /**
         * Removes the draghandle class from block handle,
         * if drag & drop is disabled for the editable
         */
        _hideDragHandlesIfDragDropDisabled: function() {
			if ( !this._dd_isDragdropEnabled() ){
				this.$element.find('.aloha-block-draghandle').each(function () {
					var $draghandle = jQuery(this);
					if (!BlockUtils.isDragdropEnabledForElement($draghandle)) {
						$draghandle.removeClass('aloha-block-draghandle');
					}
				});
			} 
        },

        /**
         * Attach mousedown/up events to block's draghandle 
         * to toggle dropzones when dragging starts and ends.
         */
        _attachDropzoneHighlightEvents: function() {
            var that = this;

            this.$element.delegate( ".aloha-block-draghandle", "mousedown", function() {
                var dropzones = that.$element.parents( ".aloha-editable" ).first().data( "block-dropzones" ) || [];
                jQuery.each( dropzones, function(i, editable_selector) {
                    var editables = jQuery( editable_selector );
                    jQuery( editables ).each(function() {
                        if (jQuery( this ).data( "block-dragdrop" )) {
                            jQuery( this ).addClass( "aloha-block-dropzone" );      
                        }
                    });
                });

                // Remove the dropzones as soon as the mouse is released,
                // irrespective of where the drop took place.
                jQuery( document ).one( "mouseup.aloha-block-dropzone", function(e) {
                    var dropzones = that.$element.parents( ".aloha-editable" ).first().data( "block-dropzones" ) || [];
                    jQuery.each( dropzones, function(i, editable_selector) {
                        jQuery( editable_selector ).removeClass( "aloha-block-dropzone" );      
                    });
                });
            });
        },

		/**************************
		 * SECTION: Drag&Drop for INLINE elements
		 **************************/
		_setupDragDropForInlineElements: function() {
			var that = this;

			// Here, we store the character DOM element which has been hovered upon recently.
			// This is needed as somehow, the "drop" event on the character is not fired.
			// Furthermore, we use it to know whether we need to "revert" the draggable to the original state or not.
			var lastHoveredCharacter = null;

            // Unless this flag is set to true, drag operation should be reverted.
            // Firing of "drop" event will set this to true.
            var blockDroppedProperly = false;

			// HACK for IE7: Internet Explorer 7 has a very weird behavior in
			// not always firing the "drop" callback of the inner droppable... However,
			// the "over" and "out" callbacks are fired correctly.
			// Because of this, we handle the "drop" inside the "stop" callback in IE7
			// instead of the "drop" callback (where it is handled in all other browsers)

			// This $currentDraggable is also needed as part of the IE 7 hack.
			// $currentDraggable contains a reference to the current draggable, but
			// only makes sense to read when lastHoveredCharacter !== NULL.
			var $currentDraggable = null;

            // We need to store the droppables created at the start of the drag,
            // they should be destroyed when the drag stops.
            var $createdDroppables = null;

			// This dropFn is the callback which handles the actual moving of
			// nodes. We created a separate function for it, as it is called inside the "stop" callback
			// in IE7 and inside the "drop" callback in all other browsers.
			var dropFn = function() {
				if (lastHoveredCharacter) {
					// the user recently hovered over a character
					var $dropReferenceNode = jQuery(lastHoveredCharacter);

					if ($dropReferenceNode.is('.aloha-block-dropInlineElementIntoEmptyBlock')) {
						// the user wanted to drop INTO an empty block!
						$dropReferenceNode.children().remove();
						$dropReferenceNode.append($currentDraggable);
					} else if ($dropReferenceNode.is('.aloha-block-droppable-right')) {
						$dropReferenceNode.html($dropReferenceNode.html() + ' ');

						// Move draggable after drop reference node
						$dropReferenceNode.after($currentDraggable);
					} else {
						// Insert space in the beginning of the drop reference node
						if ($dropReferenceNode.prev('[data-i]').length > 0) {
							// If not the last element, insert space in front of next element (i.e. after the moved block)
							$dropReferenceNode.prev('[data-i]').html($dropReferenceNode.prev('[data-i]').html() + ' ');
						}
						$dropReferenceNode.html(' ' + $dropReferenceNode.html());

						// Move draggable before drop reference node
						$dropReferenceNode.before($currentDraggable);
					}

					$currentDraggable.removeClass('ui-draggable').css({'left': 0, 'top': 0}); // Remove "draggable" options... somehow "Destroy" does not work
					that._fixScrollPositionBugsInIE();
				}
				jQuery('.aloha-block-dropInlineElementIntoEmptyBlock').removeClass('aloha-block-dropInlineElementIntoEmptyBlock');

                // clear the created droppables
                $createdDroppables.droppable( "destroy" );
                $createdDroppables = null;

                blockDroppedProperly = true;
			};
			var editablesWhichNeedToBeCleaned = [];
			this.$element.draggable({
				handle: '.aloha-block-draghandle',
				scope: 'aloha-block-inlinedragdrop',
				disabled: !this._dd_isDragdropEnabled(),
				revert: function() {
					return (lastHoveredCharacter === null || !blockDroppedProperly);
				},
				revertDuration: 250,
				stop: function() {
					if (jQuery.browser.msie && 7 === parseInt(jQuery.browser.version, 10)) {
						dropFn();
					}
					jQuery.each(editablesWhichNeedToBeCleaned, function() {
						that._dd_traverseDomTreeAndRemoveSpans(this);
					});
					$currentDraggable = null;

					editablesWhichNeedToBeCleaned = [];
				},
				start: function() {
					blockDroppedProperly = false;
					editablesWhichNeedToBeCleaned = [];

					// In order to make Inline Blocks droppable into empty paragraphs, we insert a &nbsp; manually before the placeholder-br.
					// -> for IE
					jQuery('.aloha-editable').children('p:empty').html('&nbsp;');


					// Make **ALL** editables on the page droppable, such that it is possible
					// to drag/drop *across* editable boundaries
					var droppableCfg = {
						// make block elements droppable
						tolerance: 'pointer',
						addClasses: false, // performance optimization
						scope: 'aloha-block-inlinedragdrop',

						/**
						 * When hovering over a paragraph, we make convert its contents into spans, to make
						 * them droppable.
						 */
						over: function(event, ui) {
							if (jQuery.inArray(this, editablesWhichNeedToBeCleaned) === -1) {
								editablesWhichNeedToBeCleaned.push(this);
							}

							var hasOnlyProppingBr = (
								1 === jQuery(this).contents().length &&
								1 === jQuery(this).children('br').length
							);
							$currentDraggable = ui.draggable;

							if (jQuery(this).is(':empty') ||
								hasOnlyProppingBr ||
								jQuery(this).html() === '&nbsp;') {
								// The user is hovering over an empty
								// container; simply highlight the container.
								jQuery(this).addClass(
									'aloha-block-dropInlineElementIntoEmptyBlock');
								lastHoveredCharacter = this;
								return;
							}

							that._dd_traverseDomTreeAndWrapCharactersWithSpans(this);
							jQuery('span[data-i]', this).droppable({
								tolerance: 'pointer',
								addClasses: false,
								scope: 'aloha-block-inlinedragdrop',
								over: function() {
									if (lastHoveredCharacter) {
										// Just to be sure, we remove the css class of the last hovered character.
										// This is needed such that spans are deselected which contain multiple
										// lines.
										jQuery(lastHoveredCharacter).removeClass('aloha-block-droppable');
									}
									lastHoveredCharacter = this;
									jQuery(this).addClass('aloha-block-droppable');
								},
								out: function() {
									jQuery(this).removeClass('aloha-block-droppable');
									if (lastHoveredCharacter === this) {
										lastHoveredCharacter = null;
									}
								}
							});
							// Now that we updated the droppables in the system, we need to recalculate
							// the Drag Drop offsets.
							jQuery.ui.ddmanager.prepareOffsets(ui.draggable.data('draggable'), event);
						},
						out: function() {
							jQuery(this).removeClass('aloha-block-dropInlineElementIntoEmptyBlock');
						},

						/**
						 * When dropping over a paragraph, we use the "lastHoveredCharacter"
						 * as drop target.
						 */
						drop: function() {
							if (!(jQuery.browser.msie && 7 === parseInt(jQuery.browser.version, 10))) {
								dropFn();
							}
						}
					};

					$createdDroppables = jQuery( ".aloha-editable.aloha-block-dropzone" ).children( ":not(.aloha-block)" );
					$createdDroppables.droppable( droppableCfg );
					// Small HACK: Also make table cells droppable
					jQuery('.aloha-table-cell-editable').droppable(droppableCfg);
				}
			});
		},

		/**
		 * Helper which traverses the DOM tree starting from el and wraps all non-empty texts with spans,
		 * such that they can act as drop target.
		 *
		 * @param {DomElement} el
		 */
		_dd_traverseDomTreeAndWrapCharactersWithSpans: function(el) {
			var child;
			for(var i=0, l=el.childNodes.length; i < l; i++) {
				child = el.childNodes[i];
				if (child.nodeType === 1) { // DOM Nodes
					if (!~child.className.indexOf('aloha-block') && child.attributes['data-i'] === undefined) {
						// We only recurse if child does NOT have the class "aloha-block", and is NOT data-i
						this._dd_traverseDomTreeAndWrapCharactersWithSpans(child);
					} else if (child.attributes['data-i']) {
						// data-i set -> we have converted this hierarchy level already --> early return!
						return;
					}
				} else if (child.nodeType === 3) { // Text Nodes
					var numberOfSpansInserted = this._dd_insertSpans(child);
					i += numberOfSpansInserted;
					l += numberOfSpansInserted;
				}
			}
		},

		/**
		 * Helper which splits text on word boundaries, adding whitespaces to the following element.
		 * Examples:
		 * - "Hello world" -> ["Hello", " world"]
		 * - " Hello world" -> [" Hello", " world"]
		 * --> see the unit tests for the specification
		 */
		_dd_splitText: function(text) {
			var textParts = text.split(/(?=\b)/);
			var cleanedTextParts = [];

			var isWhitespace = false;
			for (var i=0,l=textParts.length; i<l; i++) {
				if (!/[^\t\n\r ]/.test(textParts[i])) {
					// if the current text part is just whitespace, we add a flag...
					isWhitespace = true;
				} else {
					if (isWhitespace) {
						// we have a whitespace to add
						cleanedTextParts.push(' ' + textParts[i]);
						isWhitespace = false;
					} else {
						cleanedTextParts.push(textParts[i]);
					}
				}
			}
			if (isWhitespace) {
				cleanedTextParts[cleanedTextParts.length - 1] += ' ';
			}
			return cleanedTextParts;
		},

		/**
		 * This is a helper for _dd_traverseDomTreeAndWrapCharactersWithSpans,
		 * performing the actual conversion.
		 *
		 * This function returns the number of additional DOM elements inserted.
		 * This is "numberOfSpansCreated - 1" (because one text node has been initially there)
		 */
		_dd_insertSpans: function(el) {
			var text = el.nodeValue;

			// If node just contains empty strings, we do not do anything.
			// Use ECMA-262 Edition 3 String and RegExp features
			if (!/[^\t\n\r ]/.test(text)) {
				return 0;
			}
			var newNodes = document.createDocumentFragment();

			var splitText = this._dd_splitText(text);

			var l = splitText.length;
			var x, word, leftWordPartLength, t;
			var numberOfSpansInserted = 0;

			for (var i=0; i<l; i++) {
				// left half of word
				word = splitText[i];
				if (word.length === 0) continue;
				// We use "floor" here such that sentence delimiters like "!" can have a block placed afterwards
				leftWordPartLength = Math.floor(word.length/2);

				// For Internet Explorer, we only make dropping AFTER words possible to improve performance
				var browserMajorVersion = parseInt(jQuery.browser.version, 10);
				if (jQuery.browser.msie && (7 === browserMajorVersion || 8 === browserMajorVersion)) {
					leftWordPartLength = 0;
				}

				if (leftWordPartLength > 0) {
					x = document.createElement('span');
					x.appendChild(document.createTextNode(word.substr(0, leftWordPartLength)));
					x.setAttribute('data-i', i);

					newNodes.appendChild(x);
					numberOfSpansInserted++;
				}

				// right half of word
				x = document.createElement('span');
				t = word.substr(leftWordPartLength);
				x.appendChild(document.createTextNode(t));
				x.setAttribute('data-i', i);
				x.setAttribute('class', 'aloha-block-droppable-right');

				newNodes.appendChild(x);
				numberOfSpansInserted++;
			}
			el.parentNode.replaceChild(newNodes, el);
			return numberOfSpansInserted-1;
		},

		/**
		 * After the Drag/Drop operation, we need to remove the SPAN elements
		 * again.
		 */
		_dd_traverseDomTreeAndRemoveSpans: function(el) {
			var nodesToDelete = [], convertBack;
			convertBack = function(el) {
				var currentlyTraversingExpandedText = false, currentText, lastNode;
				var child;
				for(var i=0, l=el.childNodes.length; i < l; i++) {
					child = el.childNodes[i];
					if (child.nodeType === 1) { // Node
						if (child.attributes['data-i'] !== undefined) {
							if (!currentlyTraversingExpandedText) {
								// We did not traverse expanded text before, and just entered an expanded text section
								// thus, we reset all variables to their initial state
								currentlyTraversingExpandedText = true;
								currentText = '';
								lastNode = undefined;
							}
							if (currentlyTraversingExpandedText) {
								// We are currently traversing the expanded text nodes, so we collect their data
								// together in the currentText variable. We know that they only
								// have one TextNode child, as this is the way we constructed them.
								//
								// Note: we do NOT use child.innerHTML here, as this returns HTML entities;
								// but we need the HTML entities already processed:
								// - child.innerHTML returns "Hello&nbsp;World"
								// - child.firstChild.nodeValue returns "Hello World"
								currentText += child.firstChild.nodeValue;

								if (lastNode) {
									nodesToDelete.push(lastNode);
								}
								lastNode = child;
							}
						} else {
							if (currentlyTraversingExpandedText) {
								currentlyTraversingExpandedText = false;
								// We just left a region with data-i elements set.
								// so, we need to store the currentText back to the region.
								// We do this by using the last visited node as anchor.
								lastNode.parentNode.replaceChild(document.createTextNode(currentText), lastNode);
							}
							// Recursion
							if (!~child.className.indexOf('aloha-block')) {
								// If child does not have the class "aloha-block", we iterate into it
								convertBack(child);
							}
						}
					}
				}
				if (currentlyTraversingExpandedText) {
					// Special case: the last child node *is* a wrapped text node and we are at the end of the collection.
					// In this case, we convert the text as well.
					lastNode.parentNode.replaceChild(document.createTextNode(currentText), lastNode);
				}
			};

			convertBack(el);

			for (var i=0, l=nodesToDelete.length; i<l; i++) {
				nodesToDelete[i].parentNode.removeChild(nodesToDelete[i]);
			}
		},

        /**
         * Helper method to check whether the drag & drop is enabled
         * for the editable, which this block belongs to.
         */
        _dd_isDragdropEnabled: function () {
			return BlockUtils.isDragdropEnabledForElement(this.$element.parent());
        },

		/**************************
		 * SECTION: Drag&Drop for Block elements
		 **************************/

		_setupDragDropForBlockElements: function() {
			// Mark the drag handle with an extra CSS class, such that it is picked up by BlockManager.initializeBlockLevelDragDrop()
			this.$element.find('.aloha-block-draghandle').addClass('aloha-block-draghandle-blocklevel');
		},


		/**************************
		 * SECTION: Other Rendering Helpers
		 **************************/

		/**
		 * Create editables from the inner content that was
		 * rendered for this block.
		 *
		 * This method must be idempotent. I.e. it must produce the same results
		 * when called once or twice.
		 *
		 * Override to use a custom implementation and to pass
		 * special configuration to .aloha()
		 */
		createEditablesIfNeeded: function() {
			// TODO: only create them if they are no aloha element yet...
			// TODO: should only happen inside Aloha
			this.$element.find('.aloha-editable').aloha();
		},

		/**
		 * Render block toolbar elements
		 *
		 * This method must be idempotent. I.e. it must produce the same results
		 * when called once or twice.
		 *
		 * Template method to render custom block UI.
		 * @api
		 */
		renderBlockHandlesIfNeeded: function() {
			if (this.isDraggable()) {
				if (this.$element.children('.aloha-block-draghandle').length === 0) {
					this.$element.prepend('<span class="aloha-block-handle aloha-block-draghandle aloha-cleanme"></span>');
				}
			}
		},

		/**************************
		 * SECTION: Attribute Handling
		 **************************/

		/**
		 * Get or set one or many attribute, similar to the jQuery attr() function.
		 *
		 * The attribute keys are converted internally to lowercase,
		 * so attr('foo', 'bar') and attr('FoO', 'bar') are the same internally.
		 * The same applies to reading.
		 *
		 * It is not allowed to set internal attributes (starting with aloha-block-) through this API.
		 *
		 * @api
		 * @param {String|Object} attributeNameOrObject
		 * @param {String} attributeValue
		 * @param {Boolean} Optional. If true, we do not fire change events.
		 */
		attr: function(attributeNameOrObject, attributeValue, suppressEvents) {
			var that = this, attributeChanged = false;

			if (arguments.length >= 2) {
				if (attributeNameOrObject.substr(0, 12) === 'aloha-block-') {
					Aloha.Log.error('block/block', 'It is not allowed to set internal block attributes (starting with aloha-block-) through Block.attr() (You tried to set ' + attributeNameOrObject + ')');
					return;
				}
				if (this._getAttribute(attributeNameOrObject) !== attributeValue) {
					attributeChanged = true;
				}
				this._setAttribute(attributeNameOrObject, attributeValue);
			} else if (typeof attributeNameOrObject === 'object') {
				jQuery.each(attributeNameOrObject, function(key, value) {
					if (key.substr(0, 12) === 'aloha-block-') {
						Aloha.Log.error('block/block', 'It is not allowed to set internal block attributes (starting with aloha-block-) through Block.attr() (You tried to set ' + key + ')');
						return;
					}
					if (that._getAttribute(key) !== value) {
						attributeChanged = true;
					}
					that._setAttribute(key, value);
				});
			} else if (typeof attributeNameOrObject === 'string') {
				return this._getAttribute(attributeNameOrObject);
			} else {
				return this._getAttributes();
			}
			if (attributeChanged && !suppressEvents) {
				this._update();
				this.trigger('change');
				if (Aloha.activeEditable) {
					Aloha.activeEditable.smartContentChange({type: 'block-change'});
				}
			}
			return null;
		},

		/**
		 * Internal helper for setting  a single attribute.
		 */
		_setAttribute: function(name, value) {
			this.$element.attr('data-' + name.toLowerCase(), value);
		},

		/**
		 * Internal helper for getting an attribute
		 */
		_getAttribute: function(name) {
			return this.$element.attr('data-' + name.toLowerCase());
		},

		/**
		 * Internal helper for getting all attributes
		 */
		_getAttributes: function() {
			var attributes = {};

			// element.data() not always up-to-date, that's why we iterate over the attributes directly.
			jQuery.each(this.$element[0].attributes, function(i, attribute) {
				if (attribute.name.substr(0, 5) === 'data-') {
					attributes[attribute.name.substr(5).toLowerCase()] = attribute.value;
				}
			});

			return attributes;
		}
	});

	/**
	 * @name block.block.DefaultBlock
	 * @class A default block that renders the initial content
	 * @extends block.block.AbstractBlock
	 */
	var DefaultBlock = AbstractBlock.extend(
	/** @lends block.block.DefaultBlock */
	{
		update: function($element, postProcessFn) {
			postProcessFn();
		}
	});

	/**
	 * @name block.block.DebugBlock
	 * @class A debug block outputs its attributes in a table
	 * @extends block.block.AbstractBlock
	 */
	var DebugBlock = AbstractBlock.extend(
	/** @lends block.block.DebugBlock */
	{
		title: 'Debugging',
		init: function($element, postProcessFn) {
			this.update($element, postProcessFn);
		},
		update: function($element, postProcessFn) {
			$element.css({display: 'block'});
			var renderedAttributes = '<table class="debug-block">';
			jQuery.each(this.attr(), function(k, v) {
				renderedAttributes += '<tr><th>' + k + '</th><td>' + v + '</td></tr>';
			});

			renderedAttributes += '</table>';

			$element.html(renderedAttributes);
			postProcessFn();
		}
	});
	
	/**
	 * @name block.block.EmptyBlock
	 * @class An empty block doesn't render any tag fill icons or borders (no Aloha tags)
	 * @extends block.block.AbstractBlock
	 */
	var EmptyBlock = AbstractBlock.extend (
	/** @lends block.block.EmptyBlock */
	{
		title: 'EmptyBlock',
		init: function() {},
		activate: function () {},
		deactivate: function () {},
		renderBlockHandlesIfNeeded: function () {},
		_preventSelectionChangedEventHandler: function () {}
	});

	return {
		AbstractBlock: AbstractBlock,
		DefaultBlock: DefaultBlock,
		DebugBlock: DebugBlock,
		EmptyBlock: EmptyBlock
	};
});

/* blockcontenthandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('block/blockcontenthandler',
['jquery', 'aloha/contenthandlermanager', 'block/blockmanager'],
function(jQuery, ContentHandlerManager, BlockManager) {

	/**
	 * @name block.BlockContentHandler
	 * @class Special block content handler
	 *
	 * The blog content handler handles pasting of blocks in editables. Pasted
	 * block markup will be replaced by a newly created block instance.
	 */
	var BlockContentHandler = ContentHandlerManager.createHandler(
	/** @lends block.BlockContentHandler */
	{
		/**
		 * Handle the pasting. Remove all unwanted stuff.
		 *
		 * There are two main cases which we need to distinguish:
		 * 1) Aloha Blocks are selected, and crtl+c is pressed then. In this case, *only the block* is copied / pasted.
		 * 2) Text is selected, but the selection also spans an aloha block.
		 *
		 * Generally, case 2) seems to work without bigger problems in Webkit / Firefox, while
		 * case 1) results in very much undesired and inconsistent behavior. If 1) happens,
		 * the property "data-aloha-block-copy-only-block" is set to "true"; so we can kick in and
		 * do additional cleanups.
		 * @param {jQuery} content
		 */
		handleContent: function( content ) {
			if ( typeof content === 'string' ){
				content = jQuery( '<div>' + content + '</div>' );
			} else if ( content instanceof jQuery ) {
				content = jQuery( '<div>' ).append(content);
			}

			if (content.find('.aloha-block[data-aloha-block-copy-only-block="true"]').length > 0) {
				// We are in case 1; so some more cleanup is needed (at least in webkit and firefox).

				// Webkit seems to *duplicate* the block when copying. The duplicated
				// block has *no ID property* set, that's how we can find and discard it.
				// Very ugly!
				content.find('.aloha-block:not([id])').remove();
				// Further cleanup for Webkit, removing empty nodes. Quite hacky!
				content.find('.aloha-block + span:empty').remove();
				content.find('div:empty').remove();
				// (another) Hack for Webkit, removing superfluous BR
				content.find('br.Apple-interchange-newline').remove();

				// Firefox adds a <br> directly before the .aloha-block...
				content.find('.aloha-block').prev('br').remove();

				// Chrome (at least) sometimes adds an empty <br> inside an (otherwise empty) span
				content.find('div > br:only-child').parent().remove();

			}

			content.find('.aloha-block').each(function() {
				var oldBlock = jQuery(this);

				var elementAttributes = {}; // all attributes except data-*
				var blockAttributes = {}; // all data* attributes
				jQuery.each(oldBlock[0].attributes, function(k, v) {
					if (v.nodeName === 'id') return;

					if (v.nodeName.match(/^data-/)) {
						blockAttributes[v.nodeName.substr(5)] = v.nodeValue;
					} else {
						elementAttributes[v.nodeName] = v.nodeValue;
					}
				});

				var newBlockId = GENTICS.Utils.guid();

				var newBlock = jQuery('<' + this.tagName + '/>')
					.attr(elementAttributes)
					.attr('id', newBlockId)
					.removeClass('aloha-block-active')
					.removeClass('aloha-block')
					.html(oldBlock.html());

				oldBlock.replaceWith(newBlock);

				// We need to blockify the contents with a timeout, as we need the connected DOM node for it.
				window.setTimeout(function() {
					BlockManager._blockify(jQuery('#' + newBlockId), blockAttributes);
				}, 50);
			});

			return content.html();
		}
	});
	return BlockContentHandler;
});

/* editor.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @name block.editor
 * @namespace Block attribute editors
 */
define('block/editor',['jquery', 'aloha/observable', 'util/class'],
function(jQuery, Observable, Class) {
	

	/**
	 * This is the base class for all editors in the sidebar. You need to extend
	 * this class if you need to write your own editor. In most cases, however,
	 * it is sufficent to subclass the AbstractFormElementEditor.
	 *
	 * @name block.editor.AbstractEditor
	 * @class An abstract editor
	 */
	var AbstractEditor = Class.extend(Observable,
	/** @lends block.editor.AbstractEditor */
	{
		/**
		 * Schema of the current element
		 *
		 * @param {Object}
		 * @api
		 */
		schema: null,

		/**
		 * @constructor
		 */
		_constructor: function(schema) {
			this.schema = schema;
		},

		/**
		 * Template method to render the editor elements. Override it
		 * in your subclass! Needs to return the jQuery element which
		 * should be added to the DOM
		 *
		 * @return {jQuery}
		 * @api
		 */
		render: function() {
			// Implement in subclass!
		},

		/**
		 * Template method to get the current editor value
		 *
		 * Override it in your subclass!
		 *
		 * @return {String}
		 * @api
		 */
		getValue: function() {
			// Implement in subclass!
		},

		/**
		 * Method which is called at initialization time, to set the current value.
		 *
		 * Override it in your subclass!
		 *
		 * You should not throw any change event here, as we need to break the loop "Block" -> "Editor" -> "Block"
		 *
		 * @param {String} value
		 * @api
		 */
		setValue: function(value) {
			// Implement in subclass!
		},

		/**
		 * Destroy the editor elements and unbind events
		 * @api
		 */
		destroy: function() {
			// Implement in subclass!
		},

		/**
		 * On deactivating, we still need to trigger a change event if the value has been modified.
		 *
		 * @private
		 */
		_deactivate: function() {
			this.trigger('change', this.getValue());
			this.destroy();
		}
	});

	/**
	 * This is a more specialized FormElementEditor which should be used
	 * for form-based editors.
	 *
	 * @name block.editor.AbstractFormElementEditor
	 * @class An abstract form editor with label
	 * @extends block.editor.AbstractEditor
	 */
	var AbstractFormElementEditor = AbstractEditor.extend(
	/** @lends block.editor.AbstractFormElementEditor */
	{

		/**
		 * Input element HTML definition
		 *
		 * You need to override this in your subclass.
		 *
		 * @type String
		 *
		 * @api
		 */
		formInputElementDefinition: null,

		/**
		 * The jQuery element of the form input element.
		 *
		 * @type {jQuery}
		 */
		_$formInputElement: null,

		/**
		 * Render the label and form element
		 * @return {jQuery}
		 */
		render: function() {
			var $wrapper = jQuery('<div class="aloha-block-editor" />');
			var guid = GENTICS.Utils.guid();
			$wrapper.append(this.renderLabel().attr('id', guid));
			$wrapper.append(this.renderFormElement().attr('id', guid));
			return $wrapper;
		},

		/**
		 * Render the label for the editor, by using the "label" property
		 * from the schema.
		 *
		 * @return {jQuery}
		 */
		renderLabel: function() {
			var element = jQuery('<label />');
			element.html(this.schema.label);
			return element;
		},

		/**
		 * Render the form input element
		 * @return {jQuery}
		 */
		renderFormElement: function() {
			var that = this;
			this._$formInputElement = jQuery(this.formInputElementDefinition);

			this.afterRenderFormElement(this._$formInputElement);

			this._$formInputElement.change(function() {
				that.trigger('change', that.getValue());
			});

			return this._$formInputElement;
		},

		/**
		 * Callback which can be implemented by subclasses to adjust the rendered
		 * form input element
		 *
		 * @param {jQuery} $formElement the form element being rendered
		 * @api
		 */
		afterRenderFormElement: function($formElement) {

		},

		/**
		 * @return {String}
		 */
		getValue: function() {
			return this._$formInputElement.val();
		},

		/**
		 * We do not throw any change event here, as we need to break the loop "Block" -> "Editor" -> "Block"
		 */
		setValue: function(value) {
			this._$formInputElement.val(value);
		},

		/**
		 * Cleanup and remove the input element
		 */
		destroy: function() {
			this._$formInputElement.remove();
		}

	});

	/**
	 * @name block.editor.StringEditor
	 * @class An editor for string input
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var StringEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.StringEditor */
	{
		formInputElementDefinition: '<input type="text" />'
	});

	/**
	 * @name block.editor.NumberEditor
	 * @class An editor for numbers
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var NumberEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.NumberEditor */
	{
		// TODO Range should be an option
		formInputElementDefinition: '<input type="range" />',

		afterRenderFormElement: function($formElement) {
			if (!this.schema.range) return;

			if (this.schema.range.min) {
				$formElement.attr('min', this.schema.range.min);
			}

			if (this.schema.range.max) {
				$formElement.attr('max', this.schema.range.max);
			}

			if (this.schema.range.step) {
				$formElement.attr('step', this.schema.range.step);
			}
		}
	});

	/**
	 * @name block.editor.UrlEditor
	 * @class An editor for URLs
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var UrlEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.UrlEditor */
	{
		formInputElementDefinition: '<input type="url" />'
	});

	/**
	 * @name block.editor.EmailEditor
	 * @class An editor for email addresses
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var EmailEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.EmailEditor */
	{
		formInputElementDefinition: '<input type="email" />'
	});

	/**
	 * @name block.editor.SelectEditor
	 * @class An editor for select fields
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var SelectEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.SelectEditor */
	{
		formInputElementDefinition: '<select />',

		afterRenderFormElement: function($formElement) {
			jQuery.each(this.schema.values, function() {
				var el = this;
				$formElement.append(jQuery('<option />').attr('value', el.key).html(el.label));
			});
		}
	});

	/**
	 * @name block.editor.ButtonEditor
	 * @class An editor for buttons, executing a custom supplied callback "callback"
	 * @extends block.editor.AbstractFormElementEditor
	 */
	var ButtonEditor = AbstractFormElementEditor.extend(
	/** @lends block.editor.SelectEditor */
	{
		formInputElementDefinition: '<button />',

		afterRenderFormElement: function($formElement) {
			var that = this;
			$formElement.html(this.schema.buttonLabel);
			$formElement.click(function() {
				that.schema.callback();
			})
		}
	});

	return {
		AbstractEditor: AbstractEditor,
		AbstractFormElementEditor: AbstractFormElementEditor,
		StringEditor: StringEditor,
		NumberEditor: NumberEditor,
		UrlEditor: UrlEditor,
		EmailEditor: EmailEditor,
		SelectEditor: SelectEditor,
		ButtonEditor: ButtonEditor
	}
});

define('block/nls/i18n',{
	"root":  {
		"button.toggledragdrop.tooltip": "Toggle Drag & Drop"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* block-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @name block
 * @namespace Block plugin
 */
define('block/block-plugin',[
	'aloha',
	'aloha/plugin',
	'jquery',
	'aloha/contenthandlermanager',
	'block/blockmanager',
	'block/sidebarattributeeditor',
	'block/block',
	'block/editormanager',
	'block/blockcontenthandler',
	'block/editor',
	'ui/ui',
	'ui/toggleButton',
	'i18n!block/nls/i18n',
	'i18n!aloha/nls/i18n',
	'jqueryui'
], function(
	Aloha,
 	Plugin,
 	jQuery,
 	ContentHandlerManager, 
	BlockManager,
 	SidebarAttributeEditor,
 	block,
 	EditorManager,
 	BlockContentHandler,
 	editor,
 	Ui,
 	ToggleButton, 
	i18n,
	i18nCore
) {
	

	var defaultRootTags = ['div', 'span'];

	/**
	 * Register the 'block' plugin
	 */
	var BlockPlugin = Plugin.create( 'block', {
		
		/**
		 * default button configuration
		 */
		config: [], 

		settings: {},

		init: function () {
			var that = this;

			// set default root tags
			if (!this.settings.rootTags) {
				this.settings.rootTags = defaultRootTags;
			}

			// Register default block types			
			BlockManager.registerBlockType('DebugBlock', block.DebugBlock);
			BlockManager.registerBlockType('DefaultBlock', block.DefaultBlock);
			BlockManager.registerBlockType('EmptyBlock', block.EmptyBlock);

			// Register default editors
			EditorManager.register('string', editor.StringEditor);
			EditorManager.register('number', editor.NumberEditor);
			EditorManager.register('url', editor.UrlEditor);
			EditorManager.register('email', editor.EmailEditor);
			EditorManager.register('select', editor.SelectEditor);
			EditorManager.register('button', editor.ButtonEditor);

			// register content handler for block plugin
			ContentHandlerManager.register('block', BlockContentHandler);

			BlockManager.setDragDropState(that.isDragDropEnabled());
			BlockManager.registerEventHandlers();
			BlockManager.initializeBlockLevelDragDrop();

			Aloha.bind('aloha-plugins-loaded', function () {
				that._createBlocks();
				if (false !== that.settings['sidebarAttributeEditor']) {
					SidebarAttributeEditor.init();
				}
			});

			// create the toolbar buttons
			this.createButtons();

			// set the dropzones for the initialized editable
			Aloha.bind('aloha-editable-created', function (e, editable) {
				that.setDropzones(editable.obj);
 			});

			// apply specific configuration if an editable has been activated
			Aloha.bind('aloha-editable-activated', function (e, params) {
				that.applyButtonConfig(params.editable);
			});

		},

		/**
		 * Applies a configuration specific for an editable buttons not
		 * available in this configuration are hidden
		 *
		 * @param {!Object}
		 *        The editable which has become active and for which to
		 *        apply the button config.
		 */
		applyButtonConfig: function (editable) {
			if (this._isDragdropToggleEnabled(editable)) {
				this._toggleDragDropButton.show();
				this._toggleDragDropButton.setState(this._getDragdropState(editable));
			} else {
				this._toggleDragDropButton.hide();
			}
		},

		/**
		 * Test whether toggleability of the dragndrop feature is turned on.
		 *
		 * @param {!Aloha.Editable} editable
		 *        The editable for which toggling may be turned on, if
		 *        toggling of the dragndrop feature is configured
		 *        per-editable.
		 *        Only used if !toggleDragdropGlobal.
		 * @return {boolean}
		 *        Whether the toggleability of the dragndrop feature is
		 *        turned on either globally, or for the given editable.
		 */
		_isDragdropToggleEnabled: function (editable) {
			var config = this.getEditableConfig(editable.obj);

			// toggle drag & drop option can be set as
			// config: {'toggeleDragdrop': true} or
			// config: ['toggleDragdrop']
			var toggleDragdropConfigured = function() {
				return (config[0] === "toggleDragdrop") ||
								config.toggleDragdrop == true   ||
								config.toggleDragdrop == 'true' ||
								config.toggleDragdrop == 1      ||
								config.toggleDragdrop == '1'
			};

			var toggleGloballyOrPerEditable =
				((this.settings && this.settings.config && this.settings.config.toggleDragdropGlobal) ||
				 toggleDragdropConfigured());
			return toggleGloballyOrPerEditable && this.isDragDropEnabled();
		},

		/**
		 * Gets the current toggle state of the dragndrop feature.
		 *
		 * @param {!Aloha.Editable} editable
		 *        The editable that is used to track the per-editable
		 *        dragdrop toggled state.
		 *        Only used if !toggleDragdropGlobal.
		 * @return {boolean}
		 *        Whether the dragdrop feature is turned on either
		 *        globally, or for the given editable.
		 */
		_getDragdropState: function (editable) {
			return ((this.settings && this.settings.config && this.settings.config.toggleDragdropGlobal) ?
					BlockManager.getDragDropState() :
					editable.obj.data("block-dragdrop"));
		},

		/**
		 * Toggles the dragndrop state.
		 *
		 * If toggleDragdropGlobal is turned on, will toggle the
		 * dragndrop state on each existing editable.
		 *
		 * If toggleDragdropGlobal is not turned on, will toggle the
		 * dragndrop state only for the given editable.
		 *
		 * @param {!Aloha.Editable} editable
		 *        The editable that is used to track the per-editable
		 *        dragdrop toggled state.
		 *        Only used if !toggleDragdropGlobal.
		 */
		_toggleDragdropState: function (editable) {
			if (this.settings && this.settings.config && this.settings.config.toggleDragdropGlobal) {
				var dragdropState = !BlockManager.getDragDropState();
				// Setting the dragdrop state in the block manager
				// ensures that newly created editables will receive the
				// correct "block-dragdrop" data attribute.
				BlockManager.setDragDropState(dragdropState);
				for (var i = 0; i < Aloha.editables.length; i++) {
					var editable = Aloha.editables[i];
					this._setDragDropStateForEditable(editable.obj, dragdropState);
				}
			} else {
				var toggleState = !editable.obj.data("block-dragdrop");
				this._setDragDropStateForEditable(editable.obj, toggleState);
			}
		},

		createButtons: function () {
			var that = this;

			this._toggleDragDropButton = Ui.adopt( "toggleDragDrop", ToggleButton, {
				tooltip: i18n.t( 'button.toggledragdrop.tooltip' ),
				icon: 'aloha-icon aloha-icon-toggledragdrop',
				scope: 'Aloha.continuoustext',
				click: function() {
					that._toggleDragdropState(Aloha.activeEditable);
				}
			});
		},

		/**
		 * Set available dropzones for the given editable.
		 * @return void 
		 */
		setDropzones: function (editable) {
			var that = this;
			var config = that.getEditableConfig(editable);
			var dropzones = (config && config.dropzones) || that.settings.dropzones;

			if ( dropzones ) {
				editable.data( 'block-dropzones', dropzones );	
			} else {
				// if dropzones are undefined all editables should be dropzones
				editable.data( 'block-dropzones', [".aloha-editable"] );	
			}
		},

		/**
		 * Checks whether drag & drop is enabled for blocks.
		 * @return boolean 
		 */
		isDragDropEnabled: function() {
			if ( this.settings && typeof this.settings.dragdrop !== "undefined" ) {
				// Normalize config
				return (
					this.settings.dragdrop === true   ||
					this.settings.dragdrop === 'true' ||
					this.settings.dragdrop === 1      ||
					this.settings.dragdrop === '1'
				);
			} else {
				return true; // by default dragdrop is activated 
			}
		},

		/**
		 * Create blocks from default settings
		 */
		_createBlocks: function() {
			if (!this.settings.defaults) {
				this.settings.defaults = {};
			}
			jQuery.each( this.settings.defaults, function(selector, instanceDefaults) {
				jQuery( selector ).alohaBlock( instanceDefaults );
			});
		},

		/**
		 * Set the drag & drop state for the given editable.
		 */
		_setDragDropStateForEditable: function($editable, state) {
			$editable.data( "block-dragdrop", state );

			if ( $editable.hasClass("ui-sortable") ) {
				$editable.sortable( "option", "disabled", !state );	
			}

			$editable.find( ".aloha-block.ui-draggable" ).each( function() {
				jQuery( this ).draggable( "option", "disabled", !state );	
			});

			$editable.find( ".aloha-block-handle" ).each( function() {
				if (state) {
					jQuery( this ).addClass( "aloha-block-draghandle" );	
				} else {
					jQuery( this ).removeClass( "aloha-block-draghandle" );	
				}
			});
		}
	});

	/**
	 * See (http://jquery.com/).
	 * @name jQuery.fn
	 * @class
	 * See the jQuery Library  (http://jquery.com/) for full details.  This just
	 * documents the function and classes that are added to jQuery by this plug-in.
	 */

	/**
	 * Create Aloha blocks from the matched elements
	 * @api
	 * @param {Object} instanceDefaults
	 */
	jQuery.fn.alohaBlock = function(instanceDefaults) {
		instanceDefaults = instanceDefaults || {};
		jQuery( this ).each( function(index, element) {
			BlockManager._blockify( element, instanceDefaults );
		});

		// Chain
		return jQuery( this );
	};

	/**
	 * Un"block" the matched elements. If matched elements were made blocks
	 * (by calling alohaBlock() on them), they will no longer be blocks.
	 * 
	 * @api
	 */
	jQuery.fn.mahaloBlock = function() {
		jQuery(this).each(function (index, element) {
			BlockManager._unblockify(element);
		});
	};

	// jQuery.fn.mahaloBlock = TODO
	return BlockPlugin;
});

define('align/nls/i18n',{
	"root":  {
		"button.alignright.tooltip": "Align to the right",
		"button.alignleft.tooltip": "Align to the left",
		"button.aligncenter.tooltip": "Center",
		"button.alignjustify.tooltip": "Justify",
		"button.aligntop.tooltip": "Align to the top",
		"button.alignmiddle.tooltip": "Align to the middle",
		"button.alignbottom.tooltip": "Align to the bottom"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* align-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('align/align-plugin',[
	'aloha',
	'aloha/plugin',
	'ui/ui',
	'ui/toggleButton',
	'i18n!align/nls/i18n',
	'i18n!aloha/nls/i18n',
	'jquery',
	'PubSub'
], function(
		Aloha,
    Plugin,
    Ui,
    ToggleButton,
    i18n,
    i18nCore,
    jQuery,
    PubSub
) {
	

	var GENTICS = window.GENTICS;

	/**
	 * register the plugin with unique name
	 */
	 return Plugin.create('align', {
		_constructor: function(){
			this._super('align');
		},
		/**
		 * Configuration (available align options)
		 */
		config: {
			alignment: ['right','left','center','justify','top','middle','bottom']
		},

		/**
		 * Alignment wanted by the user
		 */
		alignment: '',

		verticalAlignment: '',

		/**
		 * Alignment of the selection before modification
		 */
		lastAlignment: '',

		lastVerticalAlignment: '',

		/**
		 * Initialize the plugin and set initialize flag on true
		 */
		init: function () {
			this.createButtons();

			var that = this;

			// apply specific configuration if an editable has been activated
			Aloha.bind('aloha-editable-activated', function (e, params) {
				that.applyButtonConfig(params.editable.obj);
			});

			PubSub.sub('aloha.selection.context-change', function (message) {
				var rangeObject = message.range;

				if (Aloha.activeEditable) {
					that.buttonPressed(rangeObject);
				}
			});
		},

		buttonPressed: function (rangeObject) {
			this.horizontalButtonPressed( rangeObject );
			this.verticalButtonPressed( rangeObject );
		},

		horizontalButtonPressed: function(rangeObject) {
			var that = this;

			this.lastAlignment = this.alignment;

			//reset current alignment
			this.alignment = '';

			rangeObject.findMarkup(function() {
				// try to find explicitly defined text-align style property
				if(this.style.textAlign !== "") {
					that.alignment = this.style.textAlign;
					return true;
				}

				that.alignment = jQuery(this).css('text-align');
		  }, Aloha.activeEditable.obj);

			// set horizontal button states
			if (this.alignment != this.lastAlignment) {
				// reset all button states -- it can only be one active...
				this._alignRightButton.setState(false);
				this._alignLeftButton.setState(false);
				this._alignCenterButton.setState(false);
				this._alignJustifyButton.setState(false);

				switch (this.alignment) {
				case 'right':
					this._alignRightButton.setState(true);
					break;
				case 'center':
					this._alignCenterButton.setState(true);
					break;
				case 'justify':
					this._alignJustifyButton.setState(true);
					break;
				default:
					this._alignLeftButton.setState(true);
					this.alignment = 'left';
					break;
				}
			}
		},

		verticalButtonPressed: function(rangeObject) {
			var that = this;

			this.lastVerticalAlignment = this.verticalAlignment;

			//reset current alignment
			this.verticalAlignment = '';

			rangeObject.findMarkup(function() {
				// try to find explicitly defined vertical-align style property
				if(this.style.verticalAlign !== "") {
					that.verticalAlignment = this.style.verticalAlign;
					return true;
				}

				that.verticalAlignment = jQuery(this).css('vertical-align');
		  }, Aloha.activeEditable.obj);

			// set vertical button states
			if (this.verticalAlignment != this.lastVerticalAlignment) {
				// reset all button states -- it can only be one active...
				this._alignTopButton.setState(false);
				this._alignMiddleButton.setState(false);
				this._alignBottomButton.setState(false);

				switch (this.verticalAlignment) {
				case 'top':
					this._alignTopButton.setState(true);
					break;
				case 'middle':
					this._alignMiddleButton.setState(true);
					break;
				case 'bottom':
					this._alignBottomButton.setState(true);
					break;
				default:
					this._alignTopButton.setState(true);
					this.verticalAlignment = 'top';
					break;
				}
			}
		},

		/**
		 * applys a configuration specific for an editable
		 * buttons not available in this configuration are hidden
		 * @param {Object} id of the activated editable
		 * @return void
		 */
		applyButtonConfig: function (obj) {
			var config = this.getEditableConfig(obj);

			if ( config && config.alignment && !this.settings.alignment ) {
				config = config;
			} else if ( config[0] && config[0].alignment) {
				config = config[0];
			} else if ( this.settings.alignment ) {
				config.alignment = this.settings.alignment;
			}

			if (typeof config.alignment === 'undefined') {
				config = this.config;
			}

			if ( jQuery.inArray('right', config.alignment) != -1) {
				this._alignRightButton.show(true);
			} else {
				this._alignRightButton.show(false);
			}

			if ( jQuery.inArray('left', config.alignment) != -1) {
				this._alignLeftButton.show(true);
			} else {
				this._alignLeftButton.show(false);
			}

			if ( jQuery.inArray('center', config.alignment) != -1) {
				this._alignCenterButton.show(true);
			} else {
				this._alignCenterButton.show(false);
			}

			if ( jQuery.inArray('justify', config.alignment) != -1) {
				this._alignJustifyButton.show(true);
			} else {
				this._alignJustifyButton.show(false);
			}

			if ( jQuery.inArray('top', config.alignment) != -1) {
				this._alignTopButton.show(true);
			} else {
				this._alignTopButton.show(false);
			}

			if ( jQuery.inArray('middle', config.alignment) != -1) {
				this._alignMiddleButton.show(true);
			} else {
				this._alignMiddleButton.show(false);
			}

			if ( jQuery.inArray('bottom', config.alignment) != -1) {
				this._alignBottomButton.show(true);
			} else {
				this._alignBottomButton.show(false);
			}
		},

		createButtons: function () {
		    var that = this;

			this._alignLeftButton = Ui.adopt("alignLeft", ToggleButton, {
				tooltip: i18n.t('button.alignleft.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-left',
				scope: 'Aloha.continuoustext',
				click: function(){ that.align('left'); }
			});

			this._alignCenterButton = Ui.adopt("alignCenter", ToggleButton, {
				tooltip: i18n.t('button.aligncenter.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-center',
				scope: 'Aloha.continuoustext',
				click: function(){ that.align('center'); }
			});

			this._alignRightButton = Ui.adopt("alignRight", ToggleButton, {
				tooltip: i18n.t('button.alignright.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-right',
				scope: 'Aloha.continuoustext',
				click: function(){ that.align('right'); }
			});

			this._alignJustifyButton = Ui.adopt("alignJustify", ToggleButton, {
				tooltip: i18n.t('button.alignjustify.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-justify',
				scope: 'Aloha.continuoustext',
				click: function(){ that.align('justify'); }
			});

			this._alignTopButton = Ui.adopt("alignTop", ToggleButton, {
				tooltip: i18n.t('button.aligntop.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-top',
				scope: 'table.cell',
				click: function(){ that.verticalAlign('top'); }
			});

			this._alignMiddleButton = Ui.adopt("alignMiddle", ToggleButton, {
				tooltip: i18n.t('button.alignmiddle.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-middle',
				scope: 'table.cell',
				click: function(){ that.verticalAlign('middle'); }
			});

			this._alignBottomButton = Ui.adopt("alignBottom", ToggleButton, {
				tooltip: i18n.t('button.alignbottom.tooltip'),
				icon: 'aloha-icon aloha-icon-align aloha-icon-align-bottom',
				scope: 'table.cell',
				click: function(){ that.verticalAlign('bottom'); }
			});

		},

		verticalAlign: function ( tempAlignment ) {

			var that = this;
			var range = Aloha.Selection.getRangeObject();

			this.lastVerticalAlignment = this.verticalAlignment;
			this.verticalAlignment = tempAlignment;

			// check if the selection range is inside a table
			var selectedCells = this.getSelectedCells( range );

			if ( selectedCells ) {
				that.toggleAlign ( selectedCells, 'vertical-align');
			}

			// reset previous button states
			if ( this.verticalAlignment != this.lastVerticalAlignment ) {
				switch ( this.lastVerticalAlignment ) {
					case 'top':
						this._alignTopButton.setState(false);
						break;

					case 'middle':
						this._alignMiddleButton.setState(false);
						break;

					case 'bottom':
						this._alignBottomButton.setState(false);
						break;
				}
			}

			// select the (possibly modified) range
			range.select();

		},

		/**
		 * Align the selection or remove it
		 */
		align: function ( tempAlignment ) {

			var that = this;
			var range = Aloha.Selection.getRangeObject();

			this.lastAlignment = this.alignment;
			this.alignment = tempAlignment;

			var rangeParent = range.getCommonAncestorContainer();

			// check if the selection range is inside a table
			var selectedCells = this.getSelectedCells( range );

			if ( selectedCells ) {
				that.toggleAlign( selectedCells );
			} else if (!GENTICS.Utils.Dom.isEditingHost(rangeParent)) {

				// if the parent node is not the main editable node and align
				// OR iterates the whole selectionTree and align
					that.toggleAlign( rangeParent );
			}	else {
				var alignableElements = [];
				jQuery.each(Aloha.Selection.getRangeObject().getSelectionTree(), function () {
					if (this.selection !== 'none' && this.domobj.nodeType !== 3) {
						alignableElements.push( this.domobj );
					}
				});

				that.toggleAlign( alignableElements );
			}

			// reset previous button states
			if ( this.alignment != this.lastAlignment ) {
				switch ( this.lastAlignment ) {
					case 'right':
						this._alignRightButton.setState(false);
						break;

					case 'left':
						this._alignLeftButton.setState(false);
						break;

					case 'center':
						this._alignCenterButton.setState(false);
						break;

					case 'justify':
						this._alignJustifyButton.setState(false);
						break;
				}
			}

			// select the (possibly modified) range
			range.select();

		},

		getSelectedCells: function( range ) {

			var selectedCell;

			var activeTable = range.findMarkup(function() {
				if ( jQuery(this).is( 'td,th' ) ) {
					selectedCell = this;
				}
				return jQuery( this ).is( 'table.aloha-table' );
			}, Aloha.activeEditable.obj);

			var selectedCells = jQuery( activeTable ).find( '.aloha-cell-selected' );

			return (  selectedCells.length ? selectedCells : selectedCell );

		},

		/**
		 * Toggle the align property of given DOM object(s)
		 */
		toggleAlign: function ( domObj, property ) {

			var that = this;

			property = property || 'text-align';

			var newAlignment = ( property === 'vertical-align' ) ? that.verticalAlignment : that.alignment;

			var shouldRemoveAlignment = true;

			jQuery( domObj ).each( function() {

				var currentAlignment = jQuery( this ).css( property );

				if ( currentAlignment != newAlignment ) {
					shouldRemoveAlignment = false;
					return false;
				}

			});


			jQuery( domObj ).each( function() {

				var currentAlignment = jQuery( this ).css( property );

				if ( ( currentAlignment == newAlignment ) && shouldRemoveAlignment ) {
					jQuery( this ).css( property, '' );
				} else {
					jQuery( this ).css( property, newAlignment );
				}

			});

		}

	});

});

define('abbr/nls/i18n',{
	"root":  {
		"floatingmenu.tab.abbr": "Abbreviation",
		"button.addabbr.tooltip": "insert abbreviation",
		"button.abbr.tooltip": "format as abbreviation",
		"newabbr.defaulttext": "Abbr"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* abbr-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('abbr/abbr-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'ui/ui',
	'ui/toggleButton',
	'ui/button',
	'ui/scopes',
	'ui/port-helper-attribute-field',
	'i18n!abbr/nls/i18n',
	'i18n!aloha/nls/i18n'
], function (
	Aloha,
	jQuery,
	Plugin,
	Ui,
	ToggleButton,
	Button,
	Scopes,
	AttributeField,
	i18n,
	i18nCore
) {
	
	var GENTICS = window.GENTICS;

	/**
	 * Add additional target objects, in case the selection includes
	 * several abbrs tag
	 *
	 * @param {RangeObject} rangeObject Selection Range
	 * @param {LinkPlugin} that Link Plugin object
	 */
	function addAdditionalTargetObject(rangeObject, field) {
		var abbrs = rangeObject.findAllMarkupByTagName('ABBR', rangeObject);
		for (var i = 0, len = abbrs.length; i < len; i++) {
			field.addAdditionalTargetObject(abbrs[i]);
		}
	}
	/**
	 * register the plugin with unique name
	 */
	return Plugin.create( 'abbr', {
		/**
		 * default button configuration
		 */
		config: [ 'abbr' ],

		/**
		 * Initialize the plugin and set initialize flag on true
		 */
		init: function () {
			this.createButtons();
		    this.subscribeEvents();
		    this.bindInteractions();
		},

		/**
		 * Initialize the buttons
		 */
		createButtons: function () {
		    var me = this;

			this._formatAbbrButton = Ui.adopt("formatAbbr", ToggleButton, {
				tooltip: i18n.t("button.abbr.tooltip"),
				icon: "aloha-icon aloha-icon-abbr",
				scope: 'Aloha.continuoustext',
				click: function(){
					me.formatAbbr();
				}
			});

			this._insertAbbrButton = Ui.adopt("insertAbbr", Button, {
				tooltip: i18n.t('button.addabbr.tooltip'),
				icon: 'aloha-icon aloha-icon-abbr',
				scope: 'Aloha.continuoustext',
				click: function(){
					me.insertAbbr( false );
				}
			});

		    this.abbrField = AttributeField({
		    	width: 320,
		    	name: 'abbrText',
		        scope: 'Aloha.continuoustext'
		    });
		    
		    this.remAbbrButton = Ui.adopt("removeAbbr", Button, {
				tooltip: i18n.t('button.remabbr.tooltip'),
				icon: 'aloha-icon aloha-icon-abbr-rem',
				scope: 'Aloha.continuoustext',
				click: function () {
					me.removeAbbr();
				}
			});
		},

		/**
		 * Parse a all editables for abbreviations
		 * Add the abbr shortcut to all edtiables
		 */
		bindInteractions: function () {
			var me = this;
			
		    // on blur check if abbr title is empty. If so remove the a tag
		    this.abbrField.addListener( 'blur', function ( obj, event ) {
		        if ( this.getValue() == '' ) {
		            me.removeAbbr();
		        }
		    } );

		    // add to all editables the abbr shortcut
		    for ( var i = 0; i < Aloha.editables.length; i++ ) {
		        // CTRL+G
		        Aloha.editables[ i ].obj.keydown( function ( e ) {
		    		if ( e.metaKey && e.which == 71 ) {
				        if ( me.findAbbrMarkup() ) {
							me.abbrField.foreground();
							me.abbrField.focus();
				        } else {
				        	me.insertAbbr();
				        }
						
				        // prevent from further handling
			            // on a MAC Safari cursor would jump to location bar. Use ESC then META+L
				        e.stopPropagation();
				        e.preventDefault();
						
			            return false;
		    		}
		        } );
		    }
		},

		subscribeEvents: function () {
			var me = this;
			var editableConfig = {};

			Aloha.bind('aloha-editable-activated', function () {
				if (!Aloha.activeEditable || !Aloha.activeEditable.obj) {
					return;
				}
				var config = me.getEditableConfig(Aloha.activeEditable.obj);
				editableConfig[Aloha.activeEditable.getId()] =
						jQuery.inArray('abbr', config) !== -1;
			});

			Aloha.bind('aloha-editable-destroyed', function () {
				if (Aloha.activeEditable && Aloha.activeEditable.obj) {
					delete editableConfig[Aloha.activeEditable.getId()];
				}
			});

			Aloha.bind('aloha-selection-changed', function (event, range) {
		        if (!Aloha.activeEditable) {
					return;
				}

				if (editableConfig[Aloha.activeEditable.getId()]) {
					me._formatAbbrButton.show();
					me._insertAbbrButton.show();
				} else {
					me._formatAbbrButton.hide();
					me._insertAbbrButton.hide();
					return;
				}

				var foundMarkup = me.findAbbrMarkup(range);
				if (foundMarkup) {
					me._insertAbbrButton.hide();
					me._formatAbbrButton.setState(true);
					// show the field and button for abbreviation
					me.abbrField.show();
					me.remAbbrButton.show();

					Scopes.enterScope(me.name, 'abbr');

					me.abbrField.setTargetObject(foundMarkup, 'title');
					addAdditionalTargetObject(range, me.abbrField);
				} else {
					// hide the field and button for abbreviation
					me.abbrField.hide();
					me.remAbbrButton.hide();

					Scopes.leaveScope(me.name, 'abbr', true);

					me._formatAbbrButton.setState(false);
					me.abbrField.setTargetObject(null);
				}
		    });
		},

		/**
		 * Check whether inside a abbr tag
		 * @param {GENTICS.Utils.RangeObject} range range where to insert the object (at start or end)
		 * @return markup
		 * @hide
		 */
		findAbbrMarkup: function ( range ) {
			if ( typeof range == 'undefined' ) {
		        var range = Aloha.Selection.getRangeObject();
		    }
			
			if ( Aloha.activeEditable ) {
			    return range.findMarkup( function() {
			        return this.nodeName.toLowerCase() == 'abbr';
			    }, Aloha.activeEditable.obj );
			} else {
				return null;
			}
		},

		/**
		 * Format the current selection or if collapsed the current word as abbr.
		 * If inside a abbr tag the abbr is removed.
		 */
		formatAbbr: function () {
			var range = Aloha.Selection.getRangeObject();

		    if ( Aloha.activeEditable ) {
		        if ( this.findAbbrMarkup( range ) ) {
		            this.removeAbbr();
		        } else {
		            this.insertAbbr();
		        }
		    }
		},

		/**
		 * Insert a new abbr at the current selection. When the selection is collapsed,
		 * the abbr will have a default abbr text, otherwise the selected text will be
		 * the abbr text.
		 */
		insertAbbr: function ( extendToWord ) {
		    // current selection or cursor position
		    var range = Aloha.Selection.getRangeObject();

		    // do not insert a abbr in a abbr
		    if ( this.findAbbrMarkup( range ) ) {
		        return;
		    }

		    // if selection is collapsed then extend to the word.
		    if ( range.isCollapsed() && extendToWord != false ) {
		        GENTICS.Utils.Dom.extendToWord( range );
		    }
			
		    if ( range.isCollapsed() ) {
		        // insert a abbr with text here
		        var abbrText = i18n.t( 'newabbr.defaulttext' );
		        var newAbbr = jQuery( '<abbr title="">' + abbrText + '</abbr>' );
		        GENTICS.Utils.Dom.insertIntoDOM( newAbbr, range, jQuery( Aloha.activeEditable.obj ) );
		        range.startContainer = range.endContainer = newAbbr.contents().get( 0 );
		        range.startOffset = 0;
		        range.endOffset = abbrText.length;
		    } else {
		        var newAbbr = jQuery( '<abbr title=""></abbr>' );
		        GENTICS.Utils.Dom.addMarkup( range, newAbbr, false );
		    }
			
		    range.select();

			this.abbrField.foreground();
			this.abbrField.focus();
		},

		/**
		 * Remove an a tag.
		 */
		removeAbbr: function () {
		    var range = Aloha.Selection.getRangeObject();
		    var foundMarkup = this.findAbbrMarkup();
		    if ( foundMarkup ) {
		        // remove the abbr
		        GENTICS.Utils.Dom.removeFromDOM( foundMarkup, range, true );
		        // select the (possibly modified) range
		        range.select();
		    }
		},

		/**
		 * Make the given jQuery object (representing an editable) clean for saving
		 * Find all abbrs and remove editing objects
		 * @param obj jQuery object to make clean
		 * @return void
		 */
		makeClean: function ( obj ) {
			// nothing to do...
		},

		/**
		* toString method
		* @return string
		*/
		toString: function () {
			return 'abbr';
		}

	} );
	
} );

define('horizontalruler/nls/i18n',{
	"root":  {
		"button.addhr.tooltip": "Add a horizontal ruler"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* horizontalruler-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('horizontalruler/horizontalruler-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'ui/ui',
	'ui/button',
	'i18n!horizontalruler/nls/i18n',
	'i18n!aloha/nls/i18n'
], function(Aloha,
            jQuery,
			Plugin,
			Ui,
			Button,
			i18n,
			i18nCore) {
	

	var GENTICS = window.GENTICS;

	return Plugin.create('horizontalruler', {
		_constructor: function(){
			this._super('horizontalruler');
		},
		config: ['hr'],
		init: function() {
			var that = this;

			this._insertHorizontalRuleButton = Ui.adopt("insertHorizontalRule", Button, {
				tooltip: i18n.t('button.addhr.tooltip'),
				iconOnly: true,
				icon: 'aloha-icon-horizontalruler',
				scope: 'Aloha.continuoustext',
				click: function(){
					that.insertHR();
				}
			});

			Aloha.bind( 'aloha-editable-activated', function ( event, rangeObject ) {
				if (Aloha.activeEditable) {
					that.cfg = that.getEditableConfig( Aloha.activeEditable.obj );

					if ( jQuery.inArray( 'hr', that.cfg ) != -1 ) {
						that._insertHorizontalRuleButton.show(true);
		        	} else {
						that._insertHorizontalRuleButton.show(false);
		        		return;
		        	}
				}
			});

		},
		insertHR: function(character) {
			var self = this;
			var range = Aloha.Selection.getRangeObject();
			if(Aloha.activeEditable) {
				var hr = jQuery('<hr>');
				GENTICS.Utils.Dom.insertIntoDOM(
					hr,
					range,
					jQuery(Aloha.activeEditable.obj),
					true
				);
				range.select();
			}
		}
	});

});


/* paste-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 *
 * @overview:
 * The paste plugin intercepts all browser paste events that target aloha
 * editables, and redirects the events into a hidden div.  Once pasting is done
 * into this div, its contents will be processed by registered content handlers
 * before being copied into the active editable, at the current range.
 */
define('paste/paste-plugin',[
	'jquery',
	'aloha/core',
	'aloha/plugin',
	'aloha/command',
	'contenthandler/contenthandler-utils',
	'aloha/console',
	'aloha/copypaste',
	'aloha/contenthandlermanager'
], function (
	$,
	Aloha,
	Plugin,
	Commands,
	ContentHandlerUtils,
	Console,
	CopyPaste,
	ContentHandlerManager
) {
	

	/**
	 * Reference to global window object, for quicker lookup.
	 *
	 * @type {jQuery.<window>}
	 * @const
	 */
	var $WINDOW = $(window);

	/**
	 * Whether or not the user-agent is Internet Explorer.
	 *
	 * @type {boolean}
	 * @const
	 */
	var IS_IE = !!$.browser.msie;

	/**
	 * Matches as string consisting of a single white space character.
	 *
	 * '%A0' is used instead of '&nbsp;' because it seems that IE transforms
	 * non-breaking spaces into atomic tokens.
	 *
	 * @type {RegExp}
	 * @const
	 */
	var PROPPING_SPACE = /^(\s|%A0)$/;

	/**
	 * An invisible editable element used to intercept incoming pasted content
	 * so that it can be processed before being placed into real editables.
	 *
	 * In order to hide the editable div we use clip:rect for WebKit (Chrome,
	 * Safari) and Trident (IE), and width/height for Gecko (FF).
	 *
	 * @type {jQuery.<HTMLElement>}
	 * @const
	 */
	var $CLIPBOARD = $('<div style="position:absolute; ' +
	                   'clip:rect(0px,0px,0px,0px); ' +
	                   'width:1px; height:1px;"></div>').contentEditable(true);

	/**
	 * Stored range, use to accomplish IE hack.
	 *
	 * @type {WrappedRange}
	 */
	var ieRangeBeforePaste = null;

	/**
	 * The window's scroll position at the moment just before pasting is done
	 * (beforepaste and paste events).
	 *
	 * @type {object}
	 * @property {Number} x
	 * @property {Number} y
	 **/
	var scrollPositionBeforePaste = {
		x: 0,
		y: 0
	};

	/**
	 * Set the selection to the given range and focus on the editable in which
	 * the selection is in (if any).
	 *
	 * This function is used to restore the selection to what it was before
	 * calling redirectPaste() at the offset of the pasting process.
	 *
	 * @param {WrappedRange} range The range to restore.
	 */
	function restoreSelection(range) {
		var editable = CopyPaste.getEditableAt(range);
		if (editable) {
			editable.obj.focus();
		}
		CopyPaste.setSelectionAt(range);
		window.scrollTo(
			scrollPositionBeforePaste.x,
			scrollPositionBeforePaste.y
		);
	}

	/**
	 * Redirects a paste event from the given range into a specified target
	 * element.
	 *
	 * This function is used to cause paste events that are targeting to
	 * editables to instead land in an invisible clipboard div that serves as a
	 * staging area to handle the incoming content before actually placing it
	 * into the intended editable.
	 *
	 * @param {WrappedRange} range The range at the time that the paste event
	 *                             was initiated.
	 * @param {jQuery.<HTMLElement>} $target A jQuery object containing the DOM
	 *                                       element to which the paste event
	 *                                       is to be directed to.
	 */
	function redirect(range, $target) {
		var width = 200;
		// Because moving the target element to the current scroll position
		// avoids jittering the viewport when the pasted content moves between
		// where the range is and target.
		$target.css({
			top: $WINDOW.scrollTop(),
			left: $WINDOW.scrollLeft() - width,
			width: width,
			overflow: 'hidden'
		}).contents().remove();

		var from = CopyPaste.getEditableAt(range);
		if (from) {
			from.obj.blur();
		}

		// Because the selection should end up inside the target element.
		CopyPaste.setSelectionAt({
			startContainer: $target[0],
			endContainer: $target[0],
			startOffset: 0,
			endOffset: 0
		});
		$target.focus();
	}

	/**
	 * Detects a situation where paste is about to be done into a selection
	 * beginning inside markup that looks exactly like this:
	 *
	 * '<p> </p>'
	 *
	 * or roughly like this:
	 *
	 * '<p><br/></p>'
	 *
	 * Both markups denote a "propped" paragraph.  A propped paragraph is one
	 * which contains content that has been placed in it for the sole purpose
	 * of forcing the layout engine to render the node visibly.  HTML5 standard
	 * conformance requires that empty block elements like <p> be rendered
	 * invisibly, and comformant browsers like WebKit would place <br> nodes
	 * inside content-editable paragraphs so that they can be visible for
	 * editing.
	 *
	 * IE is _not_ standard comformant however, because it renders empty <p>
	 * with a line-height of 1.  Adding a <br> elements inside it results in
	 * the <p> appearing with 2 lines.
	 *
	 * If we detect this situation, the white space is removed so that after
	 * pasting a new paragraph into the paragraph, it will not be split leaving
	 * an empty paragraph on top of the pasted content.  Therefore when working
	 * in IE, a space is placed inside an empty paragraph rather than a <br>.
	 * Hence markup like '<p> </p>'.
	 *
	 * @param {WrappedRange} range
	 * @return {boolean} True if range starts in propping node.
	 */
	function rangeStartsAtProppedParagraph(range) {
		var start = range.startContainer;
		if (1 === start.nodeType) {
			return ('p' === start.nodeName.toLowerCase() &&
					ContentHandlerUtils.isProppedParagraph(start.outerHTML));
		}
		return (3 === start.nodeType &&
				'p' === start.parentNode.nodeName.toLowerCase() &&
					1 === start.parentNode.childNodes.length &&
						PROPPING_SPACE.test(window.escape(start.data)));
	}

	/**
	 * Prepare the nodes around where pasted content is to land.
	 *
	 * @param {WrappedRange} range
	 */
	function prepRangeForPaste(range) {
		if (rangeStartsAtProppedParagraph(range)) {
			if (3 === range.startContainer.nodeType) {
				range.startContainer.data = '';
			} else {
				range.startContainer.innerHTML = ' ';
			}
			range.startOffset = 0;

			// Because of situations like <p>[ ]</p> or <p>[<br/>]</p>
			if (range.endContainer === range.startContainer) {
				range.endOffset = 0;
			}
		}
	}

	/**
	 * Delete the first match in a string
	 *
	 * @param {String} string String to modify
	 * @param {String} match Match string must be replaced
	 * @returns {string} Original string with the first match replaced.
	 */
	function deleteFirstMatch(string, match) {
		return string.replace(match, '');
	}

	/**
	 * Delete the first Header tag if exists.
	 *
	 * @param htmlString
	 * @returns {XML|string}
	 */
	function deleteFirstHeaderTag(htmlString) {
		var matchFirstHeaderTag = /^<h\d+.*?>/i.exec(htmlString),
		    startHeaderTag,
		    endHeaderTag;

		if (matchFirstHeaderTag === null) {
			return htmlString;
		}

		startHeaderTag = matchFirstHeaderTag[0];
		endHeaderTag = '</' + startHeaderTag.substr(1);

		return deleteFirstMatch(
			deleteFirstMatch(htmlString, startHeaderTag),
			endHeaderTag
		);
	}

	/**
	 * Gets the pasted content and inserts them into the current active
	 * editable.
	 *
	 * @param {jQuery.<HTMLElement>} $clipboard A jQuery object containing an
	 *                                          element holding the copied
	 *                                          content that will be placed at
	 *                                          the given range.
	 * @param {WrappedRange} range The range at which to place the contents
	 *                             from $clipboard.
	 *
	 * @param {function=} callback An optional callback function to call after
	 *                             pasting is completed.
	 */
	function paste($clipboard, range, callback) {
		if (!range) {
			return;
		}

		var content = deleteFirstHeaderTag($clipboard.html());
		var handler = ContentHandlerManager.get('formatless');

		content = handler ? handler.handleContent(content) : content;

		// Because IE inserts an insidious nbsp into the content during pasting
		// that needs to be removed.  Leaving it would otherwise result in an
		// empty paragraph being created right before the pasted content when
		// the pasted content is a paragraph.
		if (IS_IE && /^&nbsp;/.test(content)) {
			content = content.substring(6);
		}

		restoreSelection(range);
		prepRangeForPaste(range);

		if (Aloha.queryCommandSupported('insertHTML')) {
			Aloha.execCommand('insertHTML', false, content);
		} else {
			Console.error(
				'Common.Paste',
				'Command "insertHTML" not available. Enable the plugin "common/commands".'
			);
		}

		$clipboard.contents().remove();

		if (typeof callback === 'function') {
			callback();
		}
	}

	/**
	 * Handles the "paste" event initiating from the $CLIPBOARD element.
	 *
	 * @param {jQuery.Event} $event Event at paste.
	 * @param {WrappedRange} range The range to where to direct the contents
	 *                             of the $CLIPBOARD element.
	 * @param {function=} onInsert Optional callback to be invoked after pasting
	 *                            is completed.
	 */
	function onPaste($event, range, onInsert) {
		// Because we do not want the smartContentChange method to process this
		// event if the metaKey property had been set.
		$event.metaKey = null;
		$event.stopPropagation();

		// Because yeiling here allows for a small execution window to ensure
		// that the pasted content has been inserted into the paste div before
		// we attempt to retrieve it.
		window.setTimeout(function () {
			paste($CLIPBOARD, range, onInsert);
			Aloha.activeEditable.smartContentChange($event);
		}, 10);
	}

	/**
	 * Prepare each editable that is created to handle its paste events via the
	 * invisible paste div.
	 *
	 * Bind appropriate events handlers to the given editable element to be
	 * able to intercept paste events target tot it.
	 *
	 *  TODO: Move to paste command?
	 *  http://support.mozilla.com/en-US/kb/Granting%20JavaScript%20access%20to%20the%20clipboard
	 *  https://code.google.com/p/zeroclipboard/
	 *
	 * @param {jQuery.<HTMLElement>} $editable jQuery object containing an
	 *                                         editable DOM element.
	 * @param {boolean} hasClipboardAccess Whether clipboard access is possible.
	 */
	function prepare($editable, hasClipboardAccess) {
		// FIXME: Because the alternative method, which relies on clipboard
		//        access, leads to incorrect cursor positions after pasting.
		// if (IS_IE && !hasClipboardAccess) {
		if (IS_IE) {
			$editable.bind('beforepaste', function ($event) {
				scrollPositionBeforePaste.x = window.scrollX ||
					document.documentElement.scrollLeft;
				scrollPositionBeforePaste.y = window.scrollY ||
					document.documentElement.scrollTop;

				ieRangeBeforePaste = CopyPaste.getRange();
				redirect(ieRangeBeforePaste, $CLIPBOARD);
				$event.stopPropagation();
			});
		} else {
			$editable.bind('paste', function ($event) {
				scrollPositionBeforePaste.x = window.scrollX ||
					document.documentElement.scrollLeft;
				scrollPositionBeforePaste.y = window.scrollY ||
					document.documentElement.scrollTop;

				var range = CopyPaste.getRange();
				redirect(range, $CLIPBOARD);
				if (IS_IE) {
					var tmpRange = document.selection.createRange();
					tmpRange.execCommand('paste');
				}
				onPaste($event, range);
			});
		}
	}

	return Plugin.create('paste', {

		settings: {},

		init: function () {
			$('body').append($CLIPBOARD);

			var hasClipboardAccess = !this.settings.noclipboardaccess;

			Aloha.bind('aloha-editable-created', function ($event, editable) {
				prepare(editable.obj, hasClipboardAccess);
			});

			// Bind a handler to the paste event of the pasteDiv to get the
			// pasted content (but do this only once, not for every editable)
			// if (IS_IE && !hasClipboardAccess) {
			if (IS_IE) {
				$CLIPBOARD.bind('paste', function ($event) {
					onPaste($event, ieRangeBeforePaste, function () {
						ieRangeBeforePaste = null;
					});
				});
			}
		},

		/**
		 * Register the given paste handler
		 * @deprecated
		 * @param pasteHandler paste handler to be registered
		 */
		register: function (pasteHandler) {
			Console.deprecated('Plugins.Paste', 'register() for pasteHandler' +
			                                    ' is deprecated.  Use the ' +
			                                    'ContentHandler Plugin ' +
			                                    'instead.');
		}
	});
});

/*global define: true */

/* autoparagraph-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * @name autoparagraph
 * @namespace Autoparagraph plugin
 */
define('autoparagraph/autoparagraph-plugin',[
	'jquery',
	'aloha/plugin',
	'aloha/core',
	'util/html',
	'util/dom'
], function (
	$,
	Plugin,
	Aloha,
	Html,
	Dom
) {
	

	/**
	 * Name of this plugin
	 */
	var pluginName = 'autoparagraph';

	/**
	 * Auto-generate missing paragraphs in the given editable, when the editable allows insertion of paragraphs.
	 * If the editable is the currently active one, the current selection will be modified according to the content
	 * changes and the corrected range will be selected.
	 * This means that the visual selection should remain in its original state
	 * 
	 * @param {Editable} editable
	 */
	function autogenerateParagraphs(editable) {
		if (!editable) {
			return;
		}
		var $obj = editable.obj;
		if (!$obj) {
			return;
		}
		var obj = $obj[0], i, j, selectionRange = Aloha.Selection.rangeObject, contentChanged = false;

		// check whether nesting of paragraphs inside the editable is allowed
		if (!Dom.allowsNesting(obj, $('<p></p>')[0])) {
			return;
		}

		// collect lists of subsequent child elements of the editable,
		// that are no block level elements (and thus need to be wrapped
		// into a paragraph)
		var nonBlockRanges = [];
		var current;
		$obj.contents().each(function () {
			if (!Html.isBlock(this)) {
				if (!current) {
					// start a new list
					current = {
						objs: []
					};
					nonBlockRanges.push(current);
				}

				// add the DOM element to the current list
				current.objs.push(this);
			} else {
				// we found a block element, so we are done with the current list
				current = null;
			}
		});

		// wrap all non-block lists into p Tags
		// in other words: replace the list of sibling DOM elements with
		// a single (new) paragraph, that will contain the list of DOM
		// elements as children
		for (i = 0; i < nonBlockRanges.length; i++) {
			var range = nonBlockRanges[i];
			var indexStart = Dom.getIndexInParent(range.objs[0]);
			var indexEnd = Dom.getIndexInParent(range.objs[range.objs.length - 1]);
			var p = $("<p></p>");

			// correct the start of the selection range, if necessary
			if (selectionRange.startContainer === obj) {
				if (selectionRange.startOffset > indexStart && selectionRange.startOffset <= indexEnd) {
					selectionRange.startContainer = p[0];
					selectionRange.startOffset -= indexStart;
				} else if (selectionRange.startOffset > indexEnd) {
					selectionRange.startOffset -= (indexEnd - indexStart);
				}
			}
			// correct the end of the selection range, if necessary
			if (selectionRange.endContainer === obj) {
				if (selectionRange.endOffset > indexStart && selectionRange.endOffset <= indexEnd) {
					selectionRange.endContainer = p[0];
					selectionRange.endOffset -= indexStart;
				} else if (selectionRange.endOffset > indexEnd) {
					selectionRange.endOffset -= (indexEnd - indexStart);
				}
			}

			// insert the paragraph right before the old dom elements
			$(range.objs[0]).before(p);
			// move all old dom elements into the paragraph
			for (j = 0; j < range.objs.length; j++) {
				p[0].appendChild(range.objs[j]);
			}
			contentChanged = true;
		}

		// select the corrected selection, but only if we changed
		// something in the content and the editable is the active one
		if (contentChanged && editable.isActive) {
			selectionRange.select();
		}
	}

	/**
	 * Checks whether or not pluginName is activated for an editable.
	 *
	 * @param {object} The plugin/editable configuration.
	 * @return {boolean} True if activated.
	 */
	function isPluginActivated(config) {
		return (
			$.type(config) === 'array' && $.inArray(pluginName, config) !== -1
		);
	}

	/**
	 * @type {Aloha.Plugin}
	 */
	var autoParagraphPlugin = Plugin.create(pluginName, {
		/**
		 * Default config: plugin active for all editables
		 */
		config: [pluginName],

		/**
		 * Initialize the plugin
		 */
		init: function () {
			var plugin = this;
			// autogenerate paragraphs when a new editable is created
			Aloha.bind('aloha-editable-created', function (event, editable) {
				var config = plugin.getEditableConfig(editable.obj);
				if (isPluginActivated(config)) {
					autogenerateParagraphs(editable);
				}
			});

			// autogenerate paragraphs upon smart content change
			Aloha.bind('aloha-smart-content-changed', function (event, data) {
				var config = plugin.getEditableConfig(data.editable.obj);
				if (isPluginActivated(config)) {
					autogenerateParagraphs(data.editable);
				}
			});
		}
	});

	return autoParagraphPlugin;
});

define('toc/nls/i18n',{
	"root":  {
		"button.addtoc.tooltip": "Table of contents"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* toc-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('toc/toc-plugin',[
	'aloha', 
	'aloha/plugin', 
	'jquery', 
	'ui/ui', 
	'ui/button',
	'i18n!toc/nls/i18n', 
	'i18n!aloha/nls/i18n', 
	'aloha/console'
], function(
	Aloha,
    Plugin,
	jQuery,
	Ui,
	Button,
	i18n,
	i18nCore,
	console
) {
	

	var GENTICS = window.GENTICS,
	    namespace = 'toc',
	    $containers = null,
	    allTocs = [];

	/* helper functions */
	function last(a) { return a[a.length - 1]; }
	function head(a) { return a[0]; }
	function tail(a) { return a.slice(1); }
	function indexOf(a, item) {
		return detect(a, function(cmp){
			return cmp === item;
		});
	}
	function detect(a, f) {
		for (var i = 0; i < a.length; i++) {
			if (f(a[i])) {
				return a[i];
			}
		}
		return null;
	}
	function map(a, f) {
		var result = [];
		for (var i = 0; i < a.length; i++) {
			result.push(f(a[i]));
		}
		return result;
	}
	function each(a, f) {
		map(a, f);
	}

	/**
	 * register the plugin with unique name
	 */
	return Plugin.create(namespace, {
		minEntries: 0,
		updateInterval: 5000,
		config: ['toc'],

		init: function () {
			var that = this;

			if ( typeof this.settings.minEntries === 'undefined' ) {
				this.settings.minEntries = this.minEntries;
			}
			if ( typeof this.settings.updateInterval === 'undefined' ) {
				this.settings.updateInterval = this.updateInterval;
			}

			Aloha.bind( 'aloha-editable-activated', function ( event, rangeObject ) {
				if (Aloha.activeEditable) {
					that.cfg = that.getEditableConfig( Aloha.activeEditable.obj );

					if ( jQuery.inArray( 'toc', that.cfg ) != -1 ) {
						that._insertTocButton.show(true);
		        	} else {
						that._insertTocButton.show(false);
		        		return;
		        	}
				}
			});

	        this.initButtons();
			jQuery(document).ready(function(){
				that.spawn();
			});
			
			
	    },
	
		initButtons: function () {
			var that = this;
			
			this._insertTocButton = Ui.adopt("insertToc", Button, {
		        tooltip: i18n.t('button.addtoc.tooltip'),
		        icon: 'aloha-icon aloha-icon-orderedlist',
				scope: 'Aloha.continuoustext',
		        click: function () { that.insertAtSelection($containers); }
			});
	    },
	
		register: function ($c) {
			$containers = $c;
		},
	
	    /**
	     * inserts a new TOC at the current selection
	     */
	    insertAtSelection: function($containers){
		    $containers = $containers || editableContainers();
			var id = generateId('toc');

	        // we start out with an empty ordered list
	        var $tocElement = jQuery("<ol class='toc_root'></ol>").
				attr('id', id).attr('contentEditable', 'false');
		    var range = Aloha.Selection.getRangeObject();
	        var tocEditable = Aloha.activeEditable;
	        var $tocContainer = jQuery(document.getElementById(tocEditable.getId()));
		    GENTICS.Utils.Dom.insertIntoDOM($tocElement, range, $tocContainer);

		    this.create(id).register($containers).update().tickTock();
	    },
	
		/**
		 * Spawn containers for all ols with the toc_root class.
		 */
		spawn: function ($ctx, $containers) {

			$ctx        = $ctx        || jQuery('body');
			$containers = $containers || editableContainers();
			$ctx.find('ol.toc_root').each(function(){
				var id = jQuery(this).attr('id');
				if (!id) {
					id = generateId('toc');
					jQuery(this).attr('id', id);
				}
				that.create(id).register($containers).tickTock();
			});
		},

	    create: function (id) {
			allTocs.push(this);
			
	        return {
	            'id': id,
	            '$containers': jQuery(),
				'settings': this.settings,
	        /**
	         * find the TOC root element for this instance
	         */
	        root: function(){
	            return jQuery(document.getElementById(this.id));
	        },
	        /**
	         * registers the given containers with the TOC. a
	         * container is an element that may begin or contain
	         * sections. Note: use .live on all [contenteditable=true]
			 * to catch dynamically added editables.
			 * the same containers can be passed in multiple times. they will
			 * be registered only once.
	         */
	        register: function ($containers){
				var self = this;
				// the .add() method ensures that the $containers will be in
				// document order (required for correct TOC order)
				
	            self.$containers = self.$containers.add($containers);
	            self.$containers.filter(function(){
					return !jQuery(this).data(namespace + '.' + self.id + '.listening');
				}).each(function(){
		            var $container = jQuery(this);
					$container.data(namespace + '.' + self.id + '.listening', true);
		            $container.bind('blur', function(){
				        self.cleanupIds($container.get(0));
				        self.update($container);
		            });
	            });
				return self;
	        },
			tickTock: function (interval) {
				var self = this;
				interval = interval || this.settings.updateInterval;
				if (!interval) {
					return;
				}
				window.setInterval(function(){
					self.register(editableContainers());
					// TODO: use the active editable instead of rebuilding
					// the entire TOC
					self.update();
				}, interval);
				return self;
			},
	        /**
	         * there are various ways which can cause duplicate ids on targets
	         * (e.g. pressing enter in a heading and writing in a new line, or
	         * copy&pasting). Passing a ctx updates only those elements
	         * either inside or equal to it.
	         * TODO: to be correct this should do
	         *  a $.contains(documentElement...
	         */
	        cleanupIds: function (ctx) {
	            var ids = [],
					that = this;
	            this.headings(this.$containers).each(function(){
	                var id = jQuery(this).attr('id');
	                if (   (id && -1 != jQuery.inArray(id, ids))
	                    || (   ctx
	                        && (jQuery.contains(ctx, this) || ctx === this)))
	                {
	                    jQuery(this).attr('id', generateId(this));
	                }
	                ids.push(id);
	            });
				return this;
	        },
	        /**
	         * Updates the TOC from the sections in the given context, or in
	         * all containers that have been registered with this TOC, if no
	         * context is given.
	         */
	        update: function ($ctx) {
	            var self = this;
	            $ctx = $ctx || self.$containers;
	            var outline = this.outline(self.$containers);
	            var ancestors = [self.root()];
	            var prevSiblings = [];
				//TODO: handle TOC rebuilding more intelligently. currently,
				//the TOC is always rebuilt from scratch.
				last(ancestors).empty();
	            (function descend(outline) {
					var prevSiblings = [];
	                each(outline, function (node) {
			            var $section = head(node);
	                    var $entry = self.linkSection($section, ancestors, prevSiblings);
	                    ancestors.push($entry);
	                    descend(tail(node));
	                    ancestors.pop();
	                    prevSiblings.push($entry);
	                });
	            })(tail(outline));

	            // count number of li's in the TOC, if less than minEntries, hide the TOC
	            var minEntries = self.root().attr('data-TOC-minEntries') || this.settings.minEntries;
	            if (self.root().find('li').length >= minEntries) {
	            	self.root().show();
	            } else {
	            	self.root().hide();
	            }

				return this;
	        },
	        /**
	         * updates or creates an entry in the TOC for the given section.
	         */
	        linkSection: function ($section, ancestors, prevSiblings) {
	            var linkId = $section.eq(0).attr('id');
	            if (!linkId) {
	                linkId = generateId($section.get(0));
	                $section.eq(0).attr('id', linkId);
	            }
				var $root = this.root();
	            var $entry = anchorFromLinkId($root, linkId);
				if (!$entry.length) {
					$entry = jQuery('<li><a/></li>');
				}
	            $entry.find('a').
	                attr('href', '#' + linkId).
	                text($section.eq(0).text());
				if (last(prevSiblings)) {
					last(prevSiblings).after($entry);
				}
				else {
					if (last(ancestors).get(0) == $root.get(0)) {
						$root.append($entry);
					}
					else {
						var $subToc = jQuery('<ol/>').append($entry);
						last(ancestors).append($subToc);
					}
				}
	            return $entry;
	        },

		    /**
		     * returns a tree of sections in the given context. if the context
		     * element(s) begin a section, they will be included. First element
		     * of each branch in the tree is a $(section) or $() for the
		     * root node.
		     * TODO: http://www.w3.org/TR/html5/sections.html#outline
		     */
		    outline: function (ctx) {
		        var rootNode = [jQuery()];
		        var potentialParents = [rootNode];
		        this.headings(ctx).each(function(){
		            var $heading = jQuery(this);
		            var nodeName = this.nodeName.toLowerCase();
		            var hLevels = ['h6', 'h5', 'h4', 'h3', 'h2', 'h1'];
		            var currLevel = jQuery.inArray(nodeName, hLevels);
		            var higherEq = hLevels.slice(currLevel).join(',');
		            var $section = $heading.nextUntil(higherEq).andSelf();
		            var node = [$section];

		            var parent = detect(potentialParents, function (parent) {
		                var parentSection = parent[0];
		                return !parentSection.length || //top-level contains everything
		                    detect(parentSection, function (sectionElem) {
		                        return $heading.get(0) === sectionElem ||
								       jQuery.contains(sectionElem, $heading.get(0));
			                });
		            });
		            parent.push(node);
		            potentialParents.splice(0, indexOf(potentialParents, parent), node);
		        });
		        return rootNode;
		    },

		    headings: function ($ctx) {
			    return $ctx.find(':header').add($ctx.filter(':header'));
		    }



		}
		}

	});
	
	//-------------- module methods -----------------

    function editableContainers () {
	    return jQuery(map(Aloha.editables, function (editable) {
				    return document.getElementById(editable.getId());
			    }));
    }

    function anchorFromLinkId ($ctx, linkId) {
        return linkId ? $ctx.find('a[href $= "#' + linkId + '"]') : jQuery();
    }

    function linkIdFromAnchor ($anchor){
        var href = $anchor.attr('href');
        return href ? href.match(/#(.*?)$/)[1] : null;
    }

	function generateId (elemOrText) {
	    var validId;
	    if (typeof elemOrText == "object") {
	        validId = jQuery(elemOrText).text().
	            replace(/[^a-zA-Z-]+/g, '-').
	            replace(/^[^a-zA-Z]+/, '');
	    } else if (elemOrText) {
	        validId = elemOrText;
	    }
	    for (var uniquifier = 0;; uniquifier++) {
	        var uniqueId = validId;
	        if (uniquifier) {
	            uniqueId += '-' + uniquifier;
	        }
	        var conflict = document.getElementById(uniqueId);
	        if (   !conflict
	            || (   typeof elemOrText == "object"
	                && conflict === elemOrText))
	        {
				return uniqueId;
	        }
	    }
		//unreachable
	}
});

define('cite/nls/i18n',{
	"root":  {
		"cite.button.add.quote": "Format selection as quote",
		"cite.button.add.blockquote": "Format selection as blockquote"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/*global window: true define: true */
/*!
* Aloha Editor
* Author & Copyright (c) 2010 Gentics Software GmbH
* aloha-sales@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*/

define('cite/cite-plugin',[
    'aloha',
	'jquery',
	'aloha/plugin',
	'ui/ui',
	'ui/toggleButton',
	'format/format-plugin',
	'util/dom',
	'PubSub',
	'i18n!cite/nls/i18n',
	'i18n!aloha/nls/i18n'
], function (
	Aloha,
	jQuery,
	Plugin,
	Ui,
	ToggleButton,
	Format,
	domUtils,
	PubSub,
    i18n,
	i18nCore
) {
	

	var $ = jQuery,
		ns  = 'aloha-cite',
		uid = (new Date()).getTime();

	// namespaced classnames
	var nsClasses = {
		quote         : nsClass('quote'),
		blockquote    : nsClass('blockquote'),
		'panel-label' : nsClass('panel-label'),
		'panel-field' : nsClass('panel-field'),
		'panel-btns'  : nsClass('panel-btns'),
		'link-field'  : nsClass('link-field'),
		'note-field'  : nsClass('note-field'),
		references    : nsClass('references')
	};

	/**
	 * Simple templating
	 *
	 * @param {String} str - The string containing placeholder keys in curly
	 *                       brackets
	 * @param {Object} obj - Associative array of replacing placeholder keys
	 *                       with corresponding values
	 */
	function supplant(str, obj) {
		return str.replace(/\{([a-z0-9\-\_]+)\}/ig,
			function (str, p1, offset, s) {
				var replacement = obj[p1] || str;
				return (typeof replacement === 'function') ?
					replacement() :
					replacement;
			});
	}

	/**
	 * Wrapper to call the supplant method on a given string, taking the
	 * nsClasses object as the associative array containing the replacement
	 * pairs
	 *
	 * @param {String} str
	 * @return {String}
	 */
	function renderTemplate(str) {
		return (typeof str === 'string') ? supplant(str, nsClasses) : str;
	}

	/**
	 * Generates a selector string with this plugins's namespace prefixed the
	 * each classname.
	 *
	 * Usage:
	 *    nsSel('header,', 'main,', 'foooter ul')
	 *    will return
	 *    ".aloha-myplugin-header, .aloha-myplugin-main, .aloha-mypluzgin-footer ul"
	 *
	 * @return {string}
	 */
	function nsSel() {
		var strBldr = [], prx = ns;
		jQuery.each(arguments, function () {
			strBldr.push('.' + ('' === this ? prx : prx + '-' + this));
		});
		return jQuery.trim(strBldr.join(' '));
	}

	/**
	 * Generates a string with this plugins's namespace prefixed the each
	 * classname.
	 *
	 * Usage:
	 *		nsClass('header', 'innerheaderdiv')
	 *		will return
	 *		"aloha-myplugin-header aloha-myplugin-innerheaderdiv"
	 *
	 * @return {string}
	 */
	function nsClass() {
		var strBldr = [], prx = ns;
		jQuery.each(arguments, function () {
			strBldr.push('' === this ? prx : prx + '-' + this);
		});
		return jQuery.trim(strBldr.join(' '));
	}

	/**
	 * Coverts hexidecimal string #00ffcc into rgb array [0, 255, 127]
	 *
	 * @param {string} hex Hexidecimal string representing color. In the form
	 *					   #ff3344 or #f34 or f34.
	 * @return {Array.<number>} RGB representation of hexidecimal color.
	 */
	function hex2rgb(hex) {
		hex = hex.replace('#', '').split('');
		if (3 === hex.length) {
			hex[5] = hex[4] = hex[2];
			hex[3] = hex[2] = hex[1];
			hex[1] = hex[0];
		}
		var rgb = [];
		var i;
		for (i = 0; i < 3; ++i) {
			rgb[i] = parseInt('0x' + hex[i * 2] + hex[i * 2 + 1], 16);
		}
		return rgb;
	}

	return Plugin.create('cite', {

		citations: [],
		referenceContainer: null,
		settings: null,
		sidebar: null,
		config: ['quote', 'blockquote'],

		init: function () {
			var that = this;

			// Harverst configuration options that may be defined outside of
			// the plugin.
			if (Aloha.settings && Aloha.settings.plugins && Aloha.settings.plugins.cite) {

				var referenceContainer = jQuery(Aloha.settings.plugins.cite.referenceContainer);

				if (referenceContainer.length) {
					that.referenceContainer = referenceContainer;
				}

				if (typeof Aloha.settings.plugins.cite !== 'undefined') {
					that.settings = Aloha.settings.plugins.cite;
				}

				if (typeof that.settings.sidebar === 'undefined') {
					that.settings.sidebar = {};
				}

				if (typeof that.settings.sidebar.open === 'undefined') {
					that.settings.sidebar.open = true;
				}

				// be tolerant about the setting: 'false' and '0' (as strings) will be interpreted as false (boolean)
				if (typeof that.settings.sidebar.open === 'string') {
					that.settings.sidebar.open = that.settings.sidebar.open.toLowerCase();
					if (that.settings.sidebar.open === 'false' || that.settings.sidebar.open === '0') {
						// disable button only if 'false' or '0' is specified
						that.settings.sidebar.open = false;
					} else {
						// otherwise the button will always be shown
						that.settings.sidebar.open = true;
					}
				}
			}

			this._quoteButton = Ui.adopt('quote', ToggleButton, {
				tooltip: i18n.t('cite.button.add.quote'),
				icon: nsClass('button', 'inline-button'),
				scope: 'Aloha.continuoustext',
				click: function () {
					that.addInlineQuote();
				}
			});

			// We brute-forcishly push our button settings into the
			// multiSplitButton. The multiSplitButton will pick it up
			// and render it.
			Format.multiSplitButton.pushItem({
				name: 'blockquote',
				tooltip: i18n.t('cite.button.add.blockquote'),
				icon: nsClass('button', 'block-button'),
				click: function () {
					that.addBlockQuote();
				}
			});

			var citePlugin = this;

			// Note that if the sidebar is not loaded,
			// aloha-sidebar-initialized will not fire and this listener will
			// not be called, which is what we would want if there are no
			// sidebars
			Aloha.ready(function (ev) {
				citePlugin.sidebar = Aloha.Sidebar.right.show();
				// citePlugin.sidebar.settings.overlayPage = false;
				citePlugin.sidebar.addPanel({
					id       : nsClass('sidebar-panel'),
					title    : 'Citation',
					content  : '',
					expanded : true,
					activeOn : 'q, blockquote',

					// Executed once, when this panel object is instantialized
					onInit   : function () {
						var that = this;
						var additionalReferenceContainer = '';
						
						if (citePlugin.referenceContainer) {
							additionalReferenceContainer = '<label class="{panel-label}" for="{note-field}-textarea">Note</label> ' +
															'<div class="{panel-field} {note-field}" ' +
															'style="margin: 5px;">' +
															'<textarea id="{note-field}-textarea"></textarea></div>';
						}
						
						var content = this.setContent(renderTemplate(
								'<label class="{panel-label}" for="{link-field}-input">Link</label>' +
								'<div class="{panel-field} {link-field}" ' + 
								'style="margin: 5px;"><input type="text" id="{link-field}-input" /></div>' +
								additionalReferenceContainer
							)).content;

						content.find('input, textarea')
							.bind('keypress change', function () {
								citePlugin.addCiteDetails(
									that.content.attr('data-cite-id'),
									that.content.find(nsSel('link-field input')).val(),
									that.content.find(nsSel('note-field textarea')).val()
								);
							});
					},

					/**
					 * Invoked during aloha-selection-changed, if activeOn
					 * function returns true for the current selection.  Will
					 * populate panel fields with the details of the selected
					 * citation if they are already available.  If no citation
					 * exists for the selected quotation, then one will be
					 * created for it first.
					 */
					onActivate: function (effective) {
						var activeUid = effective.attr('data-cite-id');
						if (!activeUid) {
							activeUid = ++uid;
							var classes = [nsClass('wrapper')].join(' ');
							effective.addClass(classes);
							effective.attr('data-cite-id', activeUid);
						}
						var index = that.getIndexOfCitation(activeUid);

						if (-1 === index) {
							index = that.citations.push({
								uid   : activeUid,
								link  : null,
								notes : null
							}) - 1;
						}

						this.content.attr('data-cite-id', activeUid);
						this.content.find(nsSel('link-field input'))
						    .val(effective.attr('cite'));
						this.content.find(nsSel('note-field textarea'))
						    .val(that.citations[index].note);
					}

				});
			});

			Aloha.bind('aloha-editable-activated', function (event, params) {
				var config = that.getEditableConfig(params.editable.obj);

				if (!config) {
					return;
				}
				
				if ( jQuery.inArray('quote', config ) !== -1 ) {
					that._quoteButton.show(true);
				} else {
					that._quoteButton.show(false);
				}
				
				if ( jQuery.inArray( 'blockquote', config ) !== -1 ) {
					Format.multiSplitButton.showItem('blockquote');
				} else {
					Format.multiSplitButton.hideItem('blockquote');
				}
				
			});

			PubSub.sub('aloha.selection.context-change', function (message) {
				var rangeObject = message.range;

				// Set to false to prevent multiple buttons being active
				// when they should not.
				var quoteFound = false, blockquoteFound = false;
				var nodeName;
				var effective = rangeObject.markupEffectiveAtStart;
				var i = effective.length;

				// Check whether any of the effective items are citation
				// tags.
				while (i) {
					nodeName = effective[--i].nodeName;
					if (nodeName === 'Q') {
						quoteFound = true;
					} else if (nodeName === 'BLOCKQUOTE') {
						blockquoteFound = true;
					}
				}

				// set the toggle status for the quote button
				that._quoteButton.setState(quoteFound);

				// activate the blockquote multisplit item
				if (blockquoteFound) {
					Format.multiSplitButton.setActiveItem('blockquote');
				}
				
				// switch item visibility according to config
				var config = [];
				if (Aloha.activeEditable) {
					config = that.getEditableConfig(Aloha.activeEditable.obj);
				}

				// quote
				if (jQuery.inArray('quote', config) !== -1) {
					that._quoteButton.show(true);
				} else {
					that._quoteButton.show(false);
				}

				// blockquote
				if (jQuery.inArray('blockquote', config) !== -1) {
					Format.multiSplitButton.showItem('blockquote');
				} else {
					Format.multiSplitButton.hideItem('blockquote');
				}
			});
		},

		/**
		 * Do a binary search through all citations for a given uid.  The bit
		 * shifting may be a *bit* of an overkill, but with big lists it proves
		 * to be significantly more performant.
		 *
		 * @param {string} uid Th uid of the citation to retreive.
		 * @return {number} The 0-based index of the first citation found that
		 *                  matches the given uid. -1 of no citation is found
		 *                  for the given uid,
		 */
		getIndexOfCitation: function (uid) {
			var c = this.citations;
			var max = c.length;
			var min = 0;
			var mid;
			var cuid;

			// Infinite loop guard for debugging...  So your tab/browser
			// doesn't freeze up like a Christmas turkey ;-)
			// var __guard = 1000;

			while (min < max /* && --__guard */ ) {
				mid = (min + max) >> 1; // Math.floor(i) / 2 == i >> 1 == ~~(i / 2)
				cuid = c[mid].uid;

				// Don't do strict comparison here or you'll get an endless loop
				if (cuid == uid) {
					return mid;
				}
				
				if (cuid > uid) {
					max = mid;
				} else if (cuid < uid) {
					min = mid + 1;
				}
			}

			return -1;
		},

		addBlockQuote: function () {
			var classes = [nsClass('wrapper'), nsClass(++uid)].join(' ');

			var markup = jQuery(supplant(
					'<blockquote class="{classes}" data-cite-id="{uid}"></blockquote>',
					{uid: uid, classes: classes}
			));

			// Now re-enable the editable...
			if (Aloha.activeEditable) {
				jQuery(Aloha.activeEditable.obj[0]).click();
			}

			Aloha.Selection.changeMarkupOnSelection(markup);

			if (this.referenceContainer) {
				this.addCiteToReferences(uid);
			}

			if (this.sidebar && this.settings && this.settings.sidebar &&
			     this.settings.sidebar.open) {
				this.sidebar.open();
			}
			//	.activatePanel(nsClass('sidebar-panel'), markup);
		},

		addInlineQuote: function () {
			var classes = [nsClass('wrapper'), nsClass(++uid)].join(' ');
			
			var markup = jQuery(supplant(
					'<q class="{classes}" data-cite-id="{uid}"></q>',
					{ uid: uid, classes: classes }
			));

			var rangeObject = Aloha.Selection.rangeObject;
			var foundMarkup;

			if (Aloha.activeEditable) {
				jQuery(Aloha.activeEditable.obj[0]).click();
			}

			// Check whether the markup is found in the range (at the start of
			// the range).
			foundMarkup = rangeObject.findMarkup(function () {
				if (this.nodeName && markup.length &&
					(typeof this.nodeName === 'string') &&
					(typeof markup[0].nodeName === 'string')) {
					return this.nodeName.toLowerCase() ===
						markup[0].nodeName.toLowerCase();
				}

				return false;
			}, Aloha.activeEditable.obj);

			// If the we click the quote button on a range that contains quote
			// markup, then we will remove the quote markup, otherwise we will
			// wrap the selection in a quote.

			if (foundMarkup) {
				if (rangeObject.isCollapsed()) {
					// The range is collapsed; remove exactly the one DOM
					// element.
					domUtils.removeFromDOM(foundMarkup, rangeObject, true);
				} else {
					// The range is not collapsed; remove the markup from the
					// range.
					domUtils.removeMarkup(rangeObject, markup,
						Aloha.activeEditable.obj);
				}
			} else {
				// When the range is collapsed, extend it to a word.
				if (rangeObject.isCollapsed()) {
					domUtils.extendToWord(rangeObject);
				}

				domUtils.addMarkup(rangeObject, markup);
			}

			// select the modified range
			rangeObject.select();

			if (this.referenceContainer) {
				this.addCiteToReferences(uid);
			}

			if (this.sidebar && this.settings && this.settings.sidebar &&
			     this.settings.sidebar.open) {
				this.sidebar.open();
			}

			//	.activatePanel(nsClass('sidebar-panel'), markup);

			return false;
		},

		/**
		 * Adds an item for the citation matching the given uid to the
		 * references list. If no OL list for references exist, we create one.
		 * This method will assume that this.referenceContainer is a jQuery
		 * object container into which the references list should be built.
		 *
		 * @param {string} uid The uid of the citation to add.
		 */
		addCiteToReferences: function (uid) {
			var index = this.getIndexOfCitation(uid);

			if (-1 === index) {
				return;
			}

			var wrapper = jQuery('.aloha-editable-active ' + nsSel(uid));
			var note = 'cite-note-' + uid;
			var ref = 'cite-ref-'  + uid;

			wrapper.append(
				supplant(
					'<sup id="{ref}" contenteditable="false"><a href="#{note}">[{count}]</a></sup>',
					{ ref   : ref, note  : note, count : index + 1 }
				)
			);

			if (0 === this.referenceContainer.find('ol.references').length) {
				this.referenceContainer
				    .append('<h2>References</h2>')
				    .append('<ol class="references"></ol>');
			}

			this.referenceContainer.find('ol.references').append(
				supplant(
					'<li id="{note}"><a href="#{ref}">^</a> &nbsp; <span></span></li>',
					{ ref  : ref, note : note }
				)
			);
		},

		/**
		 * Responsible for updating the citation reference in memory, and in
		 * the references list when a user adds or changes information for a
		 * given citation.
		 *
		 * @param {string} uid
		 * @param {string} link
		 * @param {string} note
		 */
		addCiteDetails: function (uid, link, note) {
			this.citations[this.getIndexOfCitation(uid)] = {
				uid  : uid,
				link : link,
				note : note
			};

			if (link) {
				// Update link attribute
				var el = jQuery(nsSel(uid)).attr('cite', link);
			}

			// Update information in references list for this citation.
			if (this.referenceContainer) {
				jQuery('li#cite-note-' + uid + ' span').html(
					supplant(
						link ? '<a class="external" target="_blank" href="{url}">{url}</a>' : '',
						{ url: link }
					) + (note ? '. ' + note : '')
				);
			}
		},

		toString: function () {
			return 'aloha-citiation-plugin';
		},
		
		/**
		 * Make the given jQuery object (representing an editable) clean for saving
		 * Find all quotes and remove editing objects
		 * @param obj jQuery object to make clean
		 * @return void
		 */
		makeClean: function (obj) {

			// find all quotes
			obj.find('q, blockquote').each(function () {
				// Remove empty class attributes
				if (jQuery.trim(jQuery(this).attr('class')) === '') {
					jQuery(this).removeAttr('class');
				}
				// Only remove the data cite attribute when no reference container was set
				if (!this.referenceContainer) {
					jQuery(this).removeClass('aloha-cite-' + jQuery(this).attr('data-cite-id'));
			
					// We need to read this attribute for IE7. Otherwise it will
					// crash when the attribute gets removed. In IE7 this removal 
					// does not work at all. (no wonders here.. :.( )
					if (jQuery(this).attr('data-cite-id') != null) {
						jQuery(this).removeAttr('data-cite-id');
					}
				}
				
				jQuery(this).removeClass('aloha-cite-wrapper');
				
			});
		}

	});

});

/* flag-icons-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/**
 * Provides flag icons that are shared among various plugins.  Plugins that
 * rely on the icon images provided by this plugin should require this plugin
 * to ensure that the images are indeed there.
 *
 * This plugin's require module exports an object containing a single property:
 * the url of the plugins.  This url can be used as the path to 
 * programmatically determine the absolute urls of the icon images.
 *
 * USAGE:
 * require('flag-icons/flag-icons-plugin', function (FlagsIcons) {
 *	   // ...
 *     FlagIcons.path + languageCode;
 *     // ...
 * });
 */
define('flag-icons/flag-icons-plugin',[], function () {
	'use strict'
	return { path: Aloha.getPluginUrl( 'flag-icons' ) };
} );

define('numerated-headers/nls/i18n',{
	"root":  {
		"headerids.label.target": "Target",
		"headerids.button.reset": "Reset",
		"headerids.button.set": "Set",
		"button.numeratedHeaders.tooltip": "Toggle header numeration"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* numerated-headers-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('numerated-headers/numerated-headers-plugin',[
	'aloha/core',
	'jquery',
	'aloha/plugin',
	'ui/ui',
	'ui/toggleButton',
	'i18n!numerated-headers/nls/i18n',
	'i18n!aloha/nls/i18n'
], function (
	Aloha,
	$,
	Plugin,
	Ui,
	ToggleButton,
	i18n,
	i18nCore
) {
	

	/**
	 * A cache of editable configuration.
	 * @private
	 * @type {object<string, object>}
	 */
	var editableConfigurations = {};

	Aloha.bind('aloha-editable-destroyed', function (event, editable) {
		delete editableConfigurations[editable.getId()];
	});

	return Plugin.create('numerated-headers', {
		config: {
			numeratedactive: true,
			headingselector: 'h1, h2, h3, h4, h5, h6',
			trailingdot: false
		},

		/**
		 * Initialize the plugin.
		 */
		init: function () {
			var that = this;

			this._formatNumeratedHeadersButton = Ui.adopt('formatNumeratedHeaders',
				ToggleButton, {
					tooltip: i18n.t('button.numeratedHeaders.tooltip'),
					icon: 'aloha-icon aloha-icon-numerated-headers',
					scope: 'Aloha.continuoustext',
					click: function () {
						var buttonPressed = that._formatNumeratedHeadersButton.getState();
						if (!buttonPressed) {
							that.removeNumerations();
						} else {
							that.createNumeratedHeaders();
						}
					}
				});


			// We need to bind to smart-content-changed event to recognize
			// backspace and delete interactions.
			Aloha.bind('aloha-smart-content-changed', function (event) {
				that.cleanNumerations();
				if (that.showNumbers()) {
					that.createNumeratedHeaders();
				}
			});
			
			// We need to listen to that event, when a block is formatted to
			// header format. smart-content-changed would be not fired in 
			// that case
			Aloha.bind('aloha-format-block', function () {
				that.cleanNumerations();
				if (that.showNumbers()) {
					that.createNumeratedHeaders();
				}
			});

			Aloha.bind('aloha-editable-activated', function (event) {
				if (that.isNumeratingOn()) {
					that._formatNumeratedHeadersButton.show();
					that.initForEditable(Aloha.activeEditable.obj);
				} else {
					that._formatNumeratedHeadersButton.hide();
				}
			});
		},

		/**
		 * Init the toggle button (and numerating) for the current editable,
		 * if not yet done.
		 * If numerating shall be on by default and was not turned on, numbers
		 * will be created.
		 */
		initForEditable: function ($editable) {
			var flag = $editable.attr('aloha-numerated-headers');
			if (flag !== 'true' && flag !== 'false') {
				flag = (true === this.getCurrentConfig().numeratedactive) ? 'true' : 'false';
				$editable.attr('aloha-numerated-headers', flag);
			}

			if (flag === 'true') {
				this.createNumeratedHeaders();
				this._formatNumeratedHeadersButton.setState(true);
			} else {
				this._formatNumeratedHeadersButton.setState(false);
			}
		},

		/**
		 * Get the config for the current editable
		 */
		getCurrentConfig: function () {
			var config = this.getEditableConfig(Aloha.activeEditable.obj);

			// normalize config (set default values)
			if (config.numeratedactive === true || config.numeratedactive === 'true' || config.numeratedactive === '1') {
				config.numeratedactive = true;
			} else {
				config.numeratedactive = false;
			}

			if (typeof config.headingselector !== 'string') {
				config.headingselector = 'h1, h2, h3, h4, h5, h6';
			}
			config.headingselector = $.trim(config.headingselector);

			if (config.trailingdot === true || config.trailingdot === 'true' || config.trailingdot === '1') {
				config.trailingdot = true;
			} else {
				config.trailingdot = false;
			}

			return config;
		},

		/**
		 * Check whether numerating shall be possible in the current editable
		 */
		isNumeratingOn: function () {
			return this.getCurrentConfig().headingselector !== '';
		},

		/**
		 * Check whether numbers shall currently be shown in the current
		 * editable.
		 */
		showNumbers: function () {
			return (
				Aloha.activeEditable &&
				this.isNumeratingOn() &&
				(Aloha.activeEditable.obj.attr('aloha-numerated-headers') === 'true')
			);
		},
		
		/**
		 * Remove all annotations in the current editable.
		 */
		cleanNumerations: function () {
			var that = this;
			var active_editable_obj = this.getBaseElement();
			if (!active_editable_obj) {
				return;
			}
			this._saveRemoveAnnotations($(active_editable_obj).find('span[role=annotation]'));
		},
		
		/**
		 * Savely removes a jQuery collection of annotations.
		 * @param annotationcollection the collection of annotations.
		 */
		_saveRemoveAnnotations: function (annotationcollection) {
			var that = this;
			var range = Aloha.Selection.getRangeObject();
			var rangemod = false;
			annotationcollection.each(function () {
				if (range.startContainer === this || $.inArray(this, $(range.startContainer).parents()) > -1) {
			        range.startContainer = that._prevNode(this);
			        range.startOffset = 0;
			        rangemod = true;
				}
				if (range.startContainer === this.parentNode && range.startOffset >= $(this).index() && range.startOffset > 0) {
					range.startOffset --;
					rangemod = true;
				}
				//Check if the selection ends inside the annotation
				if (range.endContainer === this || $.inArray(this, $(range.endContainer).parents()) > -1) {
					range.endContainer = that._prevNode(this);
					range.endOffset = 0;
					rangemod = true;
				}
				if (range.endContainer === this.parentNode && range.endOffset >= $(this).index() && range.endOffset > 0) {
					range.endOffset --;
					rangemod = true;
				}
				$(this).remove();
			});
			if (rangemod === true) {
				range.update();
				range.select();
			}
		},
		
		/**
		 * Prepends the annotation to the given prependElement.
		 */
		_prependAnnotation: function (annotationcontent, prependElem) {
			var range = Aloha.Selection.getRangeObject();
			var rangemod = false;
			if (range.startContainer === prependElem) {
				range.startOffset ++;
				rangemod = true;
			}
			if (range.endContainer === prependElem) {
				range.endOffset ++;
				rangemod = true;
			}
			$(prependElem).prepend('<span role="annotation">' +
					annotationcontent + '</span>');
			if (rangemod === true) {
				range.update();
				range.select();
			}
		},
		
		/**
		 * Navigates to the previous node.
		 */
		_prevNode: function (node) {
			var prev = node.previousSibling;
			if (!prev) {
				return node.parentNode;
			}
			while (prev.lastChild) {
				prev = prev.lastChild;
			}
			return prev;
		},
		

		/**
		 * Removed and disables numeration for the current editable.
		 */
		removeNumerations : function () {
			$(Aloha.activeEditable.obj).attr('aloha-numerated-headers', 'false');
			this.cleanNumerations();
		},

		getBaseElement: function () {
			if (typeof this.baseobjectSelector !== 'undefined') {
				return ($(this.baseobjectSelector).length > 0) ?
						$(this.baseobjectSelector) : null;
			}
			return Aloha.activeEditable ? Aloha.activeEditable.obj : null;
		},

		/*
		* checks if the given Object contains a note Tag that looks like this:
		* <span annotation=''>
		*
		* @param {HTMLElement} obj The DOM object to check.
		*/
		hasNote: function (obj) {
			if (!obj || $(obj).length <= 0) {
				return false;
			}
			return $(obj).find('span[role=annotation]').length > 0;
		},

		/*
		* checks if the given Object has textual content.
		* A possible "<span annotation=''>" tag will be ignored
		*
		* @param {HTMLElement} obj The DOM object to check
		*/
		hasContent: function (obj) {
			if (!obj || 0 === $(obj).length) {
				return false;
			}
			// we have to check the content of this object without the annotation span
			var $objCleaned = $(obj).clone()
			                        .find('span[role=annotation]')
			                        .remove()
			                        .end();
			// check for text, also in other possible sub tags
			return $.trim($objCleaned.text()).length > 0;
		},

		createNumeratedHeaders: function () {
			var active_editable_obj = this.getBaseElement();
			if (!active_editable_obj) {
				return;
			}

			var config = this.getCurrentConfig();
			var headingselector = config.headingselector;
			var headers = active_editable_obj.find(headingselector);

			Aloha.activeEditable.obj.attr('aloha-numerated-headers', 'true');

			if (typeof headers === 'undefined' || headers.length === 0) {
				return;
			}

			// base rank is the lowest rank of all selected headers
			var base_rank = 7;
			var that = this;
			headers.each(function () {
				if (that.hasContent(this)) {
					var current_rank = parseInt(this.nodeName.substr(1), 10);
					if (current_rank < base_rank) {
						base_rank = current_rank;
					}
				}
			});
			if (base_rank > 6) {
				return;
			}
			var prev_rank = null,
				current_annotation = [],
				annotation_pos = 0,
				i;

			// initialize the base annotations
			for (i = 0; i < (6 - base_rank) + 1; i++) {
				current_annotation[i] = 0;
			}

			headers.each(function () {
				// build and count annotation only if there is content in this header
				if (that.hasContent(this)) {

					var current_rank = parseInt(this.nodeName.substr(1), 10);
					if (prev_rank === null && current_rank !== base_rank) {
						// when the first found header has a rank
						// different from the base rank, we omit it
						that._saveRemoveAnnotations($(this).find('span[role=annotation]'));
						return;
					} else if (prev_rank === null) {
						// increment the main annotation
						current_annotation[annotation_pos]++;
					} else if (current_rank > prev_rank) {
						// starts a sub title
						current_annotation[++annotation_pos]++;
					} else if (current_rank === prev_rank) {
						// continues subtitles
						current_annotation[annotation_pos]++;
					} else if (current_rank < prev_rank) {
						//goes back to a main title
						var current_pos = current_rank - base_rank;
						var j;
						for (j = annotation_pos; j > (current_pos); j--) {
							current_annotation[j] = 0; //reset current sub-annotation
						}
						annotation_pos = current_pos;
						current_annotation[annotation_pos]++;
					}

					prev_rank = current_rank;

					var annotation_result = '', i;
					if (config.trailingdot === true) {
						annotation_result = '';
						for (i = 0; i < current_annotation.length; i++) {
							if (current_annotation[i] !== 0) {
								annotation_result += (current_annotation[i] + '.');
							}
						}
					} else {
						annotation_result = current_annotation[0];
						for (i = 1; i < current_annotation.length; i++) {
							if (current_annotation[i] !== 0) {
								annotation_result += ('.' + current_annotation[i]);
							}
						}
					}
					//We add a trailing non-breakable space to the annotation_result
					//to separate the annotation from the heading's text.
					annotation_result += '&nbsp;';
					if (that.hasNote(this)) {
						$(this).find('span[role=annotation]').html(annotation_result);
					} else {
						
						that._prependAnnotation(annotation_result, this);
					}
				} else {
					// no Content, so remove the Note, if there is one
					if (that.hasNote(this)) {
						that._saveRemoveAnnotations($(this).find('span[role=annotation]'));
					}
				}
			});
		}
	});
});

/* formatlesshandler.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('formatlesspaste/formatlesshandler',[
	'aloha',
	'jquery',
	'aloha/contenthandlermanager'
], function (
	Aloha,
	$,
	ContentHandlerManager
) {
	

	/**
	 * Remove unwanted formatting in the pasted element.
	 *
	 * @param {jQuery.<HTMLElement>} $content Element to process.
	 * @param {Array.<string>} toStip A list of tags to strip from the content.
	 */
	function removeFormatting($content, toStrip) {
		$content.find(toStrip.join(',')).each(function () {
			$(this).contents().unwrap();
		});
	}

	/**
	 * Register the formatless paste handler.
	 *
	 * @class {FormatlessPasteHandler}
	 */
	var FormatlessPasteHandler = ContentHandlerManager.createHandler({

		/**
		 * Whether or not formatless copying and pasting is enable.
		 *
		 * @type {boolean}
		 */
		enabled: false,

		/**
		 * Handles the copying and pasting. Removes all content that have been
		 * configured to be striooed away.
		 *
		 * @param {string|jQuery.<HTMLElement>} content The content to
		 *                                              transform.
		 * @return {string} Transformed markup.
		 */
		handleContent: function (content) {
			var $content;

			if (typeof content === 'string') {
				$content = $('<div>' + content + '</div>');
			} else if (content instanceof $) {
				$content = $('<div>').append(content);
			}

			// If an aloha-block is found inside the pasted content, nothing
			// should be modified as it most probably comes from Aloha and does
			// not need to be cleaned up.
			if ($content.find('.aloha-block').length) {
				return;
			}

			if (this.enabled) {
				removeFormatting($content, this.strippedElements);
			}

			return $content.html();
		}
	});

	return FormatlessPasteHandler;
});

define('formatlesspaste/nls/i18n',{
	"root":  {
		"button.formatlessPaste.tooltip": "Toggle Formatless Pasting"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* formatlesspaste-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('formatlesspaste/formatlesspaste-plugin',[
	'aloha/core',
	'aloha/plugin',
	'jquery',
	'ui/ui',
	'ui/toggleButton',
	'formatlesspaste/formatlesshandler',
	'aloha/contenthandlermanager',
	'i18n!formatlesspaste/nls/i18n',
	'util/html'
], function (
	Aloha,
	Plugin,
	$,
	Ui,
	ToggleButton,
	FormatlessPasteHandler,
	ContentHandlerManager,
	i18n,
    Html
) {
	

	/**
	 * Normalizes the given string value to either boolean true or false.
	 *
	 * @param {*} value The value which is to be normalized.
	 * @return {boolean} True if the value if truthy and not the string "false"
	 *                   or "0".
	 */
	function normalizeToBoolean(value) {
		if (!value) {
			return false;
		}
		if (typeof value === 'string') {
			return '0' !== value && 'false' !== value.toLowerCase();
		}
		return true;
	}

	function parseConfiguration(config) {
		var parsed = {};
		if (typeof config.formatlessPasteOption !== 'undefined') {
			parsed.formatlessPasteOption =
					normalizeToBoolean(config.formatlessPasteOption);
		}
		if (typeof config.strippedElements !== 'undefined') {
			parsed.strippedElements = config.strippedElements;
		}
		if (typeof config.button !== 'undefined') {
			parsed.button = normalizeToBoolean(config.button);
		}
		return parsed;
	}

	function applyConfiguration(obj, config) {
		$.extend(obj, config);
	}

	function registerFormatlessPasteHandler(plugin) {
		ContentHandlerManager.register('formatless', FormatlessPasteHandler);
		FormatlessPasteHandler.strippedElements = Html.TEXT_LEVEL_SEMANTIC_ELEMENTS;

		plugin._toggleFormatlessPasteButton =
			Ui.adopt('toggleFormatlessPaste', ToggleButton, {
				tooltip : i18n.t('button.formatlessPaste.tooltip'),
				icon    : 'aloha-icon aloha-icon-formatless-paste',
				scope   : 'Aloha.continuoustext',
				click   : function () {
					// Toggle the value of allowFormatless
					FormatlessPasteHandler.enabled =
						!FormatlessPasteHandler.enabled;
				}
			});

		plugin._toggleFormatlessPasteButton.show(plugin.button);

		if (true === plugin.formatlessPasteOption) {
			plugin._toggleFormatlessPasteButton.setState(true);
			FormatlessPasteHandler.enabled = true;
		} else if (false === plugin.formatlessPasteOption) {
			plugin._toggleFormatlessPasteButton.setState(false);
			FormatlessPasteHandler.enabled = false;
		}
	}

	var configLookup = {};

	function getEditableConfig(plugin, editable) {
		var id = editable.getId();
		if (!id) {
			return null;
		}
		if (configLookup[id]) {
			return configLookup[id];
		}
		var config = plugin.getEditableConfig(editable.obj);
		if (!config) {
			return null;
		}
		configLookup[id] = parseConfiguration(config);
		return configLookup[id];
	}

	function clearEditableConfig(editable) {
		var id = editable.getId();
		if (id && configLookup[id]) {
			delete configLookup[id];
		}
	}

	Aloha.bind('aloha-editable-destroyed', function ($event, data) {
		clearEditableConfig(data);
	});

	return Plugin.create('formatlesspaste', {

		/**
		 * Whether or not formatless pasting is enabled.
		 *
		 * Configurable.
		 *
		 * @type {boolean}
		 */
		formatlessPasteOption: false,

		/**
		 * Whether or not to show the formatless paste button.
		 *
		 * Configurable.
		 *
		 * @type {boolean}
		 */
		button: true,

		/**
		 * Initializes formatless copying and pasting.
		 * Parses configuration.
		 */
		init: function () {
			var plugin = this;
			var config = plugin.settings.config || plugin.settings;

			applyConfiguration(plugin, parseConfiguration(config));
			registerFormatlessPasteHandler(plugin);

			Aloha.bind('aloha-editable-activated', function ($event, data) {
				var config = getEditableConfig(plugin, data.editable);
				if (!config) {
					return;
				}

				var pasteButton = plugin._toggleFormatlessPasteButton;

				if (true === config.formatlessPasteOption) {
					pasteButton.setState(true);
					FormatlessPasteHandler.enabled = true;
				} else if (false === config.formatlessPasteOption) {
					pasteButton.setState(false);
					FormatlessPasteHandler.enabled = false;
				}

				pasteButton.show(false !== config.button);
			});
		}
	});
});

/**
 * @overview: Exposes John Resig's Simply JavaScrip Inheritance as a CommonJS
 *            module (see bottom of file).
 */

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function(){};
  
  // Create a new Class that inherits from this class
  Class.extend = function() {
    var _super = this.prototype;
    
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
    
    // Copy the properties over onto the new prototype
    for(var i = 0; i < arguments.length; i++) {
      var prop = arguments[i];
      for (var name in prop) {
        // Check if we're overwriting an existing function
        prototype[name] = typeof prop[name] == "function" && 
          typeof _super[name] == "function" && fnTest.test(prop[name]) ?
          (function(name, fn){
            return function() {
              var tmp = this._super;

              // Add a new ._super() method that is the same method
              // but on the super-class
              this._super = _super[name];

              // The method only need to be bound temporarily, so we
              // remove it when we're done executing
              var ret = fn.apply(this, arguments);        
              this._super = tmp;

              return ret;
            };
          })(name, prop[name]) :
          prop[name];
      }
    }
    
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
    
    // Populate our constructed prototype object
    Class.prototype = prototype;
    
    // Enforce the constructor to be what we expect
    Class.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;
    
    return Class;
  };
})();

// Expose Class as an CommonJS module.
if ( typeof define === 'function') {
	define( 'Class', [], function () { return Class; } );
}
;
define('jstree', ['jquery'], function (jQuery) {
var $ = jQuery;

/*
 * jsTree 1.0-rc3
 * http://jstree.com/
 *
 * Copyright (c) 2010 Ivan Bozhanov (vakata.com)
 *
 * Licensed same as jquery - under the terms of either the MIT License or the GPL Version 2 License
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * $Date: 2011-02-09 01:17:14 +0200 (ср, 09 февр 2011) $
 * $Revision: 236 $
 */

/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */
/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false*/



// top wrapper to prevent multiple inclusion (is this OK?)
(function () { if(jQuery && jQuery.jstree) { return; }
	var is_ie6 = false, is_ie7 = false, is_ff2 = false;

/* 
 * jsTree core
 */
(function ($) {
	// Common functions not related to jsTree 
	// decided to move them to a `vakata` "namespace"
	$.vakata = {};
	// CSS related functions
	$.vakata.css = {
		get_css : function(rule_name, delete_flag, sheet) {
			rule_name = rule_name.toLowerCase();
			var css_rules = sheet.cssRules || sheet.rules,
				j = 0;
			do {
				if(css_rules.length && j > css_rules.length + 5) { return false; }
				if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) {
					if(delete_flag === true) {
						if(sheet.removeRule) { sheet.removeRule(j); }
						if(sheet.deleteRule) { sheet.deleteRule(j); }
						return true;
					}
					else { return css_rules[j]; }
				}
			}
			while (css_rules[++j]);
			return false;
		},
		add_css : function(rule_name, sheet) {
			if($.jstree.css.get_css(rule_name, false, sheet)) { return false; }
			if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); }
			return $.vakata.css.get_css(rule_name);
		},
		remove_css : function(rule_name, sheet) { 
			return $.vakata.css.get_css(rule_name, true, sheet); 
		},
		add_sheet : function(opts) {
			var tmp = false, is_new = true;
			if(opts.str) {
				if(opts.title) { tmp = $("style[id='" + opts.title + "-stylesheet']")[0]; }
				if(tmp) { is_new = false; }
				else {
					tmp = document.createElement("style");
					tmp.setAttribute('type',"text/css");
					if(opts.title) { tmp.setAttribute("id", opts.title + "-stylesheet"); }
				}
				if(tmp.styleSheet) {
					if(is_new) { 
						document.getElementsByTagName("head")[0].appendChild(tmp); 
						tmp.styleSheet.cssText = opts.str; 
					}
					else {
						tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str; 
					}
				}
				else {
					tmp.appendChild(document.createTextNode(opts.str));
					document.getElementsByTagName("head")[0].appendChild(tmp);
				}
				return tmp.sheet || tmp.styleSheet;
			}
			if(opts.url) {
				if(document.createStyleSheet) {
					try { tmp = document.createStyleSheet(opts.url); } catch (e) { }
				}
				else {
					tmp			= document.createElement('link');
					tmp.rel		= 'stylesheet';
					tmp.type	= 'text/css';
					tmp.media	= "all";
					tmp.href	= opts.url;
					document.getElementsByTagName("head")[0].appendChild(tmp);
					return tmp.styleSheet;
				}
			}
		}
	};

	// private variables 
	var instances = [],			// instance array (used by $.jstree.reference/create/focused)
		focused_instance = -1,	// the index in the instance array of the currently focused instance
		plugins = {},			// list of included plugins
		prepared_move = {};		// for the move_node function

	// jQuery plugin wrapper (thanks to jquery UI widget function)
	$.fn.jstree = function (settings) {
		var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node")
			args = Array.prototype.slice.call(arguments, 1), 
			returnValue = this;

		// if a method call execute the method on all selected instances
		if(isMethodCall) {
			if(settings.substring(0, 1) == '_') { return returnValue; }
			this.each(function() {
				var instance = instances[$.data(this, "jstree-instance-id")],
					methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance;
					if(typeof methodValue !== "undefined" && (settings.indexOf("is_") === 0 || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; }
			});
		}
		else {
			this.each(function() {
				// extend settings and allow for multiple hashes and $.data
				var instance_id = $.data(this, "jstree-instance-id"),
					a = [],
					b = settings ? $.extend({}, true, settings) : {},
					c = $(this), 
					s = false, 
					t = [];
				a = a.concat(args);
				if(c.data("jstree")) { a.push(c.data("jstree")); }
				b = a.length ? $.extend.apply(null, [true, b].concat(a)) : b;

				// if an instance already exists, destroy it first
				if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); }
				// push a new empty object to the instances array
				instance_id = parseInt(instances.push({}),10) - 1;
				// store the jstree instance id to the container element
				$.data(this, "jstree-instance-id", instance_id);
				// clean up all plugins
				b.plugins = $.isArray(b.plugins) ? b.plugins : $.jstree.defaults.plugins.slice();
				b.plugins.unshift("core");
				// only unique plugins
				b.plugins = b.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");

				// extend defaults with passed data
				s = $.extend(true, {}, $.jstree.defaults, b);
				s.plugins = b.plugins;
				$.each(plugins, function (i, val) { 
					if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } 
					else { t.push(i); }
				});
				s.plugins = t;

				// push the new object to the instances array (at the same time set the default classes to the container) and init
				instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); 
				// init all activated plugins for this instance
				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; });
				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } });
				// initialize the instance
				setTimeout(function() { instances[instance_id].init(); }, 0);
			});
		}
		// return the jquery selection (or if it was a method call that returned a value - the returned value)
		return returnValue;
	};
	// object to store exposed functions and objects
	$.jstree = {
		defaults : {
			plugins : []
		},
		_focused : function () { return instances[focused_instance] || null; },
		_reference : function (needle) { 
			// get by instance id
			if(instances[needle]) { return instances[needle]; }
			// get by DOM (if still no luck - return null
			var o = $(needle); 
			if(!o.length && typeof needle === "string") { o = $("#" + needle); }
			if(!o.length) { return null; }
			return instances[o.closest(".jstree").data("jstree-instance-id")] || null; 
		},
		_instance : function (index, container, settings) { 
			// for plugins to store data in
			this.data = { core : {} };
			this.get_settings	= function () { return $.extend(true, {}, settings); };
			this._get_settings	= function () { return settings; };
			this.get_index		= function () { return index; };
			this.get_container	= function () { return container; };
			this.get_container_ul = function () { return container.children("ul:eq(0)"); };
			this._set_settings	= function (s) { 
				settings = $.extend(true, {}, settings, s);
			};
		},
		_fn : { },
		plugin : function (pname, pdata) {
			pdata = $.extend({}, {
				__init		: $.noop, 
				__destroy	: $.noop,
				_fn			: {},
				defaults	: false
			}, pdata);
			plugins[pname] = pdata;

			$.jstree.defaults[pname] = pdata.defaults;
			$.each(pdata._fn, function (i, val) {
				val.plugin		= pname;
				val.old			= $.jstree._fn[i];
				$.jstree._fn[i] = function () {
					var rslt,
						func = val,
						args = Array.prototype.slice.call(arguments),
						evnt = new $.Event("before.jstree"),
						rlbk = false;

					if(this.data.core.locked === true && i !== "unlock" && i !== "is_locked") { return; }

					// Check if function belongs to the included plugins of this instance
					do {
						if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; }
						func = func.old;
					} while(func);
					if(!func) { return; }

					// context and function to trigger events, then finally call the function
					if(i.indexOf("_") === 0) {
						rslt = func.apply(this, args);
					}
					else {
						rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args, "plugin" : func.plugin });
						if(rslt === false) { return; }
						if(typeof rslt !== "undefined") { args = rslt; }

						rslt = func.apply(
							$.extend({}, this, { 
								__callback : function (data) { 
									this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk });
								},
								__rollback : function () { 
									rlbk = this.get_rollback();
									return rlbk;
								},
								__call_old : function (replace_arguments) {
									return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) );
								}
							}), args);
					}

					// return the result
					return rslt;
				};
				$.jstree._fn[i].old = val.old;
				$.jstree._fn[i].plugin = pname;
			});
		},
		rollback : function (rb) {
			if(rb) {
				if(!$.isArray(rb)) { rb = [ rb ]; }
				$.each(rb, function (i, val) {
					instances[val.i].set_rollback(val.h, val.d);
				});
			}
		}
	};
	// set the prototype for all instances
	$.jstree._fn = $.jstree._instance.prototype = {};

	// load the css when DOM is ready
	$(function() {
		// code is copied from jQuery ($.browser is deprecated + there is a bug in IE)
		var u = navigator.userAgent.toLowerCase(),
			v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
			css_string = '' + 
				'.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + 
				'.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } ' + 
				'.jstree-rtl li { margin-left:0; margin-right:18px; } ' + 
				'.jstree > ul > li { margin-left:0px; } ' + 
				'.jstree-rtl > ul > li { margin-right:0px; } ' + 
				'.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + 
				'.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + 
				'.jstree a:focus { outline: none; } ' + 
				'.jstree a > ins { height:16px; width:16px; } ' + 
				'.jstree a > .jstree-icon { margin-right:3px; } ' + 
				'.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + 
				'li.jstree-open > ul { display:block; } ' + 
				'li.jstree-closed > ul { display:none; } ';
		// Correct IE 6 (does not support the > CSS selector)
		if(/msie/.test(u) && parseInt(v, 10) == 6) { 
			is_ie6 = true;

			// fix image flicker and lack of caching
			try {
				document.execCommand("BackgroundImageCache", false, true);
			} catch (err) { }

			css_string += '' + 
				'.jstree li { height:18px; margin-left:0; margin-right:0; } ' + 
				'.jstree li li { margin-left:18px; } ' + 
				'.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + 
				'li.jstree-open ul { display:block; } ' + 
				'li.jstree-closed ul { display:none !important; } ' + 
				'.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + 
				'.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + 
				'.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } ';
		}
		// Correct IE 7 (shifts anchor nodes onhover)
		if(/msie/.test(u) && parseInt(v, 10) == 7) { 
			is_ie7 = true;
			css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } ';
		}
		// correct ff2 lack of display:inline-block
		if(!/compatible/.test(u) && /mozilla/.test(u) && parseFloat(v, 10) < 1.9) {
			is_ff2 = true;
			css_string += '' + 
				'.jstree ins { display:-moz-inline-box; } ' + 
				'.jstree li { line-height:12px; } ' + // WHY??
				'.jstree a { display:-moz-inline-box; } ' + 
				'.jstree .jstree-no-icons .jstree-checkbox { display:-moz-inline-stack !important; } ';
				/* this shouldn't be here as it is theme specific */
		}
		// the default stylesheet
		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
	});

	// core functions (open, close, create, update, delete)
	$.jstree.plugin("core", {
		__init : function () {
			this.data.core.locked = false;
			this.data.core.to_open = this.get_settings().core.initially_open;
			this.data.core.to_load = this.get_settings().core.initially_load;
		},
		defaults : { 
			html_titles	: false,
			animation	: 500,
			initially_open : [],
			initially_load : [],
			open_parents : true,
			notify_plugins : true,
			rtl			: false,
			load_open	: false,
			strings		: {
				loading		: "Loading ...",
				new_node	: "New node",
				multiple_selection : "Multiple selection"
			}
		},
		_fn : { 
			init	: function () { 
				this.set_focus(); 
				if(this._get_settings().core.rtl) {
					this.get_container().addClass("jstree-rtl").css("direction", "rtl");
				}
				this.get_container().html("<ul><li class='jstree-last jstree-leaf'><ins>&#160;</ins><a class='jstree-loading' href='#'><ins class='jstree-icon'>&#160;</ins>" + this._get_string("loading") + "</a></li></ul>");
				this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18;

				this.get_container()
					.delegate("li > ins", "click.jstree", $.proxy(function (event) {
							var trgt = $(event.target);
							if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); }
						}, this))
					.bind("mousedown.jstree", $.proxy(function () { 
							this.set_focus(); // This used to be setTimeout(set_focus,0) - why?
						}, this))
					.bind("dblclick.jstree", function (event) { 
						var sel;
						if(document.selection && document.selection.empty) { document.selection.empty(); }
						else {
							if(window.getSelection) {
								sel = window.getSelection();
								try { 
									sel.removeAllRanges();
									sel.collapse();
								} catch (err) { }
							}
						}
					});
				if(this._get_settings().core.notify_plugins) {
					this.get_container()
						.bind("load_node.jstree", $.proxy(function (e, data) { 
								var o = this._get_node(data.rslt.obj),
									t = this;
								if(o === -1) { o = this.get_container_ul(); }
								if(!o.length) { return; }
								o.find("li").each(function () {
									var th = $(this);
									if(th.data("jstree")) {
										$.each(th.data("jstree"), function (plugin, values) {
											if(t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) {
												t["_" + plugin + "_notify"].call(t, th, values);
											}
										});
									}
								});
							}, this));
				}
				if(this._get_settings().core.load_open) {
					this.get_container()
						.bind("load_node.jstree", $.proxy(function (e, data) { 
								var o = this._get_node(data.rslt.obj),
									t = this;
								if(o === -1) { o = this.get_container_ul(); }
								if(!o.length) { return; }
								o.find("li.jstree-open:not(:has(ul))").each(function () {
									t.load_node(this, $.noop, $.noop);
								});
							}, this));
				}
				this.__callback();
				this.load_node(-1, function () { this.loaded(); this.reload_nodes(); });
			},
			destroy	: function () { 
				var i,
					n = this.get_index(),
					s = this._get_settings(),
					_this = this;

				$.each(s.plugins, function (i, val) {
					try { plugins[val].__destroy.apply(_this); } catch(err) { }
				});
				this.__callback();
				// set focus to another instance if this one is focused
				if(this.is_focused()) { 
					for(i in instances) { 
						if(instances.hasOwnProperty(i) && i != n) { 
							instances[i].set_focus(); 
							break; 
						} 
					}
				}
				// if no other instance found
				if(n === focused_instance) { focused_instance = -1; }
				// remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events
				this.get_container()
					.unbind(".jstree")
					.undelegate(".jstree")
					.removeData("jstree-instance-id")
					.find("[class^='jstree']")
						.andSelf()
						.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
				$(document)
					.unbind(".jstree-" + n)
					.undelegate(".jstree-" + n);
				// remove the actual data
				instances[n] = null;
				delete instances[n];
			},

			_core_notify : function (n, data) {
				if(data.opened) {
					this.open_node(n, false, true);
				}
			},

			lock : function () {
				this.data.core.locked = true;
				this.get_container().children("ul").addClass("jstree-locked").css("opacity","0.7");
				this.__callback({});
			},
			unlock : function () {
				this.data.core.locked = false;
				this.get_container().children("ul").removeClass("jstree-locked").css("opacity","1");
				this.__callback({});
			},
			is_locked : function () { return this.data.core.locked; },
			save_opened : function () {
				var _this = this;
				this.data.core.to_open = [];
				this.get_container_ul().find("li.jstree-open").each(function () { 
					if(this.id) { _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); }
				});
				this.__callback(_this.data.core.to_open);
			},
			save_loaded : function () { },
			reload_nodes : function (is_callback) {
				var _this = this,
					done = true,
					current = [],
					remaining = [];
				if(!is_callback) { 
					this.data.core.reopen = false; 
					this.data.core.refreshing = true; 
					this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
					this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
					if(this.data.core.to_open.length) {
						this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open);
					}
				}
				if(this.data.core.to_load.length) {
					$.each(this.data.core.to_load, function (i, val) {
						if(val == "#") { return true; }
						if($(val).length) { current.push(val); }
						else { remaining.push(val); }
					});
					if(current.length) {
						this.data.core.to_load = remaining;
						$.each(current, function (i, val) { 
							if(!_this._is_loaded(val)) {
								_this.load_node(val, function () { _this.reload_nodes(true); }, function () { _this.reload_nodes(true); });
								done = false;
							}
						});
					}
				}
				if(this.data.core.to_open.length) {
					$.each(this.data.core.to_open, function (i, val) {
						_this.open_node(val, false, true); 
					});
				}
				if(done) { 
					// TODO: find a more elegant approach to syncronizing returning requests
					if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
					this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50);
					this.data.core.refreshing = false;
					this.reopen();
				}
			},
			reopen : function () {
				var _this = this;
				if(this.data.core.to_open.length) {
					$.each(this.data.core.to_open, function (i, val) {
						_this.open_node(val, false, true); 
					});
				}
				this.__callback({});
			},
			refresh : function (obj) {
				var _this = this;
				this.save_opened();
				if(!obj) { obj = -1; }
				obj = this._get_node(obj);
				if(!obj) { obj = -1; }
				if(obj !== -1) { obj.children("UL").remove(); }
				else { this.get_container_ul().empty(); }
				this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reload_nodes(); });
			},
			// Dummy function to fire after the first load (so that there is a jstree.loaded event)
			loaded	: function () { 
				this.__callback(); 
			},
			// deal with focus
			set_focus	: function () { 
				if(this.is_focused()) { return; }
				var f = $.jstree._focused();
				if(f) { f.unset_focus(); }

				this.get_container().addClass("jstree-focused"); 
				focused_instance = this.get_index(); 
				this.__callback();
			},
			is_focused	: function () { 
				return focused_instance == this.get_index(); 
			},
			unset_focus	: function () {
				if(this.is_focused()) {
					this.get_container().removeClass("jstree-focused"); 
					focused_instance = -1; 
				}
				this.__callback();
			},

			// traverse
			_get_node		: function (obj) { 
				var $obj = $(obj, this.get_container()); 
				if($obj.is(".jstree") || obj == -1) { return -1; } 
				$obj = $obj.closest("li", this.get_container()); 
				return $obj.length ? $obj : false; 
			},
			_get_next		: function (obj, strict) {
				obj = this._get_node(obj);
				if(obj === -1) { return this.get_container().find("> ul > li:first-child"); }
				if(!obj.length) { return false; }
				if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; }

				if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); }
				else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); }
				else { return obj.parentsUntil(".jstree","li").next("li").eq(0); }
			},
			_get_prev		: function (obj, strict) {
				obj = this._get_node(obj);
				if(obj === -1) { return this.get_container().find("> ul > li:last-child"); }
				if(!obj.length) { return false; }
				if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; }

				if(obj.prev("li").length) {
					obj = obj.prev("li").eq(0);
					while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); }
					return obj;
				}
				else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; }
			},
			_get_parent		: function (obj) {
				obj = this._get_node(obj);
				if(obj == -1 || !obj.length) { return false; }
				var o = obj.parentsUntil(".jstree", "li:eq(0)");
				return o.length ? o : -1;
			},
			_get_children	: function (obj) {
				obj = this._get_node(obj);
				if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); }
				if(!obj.length) { return false; }
				return obj.children("ul:eq(0)").children("li");
			},
			get_path		: function (obj, id_mode) {
				var p = [],
					_this = this;
				obj = this._get_node(obj);
				if(obj === -1 || !obj || !obj.length) { return false; }
				obj.parentsUntil(".jstree", "li").each(function () {
					p.push( id_mode ? this.id : _this.get_text(this) );
				});
				p.reverse();
				p.push( id_mode ? obj.attr("id") : this.get_text(obj) );
				return p;
			},

			// string functions
			_get_string : function (key) {
				return this._get_settings().core.strings[key] || key;
			},

			is_open		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); },
			is_closed	: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); },
			is_leaf		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); },
			correct_state	: function (obj) {
				obj = this._get_node(obj);
				if(!obj || obj === -1) { return false; }
				obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove();
				this.__callback({ "obj" : obj });
			},
			// open/close
			open_node	: function (obj, callback, skip_animation) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; }
				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation,
					t = this;
				if(!this._is_loaded(obj)) {
					obj.children("a").addClass("jstree-loading");
					this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback);
				}
				else {
					if(this._get_settings().core.open_parents) {
						obj.parentsUntil(".jstree",".jstree-closed").each(function () {
							t.open_node(this, false, true);
						});
					}
					if(s) { obj.children("ul").css("display","none"); }
					obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading");
					if(s) { obj.children("ul").stop(true, true).slideDown(s, function () { this.style.display = ""; t.after_open(obj); }); }
					else { t.after_open(obj); }
					this.__callback({ "obj" : obj });
					if(callback) { callback.call(); }
				}
			},
			after_open	: function (obj) { this.__callback({ "obj" : obj }); },
			close_node	: function (obj, skip_animation) {
				obj = this._get_node(obj);
				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation,
					t = this;
				if(!obj.length || !obj.hasClass("jstree-open")) { return false; }
				if(s) { obj.children("ul").attr("style","display:block !important"); }
				obj.removeClass("jstree-open").addClass("jstree-closed");
				if(s) { obj.children("ul").stop(true, true).slideUp(s, function () { this.style.display = ""; t.after_close(obj); }); }
				else { t.after_close(obj); }
				this.__callback({ "obj" : obj });
			},
			after_close	: function (obj) { this.__callback({ "obj" : obj }); },
			toggle_node	: function (obj) {
				obj = this._get_node(obj);
				if(obj.hasClass("jstree-closed")) { return this.open_node(obj); }
				if(obj.hasClass("jstree-open")) { return this.close_node(obj); }
			},
			open_all	: function (obj, do_animation, original_obj) {
				obj = obj ? this._get_node(obj) : -1;
				if(!obj || obj === -1) { obj = this.get_container_ul(); }
				if(original_obj) { 
					obj = obj.find("li.jstree-closed");
				}
				else {
					original_obj = obj;
					if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").andSelf(); }
					else { obj = obj.find("li.jstree-closed"); }
				}
				var _this = this;
				obj.each(function () { 
					var __this = this; 
					if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, do_animation, original_obj); }, !do_animation); }
					else { _this.open_node(this, false, !do_animation); }
				});
				// so that callback is fired AFTER all nodes are open
				if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); }
			},
			close_all	: function (obj, do_animation) {
				var _this = this;
				obj = obj ? this._get_node(obj) : this.get_container();
				if(!obj || obj === -1) { obj = this.get_container_ul(); }
				obj.find("li.jstree-open").andSelf().each(function () { _this.close_node(this, !do_animation); });
				this.__callback({ "obj" : obj });
			},
			clean_node	: function (obj) {
				obj = obj && obj != -1 ? $(obj) : this.get_container_ul();
				obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li");
				obj.removeClass("jstree-last")
					.filter("li:last-child").addClass("jstree-last").end()
					.filter(":has(li)")
						.not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed");
				obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove();
				this.__callback({ "obj" : obj });
			},
			// rollback
			get_rollback : function () { 
				this.__callback();
				return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; 
			},
			set_rollback : function (html, data) {
				this.get_container().empty().append(html);
				this.data = data;
				this.__callback();
			},
			// Dummy functions to be overwritten by any datastore plugin included
			load_node	: function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); },
			_is_loaded	: function (obj) { return true; },

			// Basic operations: create
			create_node	: function (obj, position, js, callback, is_loaded) {
				obj = this._get_node(obj);
				position = typeof position === "undefined" ? "last" : position;
				var d = $("<li />"),
					s = this._get_settings().core,
					tmp;

				if(obj !== -1 && !obj.length) { return false; }
				if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; }

				this.__rollback();

				if(typeof js === "string") { js = { "data" : js }; }
				if(!js) { js = {}; }
				if(js.attr) { d.attr(js.attr); }
				if(js.metadata) { d.data(js.metadata); }
				if(js.state) { d.addClass("jstree-" + js.state); }
				if(!js.data) { js.data = this._get_string("new_node"); }
				if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
				$.each(js.data, function (i, m) {
					tmp = $("<a />");
					if($.isFunction(m)) { m = m.call(this, js); }
					if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); }
					else {
						if(!m.attr) { m.attr = {}; }
						if(!m.attr.href) { m.attr.href = '#'; }
						tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title);
						if(m.language) { tmp.addClass(m.language); }
					}
					tmp.prepend("<ins class='jstree-icon'>&#160;</ins>");
					if(m.icon) { 
						if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
						else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
					}
					d.append(tmp);
				});
				d.prepend("<ins class='jstree-icon'>&#160;</ins>");
				if(obj === -1) {
					obj = this.get_container();
					if(position === "before") { position = "first"; }
					if(position === "after") { position = "last"; }
				}
				switch(position) {
					case "before": obj.before(d); tmp = this._get_parent(obj); break;
					case "after" : obj.after(d);  tmp = this._get_parent(obj); break;
					case "inside":
					case "first" :
						if(!obj.children("ul").length) { obj.append("<ul />"); }
						obj.children("ul").prepend(d);
						tmp = obj;
						break;
					case "last":
						if(!obj.children("ul").length) { obj.append("<ul />"); }
						obj.children("ul").append(d);
						tmp = obj;
						break;
					default:
						if(!obj.children("ul").length) { obj.append("<ul />"); }
						if(!position) { position = 0; }
						tmp = obj.children("ul").children("li").eq(position);
						if(tmp.length) { tmp.before(d); }
						else { obj.children("ul").append(d); }
						tmp = obj;
						break;
				}
				if(tmp === -1 || tmp.get(0) === this.get_container().get(0)) { tmp = -1; }
				this.clean_node(tmp);
				this.__callback({ "obj" : d, "parent" : tmp });
				if(callback) { callback.call(this, d); }
				return d;
			},
			// Basic operations: rename (deal with text)
			get_text	: function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				var s = this._get_settings().core.html_titles;
				obj = obj.children("a:eq(0)");
				if(s) {
					obj = obj.clone();
					obj.children("INS").remove();
					return obj.html();
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					return obj.nodeValue;
				}
			},
			set_text	: function (obj, val) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				obj = obj.children("a:eq(0)");
				if(this._get_settings().core.html_titles) {
					var tmp = obj.children("INS").clone();
					obj.html(val).prepend(tmp);
					this.__callback({ "obj" : obj, "name" : val });
					return true;
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					this.__callback({ "obj" : obj, "name" : val });
					return (obj.nodeValue = val);
				}
			},
			rename_node : function (obj, val) {
				obj = this._get_node(obj);
				this.__rollback();
				if(obj && obj.length && this.set_text.apply(this, Array.prototype.slice.call(arguments))) { this.__callback({ "obj" : obj, "name" : val }); }
			},
			// Basic operations: deleting nodes
			delete_node : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				this.__rollback();
				var p = this._get_parent(obj), prev = $([]), t = this;
				obj.each(function () {
					prev = prev.add(t._get_prev(this));
				});
				obj = obj.detach();
				if(p !== -1 && p.find("> ul > li").length === 0) {
					p.removeClass("jstree-open jstree-closed").addClass("jstree-leaf");
				}
				this.clean_node(p);
				this.__callback({ "obj" : obj, "prev" : prev, "parent" : p });
				return obj;
			},
			prepare_move : function (o, r, pos, cb, is_cb) {
				var p = {};

				p.ot = $.jstree._reference(o) || this;
				p.o = p.ot._get_node(o);
				p.r = r === - 1 ? -1 : this._get_node(r);
				p.p = (typeof pos === "undefined" || pos === false) ? "last" : pos; // TODO: move to a setting
				if(!is_cb && prepared_move.o && prepared_move.o[0] === p.o[0] && prepared_move.r[0] === p.r[0] && prepared_move.p === p.p) {
					this.__callback(prepared_move);
					if(cb) { cb.call(this, prepared_move); }
					return;
				}
				p.ot = $.jstree._reference(p.o) || this;
				p.rt = $.jstree._reference(p.r) || this; // r === -1 ? p.ot : $.jstree._reference(p.r) || this
				if(p.r === -1 || !p.r) {
					p.cr = -1;
					switch(p.p) {
						case "first":
						case "before":
						case "inside":
							p.cp = 0; 
							break;
						case "after":
						case "last":
							p.cp = p.rt.get_container().find(" > ul > li").length; 
							break;
						default:
							p.cp = p.p;
							break;
					}
				}
				else {
					if(!/^(before|after)$/.test(p.p) && !this._is_loaded(p.r)) {
						return this.load_node(p.r, function () { this.prepare_move(o, r, pos, cb, true); });
					}
					switch(p.p) {
						case "before":
							p.cp = p.r.index();
							p.cr = p.rt._get_parent(p.r);
							break;
						case "after":
							p.cp = p.r.index() + 1;
							p.cr = p.rt._get_parent(p.r);
							break;
						case "inside":
						case "first":
							p.cp = 0;
							p.cr = p.r;
							break;
						case "last":
							p.cp = p.r.find(" > ul > li").length; 
							p.cr = p.r;
							break;
						default: 
							p.cp = p.p;
							p.cr = p.r;
							break;
					}
				}
				p.np = p.cr == -1 ? p.rt.get_container() : p.cr;
				p.op = p.ot._get_parent(p.o);
				p.cop = p.o.index();
				if(p.op === -1) { p.op = p.ot ? p.ot.get_container() : this.get_container(); }
				if(!/^(before|after)$/.test(p.p) && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp++; }
				//if(p.p === "before" && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp--; }
				p.or = p.np.find(" > ul > li:nth-child(" + (p.cp + 1) + ")");
				prepared_move = p;
				this.__callback(prepared_move);
				if(cb) { cb.call(this, prepared_move); }
			},
			check_move : function () {
				var obj = prepared_move, ret = true, r = obj.r === -1 ? this.get_container() : obj.r;
				if(!obj || !obj.o || obj.or[0] === obj.o[0]) { return false; }
				if(obj.op && obj.np && obj.op[0] === obj.np[0] && obj.cp - 1 === obj.o.index()) { return false; }
				obj.o.each(function () { 
					if(r.parentsUntil(".jstree", "li").andSelf().index(this) !== -1) { ret = false; return false; }
				});
				return ret;
			},
			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
				if(!is_prepared) { 
					return this.prepare_move(obj, ref, position, function (p) {
						this.move_node(p, false, false, is_copy, true, skip_check);
					});
				}
				if(is_copy) { 
					prepared_move.cy = true;
				}
				if(!skip_check && !this.check_move()) { return false; }

				this.__rollback();
				var o = false;
				if(is_copy) {
					o = obj.o.clone(true);
					o.find("*[id]").andSelf().each(function () {
						if(this.id) { this.id = "copy_" + this.id; }
					});
				}
				else { o = obj.o; }

				if(obj.or.length) { obj.or.before(o); }
				else { 
					if(!obj.np.children("ul").length) { $("<ul />").appendTo(obj.np); }
					obj.np.children("ul:eq(0)").append(o); 
				}

				try { 
					obj.ot.clean_node(obj.op);
					obj.rt.clean_node(obj.np);
					if(!obj.op.find("> ul > li").length) {
						obj.op.removeClass("jstree-open jstree-closed").addClass("jstree-leaf").children("ul").remove();
					}
				} catch (e) { }

				if(is_copy) { 
					prepared_move.cy = true;
					prepared_move.oc = o; 
				}
				this.__callback(prepared_move);
				return prepared_move;
			},
			_get_move : function () { return prepared_move; }
		}
	});
})(jQuery);
//*/

/* 
 * jsTree ui plugin
 * This plugins handles selecting/deselecting/hovering/dehovering nodes
 */
(function ($) {
	var scrollbar_width, e1, e2;
	$(function() {
		if (/msie/.test(navigator.userAgent.toLowerCase())) {
			e1 = $('<textarea cols="10" rows="2"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body');
			e2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body');
			scrollbar_width = e1.width() - e2.width();
			e1.add(e2).remove();
		} 
		else {
			e1 = $('<div />').css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: 0 })
					.prependTo('body').append('<div />').find('div').css({ width: '100%', height: 200 });
			scrollbar_width = 100 - e1.width();
			e1.parent().remove();
		}
	});
	$.jstree.plugin("ui", {
		__init : function () { 
			this.data.ui.selected = $(); 
			this.data.ui.last_selected = false; 
			this.data.ui.hovered = null;
			this.data.ui.to_select = this.get_settings().ui.initially_select;

			this.get_container()
				.delegate("a", "click.jstree", $.proxy(function (event) {
						event.preventDefault();
						event.currentTarget.blur();
						if(!$(event.currentTarget).hasClass("jstree-loading")) {
							this.select_node(event.currentTarget, true, event);
						}
					}, this))
				.delegate("a", "mouseenter.jstree", $.proxy(function (event) {
						if(!$(event.currentTarget).hasClass("jstree-loading")) {
							this.hover_node(event.target);
						}
					}, this))
				.delegate("a", "mouseleave.jstree", $.proxy(function (event) {
						if(!$(event.currentTarget).hasClass("jstree-loading")) {
							this.dehover_node(event.target);
						}
					}, this))
				.bind("reopen.jstree", $.proxy(function () { 
						this.reselect();
					}, this))
				.bind("get_rollback.jstree", $.proxy(function () { 
						this.dehover_node();
						this.save_selected();
					}, this))
				.bind("set_rollback.jstree", $.proxy(function () { 
						this.reselect();
					}, this))
				.bind("close_node.jstree", $.proxy(function (event, data) { 
						var s = this._get_settings().ui,
							obj = this._get_node(data.rslt.obj),
							clk = (obj && obj.length) ? obj.children("ul").find("a.jstree-clicked") : $(),
							_this = this;
						if(s.selected_parent_close === false || !clk.length) { return; }
						clk.each(function () { 
							_this.deselect_node(this);
							if(s.selected_parent_close === "select_parent") { _this.select_node(obj); }
						});
					}, this))
				.bind("delete_node.jstree", $.proxy(function (event, data) { 
						var s = this._get_settings().ui.select_prev_on_delete,
							obj = this._get_node(data.rslt.obj),
							clk = (obj && obj.length) ? obj.find("a.jstree-clicked") : [],
							_this = this;
						clk.each(function () { _this.deselect_node(this); });
						if(s && clk.length) { 
							data.rslt.prev.each(function () { 
								if(this.parentNode) { _this.select_node(this); return false; /* if return false is removed all prev nodes will be selected */}
							});
						}
					}, this))
				.bind("move_node.jstree", $.proxy(function (event, data) { 
						if(data.rslt.cy) { 
							data.rslt.oc.find("a.jstree-clicked").removeClass("jstree-clicked");
						}
					}, this));
		},
		defaults : {
			select_limit : -1, // 0, 1, 2 ... or -1 for unlimited
			select_multiple_modifier : "ctrl", // on, or ctrl, shift, alt
			select_range_modifier : "shift",
			selected_parent_close : "select_parent", // false, "deselect", "select_parent"
			selected_parent_open : true,
			select_prev_on_delete : true,
			disable_selecting_children : false,
			initially_select : []
		},
		_fn : { 
			_get_node : function (obj, allow_multiple) {
				if(typeof obj === "undefined" || obj === null) { return allow_multiple ? this.data.ui.selected : this.data.ui.last_selected; }
				var $obj = $(obj, this.get_container()); 
				if($obj.is(".jstree") || obj == -1) { return -1; } 
				$obj = $obj.closest("li", this.get_container()); 
				return $obj.length ? $obj : false; 
			},
			_ui_notify : function (n, data) {
				if(data.selected) {
					this.select_node(n, false);
				}
			},
			save_selected : function () {
				var _this = this;
				this.data.ui.to_select = [];
				this.data.ui.selected.each(function () { if(this.id) { _this.data.ui.to_select.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); } });
				this.__callback(this.data.ui.to_select);
			},
			reselect : function () {
				var _this = this,
					s = this.data.ui.to_select;
				s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
				// this.deselect_all(); WHY deselect, breaks plugin state notifier?
				$.each(s, function (i, val) { if(val && val !== "#") { _this.select_node(val); } });
				this.data.ui.selected = this.data.ui.selected.filter(function () { return this.parentNode; });
				this.__callback();
			},
			refresh : function (obj) {
				this.save_selected();
				return this.__call_old();
			},
			hover_node : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				//if(this.data.ui.hovered && obj.get(0) === this.data.ui.hovered.get(0)) { return; }
				if(!obj.hasClass("jstree-hovered")) { this.dehover_node(); }
				this.data.ui.hovered = obj.children("a").addClass("jstree-hovered").parent();
				this._fix_scroll(obj);
				this.__callback({ "obj" : obj });
			},
			dehover_node : function () {
				var obj = this.data.ui.hovered, p;
				if(!obj || !obj.length) { return false; }
				p = obj.children("a").removeClass("jstree-hovered").parent();
				if(this.data.ui.hovered[0] === p[0]) { this.data.ui.hovered = null; }
				this.__callback({ "obj" : obj });
			},
			select_node : function (obj, check, e) {
				obj = this._get_node(obj);
				if(obj == -1 || !obj || !obj.length) { return false; }
				var s = this._get_settings().ui,
					is_multiple = (s.select_multiple_modifier == "on" || (s.select_multiple_modifier !== false && e && e[s.select_multiple_modifier + "Key"])),
					is_range = (s.select_range_modifier !== false && e && e[s.select_range_modifier + "Key"] && this.data.ui.last_selected && this.data.ui.last_selected[0] !== obj[0] && this.data.ui.last_selected.parent()[0] === obj.parent()[0]),
					is_selected = this.is_selected(obj),
					proceed = true,
					t = this;
				if(check) {
					if(s.disable_selecting_children && is_multiple && 
						(
							(obj.parentsUntil(".jstree","li").children("a.jstree-clicked").length) ||
							(obj.children("ul").find("a.jstree-clicked:eq(0)").length)
						)
					) {
						return false;
					}
					proceed = false;
					switch(!0) {
						case (is_range):
							this.data.ui.last_selected.addClass("jstree-last-selected");
							obj = obj[ obj.index() < this.data.ui.last_selected.index() ? "nextUntil" : "prevUntil" ](".jstree-last-selected").andSelf();
							if(s.select_limit == -1 || obj.length < s.select_limit) {
								this.data.ui.last_selected.removeClass("jstree-last-selected");
								this.data.ui.selected.each(function () {
									if(this !== t.data.ui.last_selected[0]) { t.deselect_node(this); }
								});
								is_selected = false;
								proceed = true;
							}
							else {
								proceed = false;
							}
							break;
						case (is_selected && !is_multiple): 
							this.deselect_all();
							is_selected = false;
							proceed = true;
							break;
						case (!is_selected && !is_multiple): 
							if(s.select_limit == -1 || s.select_limit > 0) {
								this.deselect_all();
								proceed = true;
							}
							break;
						case (is_selected && is_multiple): 
							this.deselect_node(obj);
							break;
						case (!is_selected && is_multiple): 
							if(s.select_limit == -1 || this.data.ui.selected.length + 1 <= s.select_limit) { 
								proceed = true;
							}
							break;
					}
				}
				if(proceed && !is_selected) {
					if(!is_range) { this.data.ui.last_selected = obj; }
					obj.children("a").addClass("jstree-clicked");
					if(s.selected_parent_open) {
						obj.parents(".jstree-closed").each(function () { t.open_node(this, false, true); });
					}
					this.data.ui.selected = this.data.ui.selected.add(obj);
					this._fix_scroll(obj.eq(0));
					this.__callback({ "obj" : obj, "e" : e });
				}
			},
			_fix_scroll : function (obj) {
				var c = this.get_container()[0], t;
				if(c.scrollHeight > c.offsetHeight) {
					obj = this._get_node(obj);
					if(!obj || obj === -1 || !obj.length || !obj.is(":visible")) { return; }
					t = obj.offset().top - this.get_container().offset().top;
					if(t < 0) { 
						c.scrollTop = c.scrollTop + t - 1; 
					}
					if(t + this.data.core.li_height + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0) > c.offsetHeight) { 
						c.scrollTop = c.scrollTop + (t - c.offsetHeight + this.data.core.li_height + 1 + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0)); 
					}
				}
			},
			deselect_node : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				if(this.is_selected(obj)) {
					obj.children("a").removeClass("jstree-clicked");
					this.data.ui.selected = this.data.ui.selected.not(obj);
					if(this.data.ui.last_selected.get(0) === obj.get(0)) { this.data.ui.last_selected = this.data.ui.selected.eq(0); }
					this.__callback({ "obj" : obj });
				}
			},
			toggle_select : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return false; }
				if(this.is_selected(obj)) { this.deselect_node(obj); }
				else { this.select_node(obj); }
			},
			is_selected : function (obj) { return this.data.ui.selected.index(this._get_node(obj)) >= 0; },
			get_selected : function (context) { 
				return context ? $(context).find("a.jstree-clicked").parent() : this.data.ui.selected; 
			},
			deselect_all : function (context) {
				var ret = context ? $(context).find("a.jstree-clicked").parent() : this.get_container().find("a.jstree-clicked").parent();
				ret.children("a.jstree-clicked").removeClass("jstree-clicked");
				this.data.ui.selected = $([]);
				this.data.ui.last_selected = false;
				this.__callback({ "obj" : ret });
			}
		}
	});
	// include the selection plugin by default
	$.jstree.defaults.plugins.push("ui");
})(jQuery);
//*/

/* 
 * jsTree CRRM plugin
 * Handles creating/renaming/removing/moving nodes by user interaction.
 */
(function ($) {
	$.jstree.plugin("crrm", { 
		__init : function () {
			this.get_container()
				.bind("move_node.jstree", $.proxy(function (e, data) {
					if(this._get_settings().crrm.move.open_onmove) {
						var t = this;
						data.rslt.np.parentsUntil(".jstree").andSelf().filter(".jstree-closed").each(function () {
							t.open_node(this, false, true);
						});
					}
				}, this));
		},
		defaults : {
			input_width_limit : 200,
			move : {
				always_copy			: false, // false, true or "multitree"
				open_onmove			: true,
				default_position	: "last",
				check_move			: function (m) { return true; }
			}
		},
		_fn : {
			_show_input : function (obj, callback) {
				obj = this._get_node(obj);
				var rtl = this._get_settings().core.rtl,
					w = this._get_settings().crrm.input_width_limit,
					w1 = obj.children("ins").width(),
					w2 = obj.find("> a:visible > ins").width() * obj.find("> a:visible > ins").length,
					t = this.get_text(obj),
					h1 = $("<div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
					h2 = obj.css("position","relative").append(
					$("<input />", { 
						"value" : t,
						"class" : "jstree-rename-input",
						// "size" : t.length,
						"css" : {
							"padding" : "0",
							"border" : "1px solid silver",
							"position" : "absolute",
							"left"  : (rtl ? "auto" : (w1 + w2 + 4) + "px"),
							"right" : (rtl ? (w1 + w2 + 4) + "px" : "auto"),
							"top" : "0px",
							"height" : (this.data.core.li_height - 2) + "px",
							"lineHeight" : (this.data.core.li_height - 2) + "px",
							"width" : "150px" // will be set a bit further down
						},
						"blur" : $.proxy(function () {
							var i = obj.children(".jstree-rename-input"),
								v = i.val();
							if(v === "") { v = t; }
							h1.remove();
							i.remove(); // rollback purposes
							this.set_text(obj,t); // rollback purposes
							this.rename_node(obj, v);
							callback.call(this, obj, v, t);
							obj.css("position","");
						}, this),
						"keyup" : function (event) {
							var key = event.keyCode || event.which;
							if(key == 27) { this.value = t; this.blur(); return; }
							else if(key == 13) { this.blur(); return; }
							else {
								h2.width(Math.min(h1.text("pW" + this.value).width(),w));
							}
						},
						"keypress" : function(event) {
							var key = event.keyCode || event.which;
							if(key == 13) { return false; }
						}
					})
				).children(".jstree-rename-input"); 
				this.set_text(obj, "");
				h1.css({
						fontFamily		: h2.css('fontFamily')		|| '',
						fontSize		: h2.css('fontSize')		|| '',
						fontWeight		: h2.css('fontWeight')		|| '',
						fontStyle		: h2.css('fontStyle')		|| '',
						fontStretch		: h2.css('fontStretch')		|| '',
						fontVariant		: h2.css('fontVariant')		|| '',
						letterSpacing	: h2.css('letterSpacing')	|| '',
						wordSpacing		: h2.css('wordSpacing')		|| ''
				});
				h2.width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
			},
			rename : function (obj) {
				obj = this._get_node(obj);
				this.__rollback();
				var f = this.__callback;
				this._show_input(obj, function (obj, new_name, old_name) { 
					f.call(this, { "obj" : obj, "new_name" : new_name, "old_name" : old_name });
				});
			},
			create : function (obj, position, js, callback, skip_rename) {
				var t, _this = this;
				obj = this._get_node(obj);
				if(!obj) { obj = -1; }
				this.__rollback();
				t = this.create_node(obj, position, js, function (t) {
					var p = this._get_parent(t),
						pos = $(t).index();
					if(callback) { callback.call(this, t); }
					if(p.length && p.hasClass("jstree-closed")) { this.open_node(p, false, true); }
					if(!skip_rename) { 
						this._show_input(t, function (obj, new_name, old_name) { 
							_this.__callback({ "obj" : obj, "name" : new_name, "parent" : p, "position" : pos });
						});
					}
					else { _this.__callback({ "obj" : t, "name" : this.get_text(t), "parent" : p, "position" : pos }); }
				});
				return t;
			},
			remove : function (obj) {
				obj = this._get_node(obj, true);
				var p = this._get_parent(obj), prev = this._get_prev(obj);
				this.__rollback();
				obj = this.delete_node(obj);
				if(obj !== false) { this.__callback({ "obj" : obj, "prev" : prev, "parent" : p }); }
			},
			check_move : function () {
				if(!this.__call_old()) { return false; }
				var s = this._get_settings().crrm.move;
				if(!s.check_move.call(this, this._get_move())) { return false; }
				return true;
			},
			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
				var s = this._get_settings().crrm.move;
				if(!is_prepared) { 
					if(typeof position === "undefined") { position = s.default_position; }
					if(position === "inside" && !s.default_position.match(/^(before|after)$/)) { position = s.default_position; }
					return this.__call_old(true, obj, ref, position, is_copy, false, skip_check);
				}
				// if the move is already prepared
				if(s.always_copy === true || (s.always_copy === "multitree" && obj.rt.get_index() !== obj.ot.get_index() )) {
					is_copy = true;
				}
				this.__call_old(true, obj, ref, position, is_copy, true, skip_check);
			},

			cut : function (obj) {
				obj = this._get_node(obj, true);
				if(!obj || !obj.length) { return false; }
				this.data.crrm.cp_nodes = false;
				this.data.crrm.ct_nodes = obj;
				this.__callback({ "obj" : obj });
			},
			copy : function (obj) {
				obj = this._get_node(obj, true);
				if(!obj || !obj.length) { return false; }
				this.data.crrm.ct_nodes = false;
				this.data.crrm.cp_nodes = obj;
				this.__callback({ "obj" : obj });
			},
			paste : function (obj) { 
				obj = this._get_node(obj);
				if(!obj || !obj.length) { return false; }
				var nodes = this.data.crrm.ct_nodes ? this.data.crrm.ct_nodes : this.data.crrm.cp_nodes;
				if(!this.data.crrm.ct_nodes && !this.data.crrm.cp_nodes) { return false; }
				if(this.data.crrm.ct_nodes) { this.move_node(this.data.crrm.ct_nodes, obj); this.data.crrm.ct_nodes = false; }
				if(this.data.crrm.cp_nodes) { this.move_node(this.data.crrm.cp_nodes, obj, false, true); }
				this.__callback({ "obj" : obj, "nodes" : nodes });
			}
		}
	});
	// include the crr plugin by default
	// $.jstree.defaults.plugins.push("crrm");
})(jQuery);
//*/

/* 
 * jsTree themes plugin
 * Handles loading and setting themes, as well as detecting path to themes, etc.
 */
(function ($) {
	var themes_loaded = [];
	// this variable stores the path to the themes folder - if left as false - it will be autodetected
	$.jstree._themes = false;
	$.jstree.plugin("themes", {
		__init : function () { 
			this.get_container()
				.bind("init.jstree", $.proxy(function () {
						var s = this._get_settings().themes;
						this.data.themes.dots = s.dots; 
						this.data.themes.icons = s.icons; 
						this.set_theme(s.theme, s.url);
					}, this))
				.bind("loaded.jstree", $.proxy(function () {
						// bound here too, as simple HTML tree's won't honor dots & icons otherwise
						if(!this.data.themes.dots) { this.hide_dots(); }
						else { this.show_dots(); }
						if(!this.data.themes.icons) { this.hide_icons(); }
						else { this.show_icons(); }
					}, this));
		},
		defaults : { 
			theme : "default", 
			url : false,
			dots : true,
			icons : true
		},
		_fn : {
			set_theme : function (theme_name, theme_url) {
				if(!theme_name) { return false; }
				if(!theme_url) { theme_url = $.jstree._themes + theme_name + '/style.css'; }
				if($.inArray(theme_url, themes_loaded) == -1) {
					$.vakata.css.add_sheet({ "url" : theme_url });
					themes_loaded.push(theme_url);
				}
				if(this.data.themes.theme != theme_name) {
					this.get_container().removeClass('jstree-' + this.data.themes.theme);
					this.data.themes.theme = theme_name;
				}
				this.get_container().addClass('jstree-' + theme_name);
				if(!this.data.themes.dots) { this.hide_dots(); }
				else { this.show_dots(); }
				if(!this.data.themes.icons) { this.hide_icons(); }
				else { this.show_icons(); }
				this.__callback();
			},
			get_theme	: function () { return this.data.themes.theme; },

			show_dots	: function () { this.data.themes.dots = true; this.get_container().children("ul").removeClass("jstree-no-dots"); },
			hide_dots	: function () { this.data.themes.dots = false; this.get_container().children("ul").addClass("jstree-no-dots"); },
			toggle_dots	: function () { if(this.data.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },

			show_icons	: function () { this.data.themes.icons = true; this.get_container().children("ul").removeClass("jstree-no-icons"); },
			hide_icons	: function () { this.data.themes.icons = false; this.get_container().children("ul").addClass("jstree-no-icons"); },
			toggle_icons: function () { if(this.data.themes.icons) { this.hide_icons(); } else { this.show_icons(); } }
		}
	});
	// autodetect themes path
	$(function () {
		if($.jstree._themes === false) {
			$("script").each(function () { 
				if(this.src.toString().match(/jquery\.jstree[^\/]*?\.js(\?.*)?$/)) { 
					$.jstree._themes = this.src.toString().replace(/jquery\.jstree[^\/]*?\.js(\?.*)?$/, "") + 'themes/'; 
					return false; 
				}
			});
		}
		if($.jstree._themes === false) { $.jstree._themes = "themes/"; }
	});
	// include the themes plugin by default
	$.jstree.defaults.plugins.push("themes");
})(jQuery);
//*/

/*
 * jsTree hotkeys plugin
 * Enables keyboard navigation for all tree instances
 * Depends on the jstree ui & jquery hotkeys plugins
 */
(function ($) {
	var bound = [];
	function exec(i, event) {
		var f = $.jstree._focused(), tmp;
		if(f && f.data && f.data.hotkeys && f.data.hotkeys.enabled) { 
			tmp = f._get_settings().hotkeys[i];
			if(tmp) { return tmp.call(f, event); }
		}
	}
	$.jstree.plugin("hotkeys", {
		__init : function () {
			if(typeof $.hotkeys === "undefined") { throw "jsTree hotkeys: jQuery hotkeys plugin not included."; }
			if(!this.data.ui) { throw "jsTree hotkeys: jsTree UI plugin not included."; }
			$.each(this._get_settings().hotkeys, function (i, v) {
				if(v !== false && $.inArray(i, bound) == -1) {
					$(document).bind("keydown", i, function (event) { return exec(i, event); });
					bound.push(i);
				}
			});
			this.get_container()
				.bind("lock.jstree", $.proxy(function () {
						if(this.data.hotkeys.enabled) { this.data.hotkeys.enabled = false; this.data.hotkeys.revert = true; }
					}, this))
				.bind("unlock.jstree", $.proxy(function () {
						if(this.data.hotkeys.revert) { this.data.hotkeys.enabled = true; }
					}, this));
			this.enable_hotkeys();
		},
		defaults : {
			"up" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_prev(o));
				return false; 
			},
			"ctrl+up" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_prev(o));
				return false; 
			},
			"shift+up" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_prev(o));
				return false; 
			},
			"down" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_next(o));
				return false;
			},
			"ctrl+down" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_next(o));
				return false;
			},
			"shift+down" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
				this.hover_node(this._get_next(o));
				return false;
			},
			"left" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o) {
					if(o.hasClass("jstree-open")) { this.close_node(o); }
					else { this.hover_node(this._get_prev(o)); }
				}
				return false;
			},
			"ctrl+left" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o) {
					if(o.hasClass("jstree-open")) { this.close_node(o); }
					else { this.hover_node(this._get_prev(o)); }
				}
				return false;
			},
			"shift+left" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o) {
					if(o.hasClass("jstree-open")) { this.close_node(o); }
					else { this.hover_node(this._get_prev(o)); }
				}
				return false;
			},
			"right" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o && o.length) {
					if(o.hasClass("jstree-closed")) { this.open_node(o); }
					else { this.hover_node(this._get_next(o)); }
				}
				return false;
			},
			"ctrl+right" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o && o.length) {
					if(o.hasClass("jstree-closed")) { this.open_node(o); }
					else { this.hover_node(this._get_next(o)); }
				}
				return false;
			},
			"shift+right" : function () { 
				var o = this.data.ui.hovered || this.data.ui.last_selected;
				if(o && o.length) {
					if(o.hasClass("jstree-closed")) { this.open_node(o); }
					else { this.hover_node(this._get_next(o)); }
				}
				return false;
			},
			"space" : function () { 
				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").click(); } 
				return false; 
			},
			"ctrl+space" : function (event) { 
				event.type = "click";
				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").trigger(event); } 
				return false; 
			},
			"shift+space" : function (event) { 
				event.type = "click";
				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").trigger(event); } 
				return false; 
			},
			"f2" : function () { this.rename(this.data.ui.hovered || this.data.ui.last_selected); },
			"del" : function () { this.remove(this.data.ui.hovered || this._get_node(null)); }
		},
		_fn : {
			enable_hotkeys : function () {
				this.data.hotkeys.enabled = true;
			},
			disable_hotkeys : function () {
				this.data.hotkeys.enabled = false;
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree JSON plugin
 * The JSON data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
 */
(function ($) {
	$.jstree.plugin("json_data", {
		__init : function() {
			var s = this._get_settings().json_data;
			if(s.progressive_unload) {
				this.get_container().bind("after_close.jstree", function (e, data) {
					data.rslt.obj.children("ul").remove();
				});
			}
		},
		defaults : { 
			// `data` can be a function:
			//  * accepts two arguments - node being loaded and a callback to pass the result to
			//  * will be executed in the current tree's scope & ajax won't be supported
			data : false, 
			ajax : false,
			correct_state : true,
			progressive_render : false,
			progressive_unload : false
		},
		_fn : {
			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_json(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
			_is_loaded : function (obj) { 
				var s = this._get_settings().json_data;
				obj = this._get_node(obj); 
				return obj == -1 || !obj || (!s.ajax && !s.progressive_render && !$.isFunction(s.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").length > 0;
			},
			refresh : function (obj) {
				obj = this._get_node(obj);
				var s = this._get_settings().json_data;
				if(obj && obj !== -1 && s.progressive_unload && ($.isFunction(s.data) || !!s.ajax)) {
					obj.removeData("jstree-children");
				}
				return this.__call_old();
			},
			load_node_json : function (obj, s_call, e_call) {
				var s = this.get_settings().json_data, d,
					error_func = function () {},
					success_func = function () {};
				obj = this._get_node(obj);

				if(obj && obj !== -1 && (s.progressive_render || s.progressive_unload) && !obj.is(".jstree-open, .jstree-leaf") && obj.children("ul").children("li").length === 0 && obj.data("jstree-children")) {
					d = this._parse_json(obj.data("jstree-children"), obj);
					if(d) {
						obj.append(d);
						if(!s.progressive_unload) { obj.removeData("jstree-children"); }
					}
					this.clean_node(obj);
					if(s_call) { s_call.call(this); }
					return;
				}

				if(obj && obj !== -1) {
					if(obj.data("jstree-is-loading")) { return; }
					else { obj.data("jstree-is-loading",true); }
				}
				switch(!0) {
					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
					// function option added here for easier model integration (also supporting async - see callback)
					case ($.isFunction(s.data)):
						s.data.call(this, obj, $.proxy(function (d) {
							d = this._parse_json(d, obj);
							if(!d) { 
								if(obj === -1 || !obj) {
									if(s.correct_state) { this.get_container().children("ul").empty(); }
								}
								else {
									obj.children("a.jstree-loading").removeClass("jstree-loading");
									obj.removeData("jstree-is-loading");
									if(s.correct_state) { this.correct_state(obj); }
								}
								if(e_call) { e_call.call(this); }
							}
							else {
								if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
								else { obj.append(d).children("a.jstree-loading").removeClass("jstree-loading"); obj.removeData("jstree-is-loading"); }
								this.clean_node(obj);
								if(s_call) { s_call.call(this); }
							}
						}, this));
						break;
					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
						if(!obj || obj == -1) {
							d = this._parse_json(s.data, obj);
							if(d) {
								this.get_container().children("ul").empty().append(d.children());
								this.clean_node();
							}
							else { 
								if(s.correct_state) { this.get_container().children("ul").empty(); }
							}
						}
						if(s_call) { s_call.call(this); }
						break;
					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
						error_func = function (x, t, e) {
							var ef = this.get_settings().json_data.ajax.error; 
							if(ef) { ef.call(this, x, t, e); }
							if(obj != -1 && obj.length) {
								obj.children("a.jstree-loading").removeClass("jstree-loading");
								obj.removeData("jstree-is-loading");
								if(t === "success" && s.correct_state) { this.correct_state(obj); }
							}
							else {
								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
							}
							if(e_call) { e_call.call(this); }
						};
						success_func = function (d, t, x) {
							var sf = this.get_settings().json_data.ajax.success; 
							if(sf) { d = sf.call(this,d,t,x) || d; }
							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "") || (!$.isArray(d) && !$.isPlainObject(d))) {
								return error_func.call(this, x, t, "");
							}
							d = this._parse_json(d, obj);
							if(d) {
								if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
								else { obj.append(d).children("a.jstree-loading").removeClass("jstree-loading"); obj.removeData("jstree-is-loading"); }
								this.clean_node(obj);
								if(s_call) { s_call.call(this); }
							}
							else {
								if(obj === -1 || !obj) {
									if(s.correct_state) { 
										this.get_container().children("ul").empty(); 
										if(s_call) { s_call.call(this); }
									}
								}
								else {
									obj.children("a.jstree-loading").removeClass("jstree-loading");
									obj.removeData("jstree-is-loading");
									if(s.correct_state) { 
										this.correct_state(obj);
										if(s_call) { s_call.call(this); } 
									}
								}
							}
						};
						s.ajax.context = this;
						s.ajax.error = error_func;
						s.ajax.success = success_func;
						if(!s.ajax.dataType) { s.ajax.dataType = "json"; }
						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
						$.ajax(s.ajax);
						break;
				}
			},
			_parse_json : function (js, obj, is_callback) {
				var d = false, 
					p = this._get_settings(),
					s = p.json_data,
					t = p.core.html_titles,
					tmp, i, j, ul1, ul2;

				if(!js) { return d; }
				if(s.progressive_unload && obj && obj !== -1) { 
					obj.data("jstree-children", d);
				}
				if($.isArray(js)) {
					d = $();
					if(!js.length) { return false; }
					for(i = 0, j = js.length; i < j; i++) {
						tmp = this._parse_json(js[i], obj, true);
						if(tmp.length) { d = d.add(tmp); }
					}
				}
				else {
					if(typeof js == "string") { js = { data : js }; }
					if(!js.data && js.data !== "") { return d; }
					d = $("<li />");
					if(js.attr) { d.attr(js.attr); }
					if(js.metadata) { d.data(js.metadata); }
					if(js.state) { d.addClass("jstree-" + js.state); }
					if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
					$.each(js.data, function (i, m) {
						tmp = $("<a />");
						if($.isFunction(m)) { m = m.call(this, js); }
						if(typeof m == "string") { tmp.attr('href','#')[ t ? "html" : "text" ](m); }
						else {
							if(!m.attr) { m.attr = {}; }
							if(!m.attr.href) { m.attr.href = '#'; }
							tmp.attr(m.attr)[ t ? "html" : "text" ](m.title);
							if(m.language) { tmp.addClass(m.language); }
						}
						tmp.prepend("<ins class='jstree-icon'>&#160;</ins>");
						if(!m.icon && js.icon) { m.icon = js.icon; }
						if(m.icon) { 
							if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
							else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
						}
						d.append(tmp);
					});
					d.prepend("<ins class='jstree-icon'>&#160;</ins>");
					if(js.children) { 
						if(s.progressive_render && js.state !== "open") {
							d.addClass("jstree-closed").data("jstree-children", js.children);
						}
						else {
							if(s.progressive_unload) { d.data("jstree-children", js.children); }
							if($.isArray(js.children) && js.children.length) {
								tmp = this._parse_json(js.children, obj, true);
								if(tmp.length) {
									ul2 = $("<ul />");
									ul2.append(tmp);
									d.append(ul2);
								}
							}
						}
					}
				}
				if(!is_callback) {
					ul1 = $("<ul />");
					ul1.append(d);
					d = ul1;
				}
				return d;
			},
			get_json : function (obj, li_attr, a_attr, is_callback) {
				var result = [], 
					s = this._get_settings(), 
					_this = this,
					tmp1, tmp2, li, a, t, lang;
				obj = this._get_node(obj);
				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
				if(!is_callback && this.data.types) { li_attr.push(s.types.type_attr); }
				a_attr = $.isArray(a_attr) ? a_attr : [ ];

				obj.each(function () {
					li = $(this);
					tmp1 = { data : [] };
					if(li_attr.length) { tmp1.attr = { }; }
					$.each(li_attr, function (i, v) { 
						tmp2 = li.attr(v); 
						if(tmp2 && tmp2.length && tmp2.replace(/jstree[^ ]*/ig,'').length) {
							tmp1.attr[v] = (" " + tmp2).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,""); 
						}
					});
					if(li.hasClass("jstree-open")) { tmp1.state = "open"; }
					if(li.hasClass("jstree-closed")) { tmp1.state = "closed"; }
					if(li.data()) { tmp1.metadata = li.data(); }
					a = li.children("a");
					a.each(function () {
						t = $(this);
						if(
							a_attr.length || 
							$.inArray("languages", s.plugins) !== -1 || 
							t.children("ins").get(0).style.backgroundImage.length || 
							(t.children("ins").get(0).className && t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').length)
						) { 
							lang = false;
							if($.inArray("languages", s.plugins) !== -1 && $.isArray(s.languages) && s.languages.length) {
								$.each(s.languages, function (l, lv) {
									if(t.hasClass(lv)) {
										lang = lv;
										return false;
									}
								});
							}
							tmp2 = { attr : { }, title : _this.get_text(t, lang) }; 
							$.each(a_attr, function (k, z) {
								tmp2.attr[z] = (" " + (t.attr(z) || "")).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"");
							});
							if($.inArray("languages", s.plugins) !== -1 && $.isArray(s.languages) && s.languages.length) {
								$.each(s.languages, function (k, z) {
									if(t.hasClass(z)) { tmp2.language = z; return true; }
								});
							}
							if(t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
								tmp2.icon = t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"");
							}
							if(t.children("ins").get(0).style.backgroundImage.length) {
								tmp2.icon = t.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","");
							}
						}
						else {
							tmp2 = _this.get_text(t);
						}
						if(a.length > 1) { tmp1.data.push(tmp2); }
						else { tmp1.data = tmp2; }
					});
					li = li.find("> ul > li");
					if(li.length) { tmp1.children = _this.get_json(li, li_attr, a_attr, true); }
					result.push(tmp1);
				});
				return result;
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree languages plugin
 * Adds support for multiple language versions in one tree
 * This basically allows for many titles coexisting in one node, but only one of them being visible at any given time
 * This is useful for maintaining the same structure in many languages (hence the name of the plugin)
 */
(function ($) {
	$.jstree.plugin("languages", {
		__init : function () { this._load_css();  },
		defaults : [],
		_fn : {
			set_lang : function (i) { 
				var langs = this._get_settings().languages,
					st = false,
					selector = ".jstree-" + this.get_index() + ' a';
				if(!$.isArray(langs) || langs.length === 0) { return false; }
				if($.inArray(i,langs) == -1) {
					if(!!langs[i]) { i = langs[i]; }
					else { return false; }
				}
				if(i == this.data.languages.current_language) { return true; }
				st = $.vakata.css.get_css(selector + "." + this.data.languages.current_language, false, this.data.languages.language_css);
				if(st !== false) { st.style.display = "none"; }
				st = $.vakata.css.get_css(selector + "." + i, false, this.data.languages.language_css);
				if(st !== false) { st.style.display = ""; }
				this.data.languages.current_language = i;
				this.__callback(i);
				return true;
			},
			get_lang : function () {
				return this.data.languages.current_language;
			},
			_get_string : function (key, lang) {
				var langs = this._get_settings().languages,
					s = this._get_settings().core.strings;
				if($.isArray(langs) && langs.length) {
					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
				}
				if(s[lang] && s[lang][key]) { return s[lang][key]; }
				if(s[key]) { return s[key]; }
				return key;
			},
			get_text : function (obj, lang) {
				obj = this._get_node(obj) || this.data.ui.last_selected;
				if(!obj.size()) { return false; }
				var langs = this._get_settings().languages,
					s = this._get_settings().core.html_titles;
				if($.isArray(langs) && langs.length) {
					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
					obj = obj.children("a." + lang);
				}
				else { obj = obj.children("a:eq(0)"); }
				if(s) {
					obj = obj.clone();
					obj.children("INS").remove();
					return obj.html();
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					return obj.nodeValue;
				}
			},
			set_text : function (obj, val, lang) {
				obj = this._get_node(obj) || this.data.ui.last_selected;
				if(!obj.size()) { return false; }
				var langs = this._get_settings().languages,
					s = this._get_settings().core.html_titles,
					tmp;
				if($.isArray(langs) && langs.length) {
					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
					obj = obj.children("a." + lang);
				}
				else { obj = obj.children("a:eq(0)"); }
				if(s) {
					tmp = obj.children("INS").clone();
					obj.html(val).prepend(tmp);
					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
					return true;
				}
				else {
					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
					return (obj.nodeValue = val);
				}
			},
			_load_css : function () {
				var langs = this._get_settings().languages,
					str = "/* languages css */",
					selector = ".jstree-" + this.get_index() + ' a',
					ln;
				if($.isArray(langs) && langs.length) {
					this.data.languages.current_language = langs[0];
					for(ln = 0; ln < langs.length; ln++) {
						str += selector + "." + langs[ln] + " {";
						if(langs[ln] != this.data.languages.current_language) { str += " display:none; "; }
						str += " } ";
					}
					this.data.languages.language_css = $.vakata.css.add_sheet({ 'str' : str, 'title' : "jstree-languages" });
				}
			},
			create_node : function (obj, position, js, callback) {
				var t = this.__call_old(true, obj, position, js, function (t) {
					var langs = this._get_settings().languages,
						a = t.children("a"),
						ln;
					if($.isArray(langs) && langs.length) {
						for(ln = 0; ln < langs.length; ln++) {
							if(!a.is("." + langs[ln])) {
								t.append(a.eq(0).clone().removeClass(langs.join(" ")).addClass(langs[ln]));
							}
						}
						a.not("." + langs.join(", .")).remove();
					}
					if(callback) { callback.call(this, t); }
				});
				return t;
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree cookies plugin
 * Stores the currently opened/selected nodes in a cookie and then restores them
 * Depends on the jquery.cookie plugin
 */
(function ($) {
	$.jstree.plugin("cookies", {
		__init : function () {
			if(typeof $.cookie === "undefined") { throw "jsTree cookie: jQuery cookie plugin not included."; }

			var s = this._get_settings().cookies,
				tmp;
			if(!!s.save_loaded) {
				tmp = $.cookie(s.save_loaded);
				if(tmp && tmp.length) { this.data.core.to_load = tmp.split(","); }
			}
			if(!!s.save_opened) {
				tmp = $.cookie(s.save_opened);
				if(tmp && tmp.length) { this.data.core.to_open = tmp.split(","); }
			}
			if(!!s.save_selected) {
				tmp = $.cookie(s.save_selected);
				if(tmp && tmp.length && this.data.ui) { this.data.ui.to_select = tmp.split(","); }
			}
			this.get_container()
				.one( ( this.data.ui ? "reselect" : "reopen" ) + ".jstree", $.proxy(function () {
					this.get_container()
						.bind("open_node.jstree close_node.jstree select_node.jstree deselect_node.jstree", $.proxy(function (e) { 
								if(this._get_settings().cookies.auto_save) { this.save_cookie((e.handleObj.namespace + e.handleObj.type).replace("jstree","")); }
							}, this));
				}, this));
		},
		defaults : {
			save_loaded		: "jstree_load",
			save_opened		: "jstree_open",
			save_selected	: "jstree_select",
			auto_save		: true,
			cookie_options	: {}
		},
		_fn : {
			save_cookie : function (c) {
				if(this.data.core.refreshing) { return; }
				var s = this._get_settings().cookies;
				if(!c) { // if called manually and not by event
					if(s.save_loaded) {
						this.save_loaded();
						$.cookie(s.save_loaded, this.data.core.to_load.join(","), s.cookie_options);
					}
					if(s.save_opened) {
						this.save_opened();
						$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options);
					}
					if(s.save_selected && this.data.ui) {
						this.save_selected();
						$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options);
					}
					return;
				}
				switch(c) {
					case "open_node":
					case "close_node":
						if(!!s.save_opened) { 
							this.save_opened(); 
							$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options); 
						}
						if(!!s.save_loaded) { 
							this.save_loaded(); 
							$.cookie(s.save_loaded, this.data.core.to_load.join(","), s.cookie_options); 
						}
						break;
					case "select_node":
					case "deselect_node":
						if(!!s.save_selected && this.data.ui) { 
							this.save_selected(); 
							$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options); 
						}
						break;
				}
			}
		}
	});
	// include cookies by default
	// $.jstree.defaults.plugins.push("cookies");
})(jQuery);
//*/

/*
 * jsTree sort plugin
 * Sorts items alphabetically (or using any other function)
 */
(function ($) {
	$.jstree.plugin("sort", {
		__init : function () {
			this.get_container()
				.bind("load_node.jstree", $.proxy(function (e, data) {
						var obj = this._get_node(data.rslt.obj);
						obj = obj === -1 ? this.get_container().children("ul") : obj.children("ul");
						this.sort(obj);
					}, this))
				.bind("rename_node.jstree create_node.jstree create.jstree", $.proxy(function (e, data) {
						this.sort(data.rslt.obj.parent());
					}, this))
				.bind("move_node.jstree", $.proxy(function (e, data) {
						var m = data.rslt.np == -1 ? this.get_container() : data.rslt.np;
						this.sort(m.children("ul"));
					}, this));
		},
		defaults : function (a, b) { return this.get_text(a) > this.get_text(b) ? 1 : -1; },
		_fn : {
			sort : function (obj) {
				var s = this._get_settings().sort,
					t = this;
				obj.append($.makeArray(obj.children("li")).sort($.proxy(s, t)));
				obj.find("> li > ul").each(function() { t.sort($(this)); });
				this.clean_node(obj);
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree DND plugin
 * Drag and drop plugin for moving/copying nodes
 */
(function ($) {
	var o = false,
		r = false,
		m = false,
		ml = false,
		sli = false,
		sti = false,
		dir1 = false,
		dir2 = false,
		last_pos = false;
	$.vakata.dnd = {
		is_down : false,
		is_drag : false,
		helper : false,
		scroll_spd : 10,
		init_x : 0,
		init_y : 0,
		threshold : 5,
		helper_left : 5,
		helper_top : 10,
		user_data : {},

		drag_start : function (e, data, html) { 
			if($.vakata.dnd.is_drag) { $.vakata.drag_stop({}); }
			try {
				e.currentTarget.unselectable = "on";
				e.currentTarget.onselectstart = function() { return false; };
				if(e.currentTarget.style) { e.currentTarget.style.MozUserSelect = "none"; }
			} catch(err) { }
			$.vakata.dnd.init_x = e.pageX;
			$.vakata.dnd.init_y = e.pageY;
			$.vakata.dnd.user_data = data;
			$.vakata.dnd.is_down = true;
			$.vakata.dnd.helper = $("<div id='vakata-dragged' />").html(html); //.fadeTo(10,0.25);
			$(document).bind("mousemove", $.vakata.dnd.drag);
			$(document).bind("mouseup", $.vakata.dnd.drag_stop);
			return false;
		},
		drag : function (e) { 
			if(!$.vakata.dnd.is_down) { return; }
			if(!$.vakata.dnd.is_drag) {
				if(Math.abs(e.pageX - $.vakata.dnd.init_x) > 5 || Math.abs(e.pageY - $.vakata.dnd.init_y) > 5) { 
					$.vakata.dnd.helper.appendTo("body");
					$.vakata.dnd.is_drag = true;
					$(document).triggerHandler("drag_start.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
				}
				else { return; }
			}

			// maybe use a scrolling parent element instead of document?
			if(e.type === "mousemove") { // thought of adding scroll in order to move the helper, but mouse poisition is n/a
				var d = $(document), t = d.scrollTop(), l = d.scrollLeft();
				if(e.pageY - t < 20) { 
					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
					if(!sti) { dir1 = "up"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() - $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
				}
				if($(window).height() - (e.pageY - t) < 20) {
					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
					if(!sti) { dir1 = "down"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() + $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
				}

				if(e.pageX - l < 20) {
					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
					if(!sli) { dir2 = "left"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() - $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
				}
				if($(window).width() - (e.pageX - l) < 20) {
					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
					if(!sli) { dir2 = "right"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() + $.vakata.dnd.scroll_spd); }, 150); }
				}
				else { 
					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
				}
			}

			$.vakata.dnd.helper.css({ left : (e.pageX + $.vakata.dnd.helper_left) + "px", top : (e.pageY + $.vakata.dnd.helper_top) + "px" });
			$(document).triggerHandler("drag.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
		},
		drag_stop : function (e) {
			if(sli) { clearInterval(sli); }
			if(sti) { clearInterval(sti); }
			$(document).unbind("mousemove", $.vakata.dnd.drag);
			$(document).unbind("mouseup", $.vakata.dnd.drag_stop);
			$(document).triggerHandler("drag_stop.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
			$.vakata.dnd.helper.remove();
			$.vakata.dnd.init_x = 0;
			$.vakata.dnd.init_y = 0;
			$.vakata.dnd.user_data = {};
			$.vakata.dnd.is_down = false;
			$.vakata.dnd.is_drag = false;
		}
	};
	$(function() {
		var css_string = '#vakata-dragged { display:block; margin:0 0 0 0; padding:4px 4px 4px 24px; position:absolute; top:-2000px; line-height:16px; z-index:10000; } ';
		$.vakata.css.add_sheet({ str : css_string, title : "vakata" });
	});

	$.jstree.plugin("dnd", {
		__init : function () {
			this.data.dnd = {
				active : false,
				after : false,
				inside : false,
				before : false,
				off : false,
				prepared : false,
				w : 0,
				to1 : false,
				to2 : false,
				cof : false,
				cw : false,
				ch : false,
				i1 : false,
				i2 : false,
				mto : false
			};
			this.get_container()
				.bind("mouseenter.jstree", $.proxy(function (e) {
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							if(this.data.themes) {
								m.attr("class", "jstree-" + this.data.themes.theme); 
								if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
								$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme);
							}
							//if($(e.currentTarget).find("> ul > li").length === 0) {
							if(e.currentTarget === e.target && $.vakata.dnd.user_data.obj && $($.vakata.dnd.user_data.obj).length && $($.vakata.dnd.user_data.obj).parents(".jstree:eq(0)")[0] !== e.target) { // node should not be from the same tree
								var tr = $.jstree._reference(e.target), dc;
								if(tr.data.dnd.foreign) {
									dc = tr._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
									if(dc === true || dc.inside === true || dc.before === true || dc.after === true) {
										$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
									}
								}
								else {
									tr.prepare_move(o, tr.get_container(), "last");
									if(tr.check_move()) {
										$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
									}
								}
							}
						}
					}, this))
				.bind("mouseup.jstree", $.proxy(function (e) {
						//if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && $(e.currentTarget).find("> ul > li").length === 0) {
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && e.currentTarget === e.target && $.vakata.dnd.user_data.obj && $($.vakata.dnd.user_data.obj).length && $($.vakata.dnd.user_data.obj).parents(".jstree:eq(0)")[0] !== e.target) { // node should not be from the same tree
							var tr = $.jstree._reference(e.currentTarget), dc;
							if(tr.data.dnd.foreign) {
								dc = tr._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
								if(dc === true || dc.inside === true || dc.before === true || dc.after === true) {
									tr._get_settings().dnd.drag_finish.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
								}
							}
							else {
								tr.move_node(o, tr.get_container(), "last", e[tr._get_settings().dnd.copy_modifier + "Key"]);
							}
						}
					}, this))
				.bind("mouseleave.jstree", $.proxy(function (e) {
						if(e.relatedTarget && e.relatedTarget.id && e.relatedTarget.id === "jstree-marker-line") {
							return false; 
						}
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
							if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
							if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
							if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
							if($.vakata.dnd.helper.children("ins").hasClass("jstree-ok")) {
								$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
							}
						}
					}, this))
				.bind("mousemove.jstree", $.proxy(function (e) {
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							var cnt = this.get_container()[0];

							// Horizontal scroll
							if(e.pageX + 24 > this.data.dnd.cof.left + this.data.dnd.cw) {
								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft += $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else if(e.pageX - 24 < this.data.dnd.cof.left) {
								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft -= $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else {
								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
							}

							// Vertical scroll
							if(e.pageY + 24 > this.data.dnd.cof.top + this.data.dnd.ch) {
								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop += $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else if(e.pageY - 24 < this.data.dnd.cof.top) {
								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop -= $.vakata.dnd.scroll_spd; }, cnt), 100);
							}
							else {
								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
							}

						}
					}, this))
				.bind("scroll.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && m && ml) {
							m.hide();
							ml.hide();
						}
					}, this))
				.delegate("a", "mousedown.jstree", $.proxy(function (e) { 
						if(e.which === 1) {
							this.start_drag(e.currentTarget, e);
							return false;
						}
					}, this))
				.delegate("a", "mouseenter.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							this.dnd_enter(e.currentTarget);
						}
					}, this))
				.delegate("a", "mousemove.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							if(!r || !r.length || r.children("a")[0] !== e.currentTarget) {
								this.dnd_enter(e.currentTarget);
							}
							if(typeof this.data.dnd.off.top === "undefined") { this.data.dnd.off = $(e.target).offset(); }
							this.data.dnd.w = (e.pageY - (this.data.dnd.off.top || 0)) % this.data.core.li_height;
							if(this.data.dnd.w < 0) { this.data.dnd.w += this.data.core.li_height; }
							this.dnd_show();
						}
					}, this))
				.delegate("a", "mouseleave.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							if(e.relatedTarget && e.relatedTarget.id && e.relatedTarget.id === "jstree-marker-line") {
								return false; 
							}
								if(m) { m.hide(); }
								if(ml) { ml.hide(); }
							/*
							var ec = $(e.currentTarget).closest("li"), 
								er = $(e.relatedTarget).closest("li");
							if(er[0] !== ec.prev()[0] && er[0] !== ec.next()[0]) {
								if(m) { m.hide(); }
								if(ml) { ml.hide(); }
							}
							*/
							this.data.dnd.mto = setTimeout( 
								(function (t) { return function () { t.dnd_leave(e); }; })(this),
							0);
						}
					}, this))
				.delegate("a", "mouseup.jstree", $.proxy(function (e) { 
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
							this.dnd_finish(e);
						}
					}, this));

			$(document)
				.bind("drag_stop.vakata", $.proxy(function () {
						if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
						if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
						if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
						if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
						this.data.dnd.after		= false;
						this.data.dnd.before	= false;
						this.data.dnd.inside	= false;
						this.data.dnd.off		= false;
						this.data.dnd.prepared	= false;
						this.data.dnd.w			= false;
						this.data.dnd.to1		= false;
						this.data.dnd.to2		= false;
						this.data.dnd.i1		= false;
						this.data.dnd.i2		= false;
						this.data.dnd.active	= false;
						this.data.dnd.foreign	= false;
						if(m) { m.css({ "top" : "-2000px" }); }
						if(ml) { ml.css({ "top" : "-2000px" }); }
					}, this))
				.bind("drag_start.vakata", $.proxy(function (e, data) {
						if(data.data.jstree) { 
							var et = $(data.event.target);
							if(et.closest(".jstree").hasClass("jstree-" + this.get_index())) {
								this.dnd_enter(et);
							}
						}
					}, this));
				/*
				.bind("keydown.jstree-" + this.get_index() + " keyup.jstree-" + this.get_index(), $.proxy(function(e) {
						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && !this.data.dnd.foreign) {
							var h = $.vakata.dnd.helper.children("ins");
							if(e[this._get_settings().dnd.copy_modifier + "Key"] && h.hasClass("jstree-ok")) {
								h.parent().html(h.parent().html().replace(/ \(Copy\)$/, "") + " (Copy)");
							} 
							else {
								h.parent().html(h.parent().html().replace(/ \(Copy\)$/, ""));
							}
						}
					}, this)); */



			var s = this._get_settings().dnd;
			if(s.drag_target) {
				$(document)
					.delegate(s.drag_target, "mousedown.jstree-" + this.get_index(), $.proxy(function (e) {
						o = e.target;
						$.vakata.dnd.drag_start(e, { jstree : true, obj : e.target }, "<ins class='jstree-icon'></ins>" + $(e.target).text() );
						if(this.data.themes) { 
							if(m) { m.attr("class", "jstree-" + this.data.themes.theme); }
							if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
							$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
						}
						$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
						var cnt = this.get_container();
						this.data.dnd.cof = cnt.offset();
						this.data.dnd.cw = parseInt(cnt.width(),10);
						this.data.dnd.ch = parseInt(cnt.height(),10);
						this.data.dnd.foreign = true;
						e.preventDefault();
					}, this));
			}
			if(s.drop_target) {
				$(document)
					.delegate(s.drop_target, "mouseenter.jstree-" + this.get_index(), $.proxy(function (e) {
							if(this.data.dnd.active && this._get_settings().dnd.drop_check.call(this, { "o" : o, "r" : $(e.target), "e" : e })) {
								$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
							}
						}, this))
					.delegate(s.drop_target, "mouseleave.jstree-" + this.get_index(), $.proxy(function (e) {
							if(this.data.dnd.active) {
								$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
							}
						}, this))
					.delegate(s.drop_target, "mouseup.jstree-" + this.get_index(), $.proxy(function (e) {
							if(this.data.dnd.active && $.vakata.dnd.helper.children("ins").hasClass("jstree-ok")) {
								this._get_settings().dnd.drop_finish.call(this, { "o" : o, "r" : $(e.target), "e" : e });
							}
						}, this));
			}
		},
		defaults : {
			copy_modifier	: "ctrl",
			check_timeout	: 100,
			open_timeout	: 500,
			drop_target		: ".jstree-drop",
			drop_check		: function (data) { return true; },
			drop_finish		: $.noop,
			drag_target		: ".jstree-draggable",
			drag_finish		: $.noop,
			drag_check		: function (data) { return { after : false, before : false, inside : true }; }
		},
		_fn : {
			dnd_prepare : function () {
				if(!r || !r.length) { return; }
				this.data.dnd.off = r.offset();
				if(this._get_settings().core.rtl) {
					this.data.dnd.off.right = this.data.dnd.off.left + r.width();
				}
				if(this.data.dnd.foreign) {
					var a = this._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : r });
					this.data.dnd.after = a.after;
					this.data.dnd.before = a.before;
					this.data.dnd.inside = a.inside;
					this.data.dnd.prepared = true;
					return this.dnd_show();
				}
				this.prepare_move(o, r, "before");
				this.data.dnd.before = this.check_move();
				this.prepare_move(o, r, "after");
				this.data.dnd.after = this.check_move();
				if(this._is_loaded(r)) {
					this.prepare_move(o, r, "inside");
					this.data.dnd.inside = this.check_move();
				}
				else {
					this.data.dnd.inside = false;
				}
				this.data.dnd.prepared = true;
				return this.dnd_show();
			},
			dnd_show : function () {
				if(!this.data.dnd.prepared) { return; }
				var o = ["before","inside","after"],
					r = false,
					rtl = this._get_settings().core.rtl,
					pos;
				if(this.data.dnd.w < this.data.core.li_height/3) { o = ["before","inside","after"]; }
				else if(this.data.dnd.w <= this.data.core.li_height*2/3) {
					o = this.data.dnd.w < this.data.core.li_height/2 ? ["inside","before","after"] : ["inside","after","before"];
				}
				else { o = ["after","inside","before"]; }
				$.each(o, $.proxy(function (i, val) { 
					if(this.data.dnd[val]) {
						$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
						r = val;
						return false;
					}
				}, this));
				if(r === false) { $.vakata.dnd.helper.children("ins").attr("class","jstree-invalid"); }
				
				pos = rtl ? (this.data.dnd.off.right - 18) : (this.data.dnd.off.left + 10);
				switch(r) {
					case "before":
						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top - 6) + "px" }).show();
						if(ml) { ml.css({ "left" : (pos + 8) + "px", "top" : (this.data.dnd.off.top - 1) + "px" }).show(); }
						break;
					case "after":
						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height - 6) + "px" }).show();
						if(ml) { ml.css({ "left" : (pos + 8) + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height - 1) + "px" }).show(); }
						break;
					case "inside":
						m.css({ "left" : pos + ( rtl ? -4 : 4) + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height/2 - 5) + "px" }).show();
						if(ml) { ml.hide(); }
						break;
					default:
						m.hide();
						if(ml) { ml.hide(); }
						break;
				}
				last_pos = r;
				return r;
			},
			dnd_open : function () {
				this.data.dnd.to2 = false;
				this.open_node(r, $.proxy(this.dnd_prepare,this), true);
			},
			dnd_finish : function (e) {
				if(this.data.dnd.foreign) {
					if(this.data.dnd.after || this.data.dnd.before || this.data.dnd.inside) {
						this._get_settings().dnd.drag_finish.call(this, { "o" : o, "r" : r, "p" : last_pos });
					}
				}
				else {
					this.dnd_prepare();
					this.move_node(o, r, last_pos, e[this._get_settings().dnd.copy_modifier + "Key"]);
				}
				o = false;
				r = false;
				m.hide();
				if(ml) { ml.hide(); }
			},
			dnd_enter : function (obj) {
				if(this.data.dnd.mto) { 
					clearTimeout(this.data.dnd.mto);
					this.data.dnd.mto = false;
				}
				var s = this._get_settings().dnd;
				this.data.dnd.prepared = false;
				r = this._get_node(obj);
				if(s.check_timeout) { 
					// do the calculations after a minimal timeout (users tend to drag quickly to the desired location)
					if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
					this.data.dnd.to1 = setTimeout($.proxy(this.dnd_prepare, this), s.check_timeout); 
				}
				else { 
					this.dnd_prepare(); 
				}
				if(s.open_timeout) { 
					if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
					if(r && r.length && r.hasClass("jstree-closed")) { 
						// if the node is closed - open it, then recalculate
						this.data.dnd.to2 = setTimeout($.proxy(this.dnd_open, this), s.open_timeout);
					}
				}
				else {
					if(r && r.length && r.hasClass("jstree-closed")) { 
						this.dnd_open();
					}
				}
			},
			dnd_leave : function (e) {
				this.data.dnd.after		= false;
				this.data.dnd.before	= false;
				this.data.dnd.inside	= false;
				$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
				m.hide();
				if(ml) { ml.hide(); }
				if(r && r[0] === e.target.parentNode) {
					if(this.data.dnd.to1) {
						clearTimeout(this.data.dnd.to1);
						this.data.dnd.to1 = false;
					}
					if(this.data.dnd.to2) {
						clearTimeout(this.data.dnd.to2);
						this.data.dnd.to2 = false;
					}
				}
			},
			start_drag : function (obj, e) {
				o = this._get_node(obj);
				if(this.data.ui && this.is_selected(o)) { o = this._get_node(null, true); }
				var dt = o.length > 1 ? this._get_string("multiple_selection") : this.get_text(o),
					cnt = this.get_container();
				if(!this._get_settings().core.html_titles) { dt = dt.replace(/</ig,"&lt;").replace(/>/ig,"&gt;"); }
				$.vakata.dnd.drag_start(e, { jstree : true, obj : o }, "<ins class='jstree-icon'></ins>" + dt );
				if(this.data.themes) { 
					if(m) { m.attr("class", "jstree-" + this.data.themes.theme); }
					if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
					$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
				}
				this.data.dnd.cof = cnt.offset();
				this.data.dnd.cw = parseInt(cnt.width(),10);
				this.data.dnd.ch = parseInt(cnt.height(),10);
				this.data.dnd.active = true;
			}
		}
	});
	$(function() {
		var css_string = '' + 
			'#vakata-dragged ins { display:block; text-decoration:none; width:16px; height:16px; margin:0 0 0 0; padding:0; position:absolute; top:4px; left:4px; ' + 
			' -moz-border-radius:4px; border-radius:4px; -webkit-border-radius:4px; ' +
			'} ' + 
			'#vakata-dragged .jstree-ok { background:green; } ' + 
			'#vakata-dragged .jstree-invalid { background:red; } ' + 
			'#jstree-marker { padding:0; margin:0; font-size:12px; overflow:hidden; height:12px; width:8px; position:absolute; top:-30px; z-index:10001; background-repeat:no-repeat; display:none; background-color:transparent; text-shadow:1px 1px 1px white; color:black; line-height:10px; } ' + 
			'#jstree-marker-line { padding:0; margin:0; line-height:0%; font-size:1px; overflow:hidden; height:1px; width:100px; position:absolute; top:-30px; z-index:10000; background-repeat:no-repeat; display:none; background-color:#456c43; ' + 
			' cursor:pointer; border:1px solid #eeeeee; border-left:0; -moz-box-shadow: 0px 0px 2px #666; -webkit-box-shadow: 0px 0px 2px #666; box-shadow: 0px 0px 2px #666; ' + 
			' -moz-border-radius:1px; border-radius:1px; -webkit-border-radius:1px; ' +
			'}' + 
			'';
		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
		m = $("<div />").attr({ id : "jstree-marker" }).hide().html("&raquo;")
			.bind("mouseleave mouseenter", function (e) { 
				m.hide();
				ml.hide();
				e.preventDefault(); 
				e.stopImmediatePropagation(); 
				return false; 
			})
			.appendTo("body");
		ml = $("<div />").attr({ id : "jstree-marker-line" }).hide()
			.bind("mouseup", function (e) { 
				if(r && r.length) { 
					r.children("a").trigger(e); 
					e.preventDefault(); 
					e.stopImmediatePropagation(); 
					return false; 
				} 
			})
			.bind("mouseleave", function (e) { 
				var rt = $(e.relatedTarget);
				if(rt.is(".jstree") || rt.closest(".jstree").length === 0) {
					if(r && r.length) { 
						r.children("a").trigger(e); 
						m.hide();
						ml.hide();
						e.preventDefault(); 
						e.stopImmediatePropagation(); 
						return false; 
					}
				}
			})
			.appendTo("body");
		$(document).bind("drag_start.vakata", function (e, data) {
			if(data.data.jstree) { m.show(); if(ml) { ml.show(); } }
		});
		$(document).bind("drag_stop.vakata", function (e, data) {
			if(data.data.jstree) { m.hide(); if(ml) { ml.hide(); } }
		});
	});
})(jQuery);
//*/

/*
 * jsTree checkbox plugin
 * Inserts checkboxes in front of every node
 * Depends on the ui plugin
 * DOES NOT WORK NICELY WITH MULTITREE DRAG'N'DROP
 */
(function ($) {
	$.jstree.plugin("checkbox", {
		__init : function () {
			this.data.checkbox.noui = this._get_settings().checkbox.override_ui;
			if(this.data.ui && this.data.checkbox.noui) {
				this.select_node = this.deselect_node = this.deselect_all = $.noop;
				this.get_selected = this.get_checked;
			}

			this.get_container()
				.bind("open_node.jstree create_node.jstree clean_node.jstree refresh.jstree", $.proxy(function (e, data) { 
						this._prepare_checkboxes(data.rslt.obj);
					}, this))
				.bind("loaded.jstree", $.proxy(function (e) {
						this._prepare_checkboxes();
					}, this))
				.delegate( (this.data.ui && this.data.checkbox.noui ? "a" : "ins.jstree-checkbox") , "click.jstree", $.proxy(function (e) {
						e.preventDefault();
						if(this._get_node(e.target).hasClass("jstree-checked")) { this.uncheck_node(e.target); }
						else { this.check_node(e.target); }
						if(this.data.ui && this.data.checkbox.noui) {
							this.save_selected();
							if(this.data.cookies) { this.save_cookie("select_node"); }
						}
						else {
							e.stopImmediatePropagation();
							return false;
						}
					}, this));
		},
		defaults : {
			override_ui : false,
			two_state : false,
			real_checkboxes : false,
			checked_parent_open : true,
			real_checkboxes_names : function (n) { return [ ("check_" + (n[0].id || Math.ceil(Math.random() * 10000))) , 1]; }
		},
		__destroy : function () {
			this.get_container()
				.find("input.jstree-real-checkbox").removeClass("jstree-real-checkbox").end()
				.find("ins.jstree-checkbox").remove();
		},
		_fn : {
			_checkbox_notify : function (n, data) {
				if(data.checked) {
					this.check_node(n, false);
				}
			},
			_prepare_checkboxes : function (obj) {
				obj = !obj || obj == -1 ? this.get_container().find("> ul > li") : this._get_node(obj);
				if(obj === false) { return; } // added for removing root nodes
				var c, _this = this, t, ts = this._get_settings().checkbox.two_state, rc = this._get_settings().checkbox.real_checkboxes, rcn = this._get_settings().checkbox.real_checkboxes_names;
				obj.each(function () {
					t = $(this);
					c = t.is("li") && (t.hasClass("jstree-checked") || (rc && t.children(":checked").length)) ? "jstree-checked" : "jstree-unchecked";
					t.find("li").andSelf().each(function () {
						var $t = $(this), nm;
						$t.children("a" + (_this.data.languages ? "" : ":eq(0)") ).not(":has(.jstree-checkbox)").prepend("<ins class='jstree-checkbox'>&#160;</ins>").parent().not(".jstree-checked, .jstree-unchecked").addClass( ts ? "jstree-unchecked" : c );
						if(rc) {
							if(!$t.children(":checkbox").length) {
								nm = rcn.call(_this, $t);
								$t.prepend("<input type='checkbox' class='jstree-real-checkbox' id='" + nm[0] + "' name='" + nm[0] + "' value='" + nm[1] + "' />");
							}
							else {
								$t.children(":checkbox").addClass("jstree-real-checkbox");
							}
							if(c === "jstree-checked") { 
								$t.children(":checkbox").attr("checked","checked"); 
							}
						}
						if(c === "jstree-checked" && !ts) {
							$t.find("li").addClass("jstree-checked");
						}
					});
				});
				if(!ts) {
					if(obj.length === 1 && obj.is("li")) { this._repair_state(obj); }
					if(obj.is("li")) { obj.each(function () { _this._repair_state(this); }); }
					else { obj.find("> ul > li").each(function () { _this._repair_state(this); }); }
					obj.find(".jstree-checked").parent().parent().each(function () { _this._repair_state(this); }); 
				}
			},
			change_state : function (obj, state) {
				obj = this._get_node(obj);
				var coll = false, rc = this._get_settings().checkbox.real_checkboxes;
				if(!obj || obj === -1) { return false; }
				state = (state === false || state === true) ? state : obj.hasClass("jstree-checked");
				if(this._get_settings().checkbox.two_state) {
					if(state) { 
						obj.removeClass("jstree-checked").addClass("jstree-unchecked"); 
						if(rc) { obj.children(":checkbox").removeAttr("checked"); }
					}
					else { 
						obj.removeClass("jstree-unchecked").addClass("jstree-checked"); 
						if(rc) { obj.children(":checkbox").attr("checked","checked"); }
					}
				}
				else {
					if(state) { 
						coll = obj.find("li").andSelf();
						if(!coll.filter(".jstree-checked, .jstree-undetermined").length) { return false; }
						coll.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked"); 
						if(rc) { coll.children(":checkbox").removeAttr("checked"); }
					}
					else { 
						coll = obj.find("li").andSelf();
						if(!coll.filter(".jstree-unchecked, .jstree-undetermined").length) { return false; }
						coll.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked"); 
						if(rc) { coll.children(":checkbox").attr("checked","checked"); }
						if(this.data.ui) { this.data.ui.last_selected = obj; }
						this.data.checkbox.last_selected = obj;
					}
					obj.parentsUntil(".jstree", "li").each(function () {
						var $this = $(this);
						if(state) {
							if($this.children("ul").children("li.jstree-checked, li.jstree-undetermined").length) {
								$this.parentsUntil(".jstree", "li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
								if(rc) { $this.parentsUntil(".jstree", "li").andSelf().children(":checkbox").removeAttr("checked"); }
								return false;
							}
							else {
								$this.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked");
								if(rc) { $this.children(":checkbox").removeAttr("checked"); }
							}
						}
						else {
							if($this.children("ul").children("li.jstree-unchecked, li.jstree-undetermined").length) {
								$this.parentsUntil(".jstree", "li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
								if(rc) { $this.parentsUntil(".jstree", "li").andSelf().children(":checkbox").removeAttr("checked"); }
								return false;
							}
							else {
								$this.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked");
								if(rc) { $this.children(":checkbox").attr("checked","checked"); }
							}
						}
					});
				}
				if(this.data.ui && this.data.checkbox.noui) { this.data.ui.selected = this.get_checked(); }
				this.__callback(obj);
				return true;
			},
			check_node : function (obj) {
				if(this.change_state(obj, false)) { 
					obj = this._get_node(obj);
					if(this._get_settings().checkbox.checked_parent_open) {
						var t = this;
						obj.parents(".jstree-closed").each(function () { t.open_node(this, false, true); });
					}
					this.__callback({ "obj" : obj }); 
				}
			},
			uncheck_node : function (obj) {
				if(this.change_state(obj, true)) { this.__callback({ "obj" : this._get_node(obj) }); }
			},
			check_all : function () {
				var _this = this, 
					coll = this._get_settings().checkbox.two_state ? this.get_container_ul().find("li") : this.get_container_ul().children("li");
				coll.each(function () {
					_this.change_state(this, false);
				});
				this.__callback();
			},
			uncheck_all : function () {
				var _this = this,
					coll = this._get_settings().checkbox.two_state ? this.get_container_ul().find("li") : this.get_container_ul().children("li");
				coll.each(function () {
					_this.change_state(this, true);
				});
				this.__callback();
			},

			is_checked : function(obj) {
				obj = this._get_node(obj);
				return obj.length ? obj.is(".jstree-checked") : false;
			},
			get_checked : function (obj, get_all) {
				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
				return get_all || this._get_settings().checkbox.two_state ? obj.find(".jstree-checked") : obj.find("> ul > .jstree-checked, .jstree-undetermined > ul > .jstree-checked");
			},
			get_unchecked : function (obj, get_all) { 
				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
				return get_all || this._get_settings().checkbox.two_state ? obj.find(".jstree-unchecked") : obj.find("> ul > .jstree-unchecked, .jstree-undetermined > ul > .jstree-unchecked");
			},

			show_checkboxes : function () { this.get_container().children("ul").removeClass("jstree-no-checkboxes"); },
			hide_checkboxes : function () { this.get_container().children("ul").addClass("jstree-no-checkboxes"); },

			_repair_state : function (obj) {
				obj = this._get_node(obj);
				if(!obj.length) { return; }
				var rc = this._get_settings().checkbox.real_checkboxes,
					a = obj.find("> ul > .jstree-checked").length,
					b = obj.find("> ul > .jstree-undetermined").length,
					c = obj.find("> ul > li").length;
				if(c === 0) { if(obj.hasClass("jstree-undetermined")) { this.change_state(obj, false); } }
				else if(a === 0 && b === 0) { this.change_state(obj, true); }
				else if(a === c) { this.change_state(obj, false); }
				else { 
					obj.parentsUntil(".jstree","li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
					if(rc) { obj.parentsUntil(".jstree", "li").andSelf().children(":checkbox").removeAttr("checked"); }
				}
			},
			reselect : function () {
				if(this.data.ui && this.data.checkbox.noui) { 
					var _this = this,
						s = this.data.ui.to_select;
					s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
					this.deselect_all();
					$.each(s, function (i, val) { _this.check_node(val); });
					this.__callback();
				}
				else { 
					this.__call_old(); 
				}
			},
			save_loaded : function () {
				var _this = this;
				this.data.core.to_load = [];
				this.get_container_ul().find("li.jstree-closed.jstree-undetermined").each(function () {
					if(this.id) { _this.data.core.to_load.push("#" + this.id); }
				});
			}
		}
	});
	$(function() {
		var css_string = '.jstree .jstree-real-checkbox { display:none; } ';
		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
	});
})(jQuery);
//*/

/* 
 * jsTree XML plugin
 * The XML data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
 */
(function ($) {
	$.vakata.xslt = function (xml, xsl, callback) {
		var rs = "", xm, xs, processor, support;
		// TODO: IE9 no XSLTProcessor, no document.recalc
		if(document.recalc) {
			xm = document.createElement('xml');
			xs = document.createElement('xml');
			xm.innerHTML = xml;
			xs.innerHTML = xsl;
			$("body").append(xm).append(xs);
			setTimeout( (function (xm, xs, callback) {
				return function () {
					callback.call(null, xm.transformNode(xs.XMLDocument));
					setTimeout( (function (xm, xs) { return function () { $(xm).remove(); $(xs).remove(); }; })(xm, xs), 200);
				};
			})(xm, xs, callback), 100);
			return true;
		}
		if(typeof window.DOMParser !== "undefined" && typeof window.XMLHttpRequest !== "undefined" && typeof window.XSLTProcessor === "undefined") {
			xml = new DOMParser().parseFromString(xml, "text/xml");
			xsl = new DOMParser().parseFromString(xsl, "text/xml");
			// alert(xml.transformNode());
			// callback.call(null, new XMLSerializer().serializeToString(rs));
			
		}
		if(typeof window.DOMParser !== "undefined" && typeof window.XMLHttpRequest !== "undefined" && typeof window.XSLTProcessor !== "undefined") {
			processor = new XSLTProcessor();
			support = $.isFunction(processor.transformDocument) ? (typeof window.XMLSerializer !== "undefined") : true;
			if(!support) { return false; }
			xml = new DOMParser().parseFromString(xml, "text/xml");
			xsl = new DOMParser().parseFromString(xsl, "text/xml");
			if($.isFunction(processor.transformDocument)) {
				rs = document.implementation.createDocument("", "", null);
				processor.transformDocument(xml, xsl, rs, null);
				callback.call(null, new XMLSerializer().serializeToString(rs));
				return true;
			}
			else {
				processor.importStylesheet(xsl);
				rs = processor.transformToFragment(xml, document);
				callback.call(null, $("<div />").append(rs).html());
				return true;
			}
		}
		return false;
	};
	var xsl = {
		'nest' : '<' + '?xml version="1.0" encoding="utf-8" ?>' + 
			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/html" />' + 
			'<xsl:template match="/">' + 
			'	<xsl:call-template name="nodes">' + 
			'		<xsl:with-param name="node" select="/root" />' + 
			'	</xsl:call-template>' + 
			'</xsl:template>' + 
			'<xsl:template name="nodes">' + 
			'	<xsl:param name="node" />' + 
			'	<ul>' + 
			'	<xsl:for-each select="$node/item">' + 
			'		<xsl:variable name="children" select="count(./item) &gt; 0" />' + 
			'		<li>' + 
			'			<xsl:attribute name="class">' + 
			'				<xsl:if test="position() = last()">jstree-last </xsl:if>' + 
			'				<xsl:choose>' + 
			'					<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
			'					<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
			'					<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
			'				</xsl:choose>' + 
			'				<xsl:value-of select="@class" />' + 
			'			</xsl:attribute>' + 
			'			<xsl:for-each select="@*">' + 
			'				<xsl:if test="name() != \'class\' and name() != \'state\' and name() != \'hasChildren\'">' + 
			'					<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'				</xsl:if>' + 
			'			</xsl:for-each>' + 
			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
			'			<xsl:for-each select="content/name">' + 
			'				<a>' + 
			'				<xsl:attribute name="href">' + 
			'					<xsl:choose>' + 
			'					<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
			'					<xsl:otherwise>#</xsl:otherwise>' + 
			'					</xsl:choose>' + 
			'				</xsl:attribute>' + 
			'				<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
			'				<xsl:attribute name="style"><xsl:value-of select="@style" /></xsl:attribute>' + 
			'				<xsl:for-each select="@*">' + 
			'					<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
			'						<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'					</xsl:if>' + 
			'				</xsl:for-each>' + 
			'					<ins>' + 
			'						<xsl:attribute name="class">jstree-icon ' + 
			'							<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
			'						</xsl:attribute>' + 
			'						<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
			'						<xsl:text>&#xa0;</xsl:text>' + 
			'					</ins>' + 
			'					<xsl:copy-of select="./child::node()" />' + 
			'				</a>' + 
			'			</xsl:for-each>' + 
			'			<xsl:if test="$children or @hasChildren"><xsl:call-template name="nodes"><xsl:with-param name="node" select="current()" /></xsl:call-template></xsl:if>' + 
			'		</li>' + 
			'	</xsl:for-each>' + 
			'	</ul>' + 
			'</xsl:template>' + 
			'</xsl:stylesheet>',

		'flat' : '<' + '?xml version="1.0" encoding="utf-8" ?>' + 
			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/xml" />' + 
			'<xsl:template match="/">' + 
			'	<ul>' + 
			'	<xsl:for-each select="//item[not(@parent_id) or @parent_id=0 or not(@parent_id = //item/@id)]">' + /* the last `or` may be removed */
			'		<xsl:call-template name="nodes">' + 
			'			<xsl:with-param name="node" select="." />' + 
			'			<xsl:with-param name="is_last" select="number(position() = last())" />' + 
			'		</xsl:call-template>' + 
			'	</xsl:for-each>' + 
			'	</ul>' + 
			'</xsl:template>' + 
			'<xsl:template name="nodes">' + 
			'	<xsl:param name="node" />' + 
			'	<xsl:param name="is_last" />' + 
			'	<xsl:variable name="children" select="count(//item[@parent_id=$node/attribute::id]) &gt; 0" />' + 
			'	<li>' + 
			'	<xsl:attribute name="class">' + 
			'		<xsl:if test="$is_last = true()">jstree-last </xsl:if>' + 
			'		<xsl:choose>' + 
			'			<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
			'			<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
			'			<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
			'		</xsl:choose>' + 
			'		<xsl:value-of select="@class" />' + 
			'	</xsl:attribute>' + 
			'	<xsl:for-each select="@*">' + 
			'		<xsl:if test="name() != \'parent_id\' and name() != \'hasChildren\' and name() != \'class\' and name() != \'state\'">' + 
			'		<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'		</xsl:if>' + 
			'	</xsl:for-each>' + 
			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
			'	<xsl:for-each select="content/name">' + 
			'		<a>' + 
			'		<xsl:attribute name="href">' + 
			'			<xsl:choose>' + 
			'			<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
			'			<xsl:otherwise>#</xsl:otherwise>' + 
			'			</xsl:choose>' + 
			'		</xsl:attribute>' + 
			'		<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
			'		<xsl:attribute name="style"><xsl:value-of select="@style" /></xsl:attribute>' + 
			'		<xsl:for-each select="@*">' + 
			'			<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
			'				<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
			'			</xsl:if>' + 
			'		</xsl:for-each>' + 
			'			<ins>' + 
			'				<xsl:attribute name="class">jstree-icon ' + 
			'					<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
			'				</xsl:attribute>' + 
			'				<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
			'				<xsl:text>&#xa0;</xsl:text>' + 
			'			</ins>' + 
			'			<xsl:copy-of select="./child::node()" />' + 
			'		</a>' + 
			'	</xsl:for-each>' + 
			'	<xsl:if test="$children">' + 
			'		<ul>' + 
			'		<xsl:for-each select="//item[@parent_id=$node/attribute::id]">' + 
			'			<xsl:call-template name="nodes">' + 
			'				<xsl:with-param name="node" select="." />' + 
			'				<xsl:with-param name="is_last" select="number(position() = last())" />' + 
			'			</xsl:call-template>' + 
			'		</xsl:for-each>' + 
			'		</ul>' + 
			'	</xsl:if>' + 
			'	</li>' + 
			'</xsl:template>' + 
			'</xsl:stylesheet>'
	},
	escape_xml = function(string) {
		return string
			.toString()
			.replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/"/g, '&quot;')
			.replace(/'/g, '&apos;');
	};
	$.jstree.plugin("xml_data", {
		defaults : { 
			data : false,
			ajax : false,
			xsl : "flat",
			clean_node : false,
			correct_state : true,
			get_skip_empty : false,
			get_include_preamble : true
		},
		_fn : {
			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_xml(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
			_is_loaded : function (obj) { 
				var s = this._get_settings().xml_data;
				obj = this._get_node(obj);
				return obj == -1 || !obj || (!s.ajax && !$.isFunction(s.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
			},
			load_node_xml : function (obj, s_call, e_call) {
				var s = this.get_settings().xml_data,
					error_func = function () {},
					success_func = function () {};

				obj = this._get_node(obj);
				if(obj && obj !== -1) {
					if(obj.data("jstree-is-loading")) { return; }
					else { obj.data("jstree-is-loading",true); }
				}
				switch(!0) {
					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
					case ($.isFunction(s.data)):
						s.data.call(this, obj, $.proxy(function (d) {
							this.parse_xml(d, $.proxy(function (d) {
								if(d) {
									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
									if(d.length > 10) {
										d = $(d);
										if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
										else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d); obj.removeData("jstree-is-loading"); }
										if(s.clean_node) { this.clean_node(obj); }
										if(s_call) { s_call.call(this); }
									}
									else {
										if(obj && obj !== -1) { 
											obj.children("a.jstree-loading").removeClass("jstree-loading");
											obj.removeData("jstree-is-loading");
											if(s.correct_state) { 
												this.correct_state(obj);
												if(s_call) { s_call.call(this); } 
											}
										}
										else {
											if(s.correct_state) { 
												this.get_container().children("ul").empty();
												if(s_call) { s_call.call(this); } 
											}
										}
									}
								}
							}, this));
						}, this));
						break;
					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
						if(!obj || obj == -1) {
							this.parse_xml(s.data, $.proxy(function (d) {
								if(d) {
									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
									if(d.length > 10) {
										d = $(d);
										this.get_container().children("ul").empty().append(d.children());
										if(s.clean_node) { this.clean_node(obj); }
										if(s_call) { s_call.call(this); }
									}
								}
								else { 
									if(s.correct_state) { 
										this.get_container().children("ul").empty(); 
										if(s_call) { s_call.call(this); }
									}
								}
							}, this));
						}
						break;
					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
						error_func = function (x, t, e) {
							var ef = this.get_settings().xml_data.ajax.error; 
							if(ef) { ef.call(this, x, t, e); }
							if(obj !== -1 && obj.length) {
								obj.children("a.jstree-loading").removeClass("jstree-loading");
								obj.removeData("jstree-is-loading");
								if(t === "success" && s.correct_state) { this.correct_state(obj); }
							}
							else {
								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
							}
							if(e_call) { e_call.call(this); }
						};
						success_func = function (d, t, x) {
							d = x.responseText;
							var sf = this.get_settings().xml_data.ajax.success; 
							if(sf) { d = sf.call(this,d,t,x) || d; }
							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "")) {
								return error_func.call(this, x, t, "");
							}
							this.parse_xml(d, $.proxy(function (d) {
								if(d) {
									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
									if(d.length > 10) {
										d = $(d);
										if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
										else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d); obj.removeData("jstree-is-loading"); }
										if(s.clean_node) { this.clean_node(obj); }
										if(s_call) { s_call.call(this); }
									}
									else {
										if(obj && obj !== -1) { 
											obj.children("a.jstree-loading").removeClass("jstree-loading");
											obj.removeData("jstree-is-loading");
											if(s.correct_state) { 
												this.correct_state(obj);
												if(s_call) { s_call.call(this); } 
											}
										}
										else {
											if(s.correct_state) { 
												this.get_container().children("ul").empty();
												if(s_call) { s_call.call(this); } 
											}
										}
									}
								}
							}, this));
						};
						s.ajax.context = this;
						s.ajax.error = error_func;
						s.ajax.success = success_func;
						if(!s.ajax.dataType) { s.ajax.dataType = "xml"; }
						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
						$.ajax(s.ajax);
						break;
				}
			},
			parse_xml : function (xml, callback) {
				var s = this._get_settings().xml_data;
				$.vakata.xslt(xml, xsl[s.xsl], callback);
			},
			get_xml : function (tp, obj, li_attr, a_attr, is_callback) {
				var result = "", 
					s = this._get_settings(), 
					_this = this,
					tmp1, tmp2, li, a, lang;
				if(!tp) { tp = "flat"; }
				if(!is_callback) { is_callback = 0; }
				obj = this._get_node(obj);
				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
				if(!is_callback && this.data.types && $.inArray(s.types.type_attr, li_attr) === -1) { li_attr.push(s.types.type_attr); }

				a_attr = $.isArray(a_attr) ? a_attr : [ ];

				if(!is_callback) { 
					if(s.xml_data.get_include_preamble) { 
						result += '<' + '?xml version="1.0" encoding="UTF-8"?' + '>'; 
					}
					result += "<root>"; 
				}
				obj.each(function () {
					result += "<item";
					li = $(this);
					$.each(li_attr, function (i, v) { 
						var t = li.attr(v);
						if(!s.xml_data.get_skip_empty || typeof t !== "undefined") {
							result += " " + v + "=\"" + escape_xml((" " + (t || "")).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + "\""; 
						}
					});
					if(li.hasClass("jstree-open")) { result += " state=\"open\""; }
					if(li.hasClass("jstree-closed")) { result += " state=\"closed\""; }
					if(tp === "flat") { result += " parent_id=\"" + escape_xml(is_callback) + "\""; }
					result += ">";
					result += "<content>";
					a = li.children("a");
					a.each(function () {
						tmp1 = $(this);
						lang = false;
						result += "<name";
						if($.inArray("languages", s.plugins) !== -1) {
							$.each(s.languages, function (k, z) {
								if(tmp1.hasClass(z)) { result += " lang=\"" + escape_xml(z) + "\""; lang = z; return false; }
							});
						}
						if(a_attr.length) { 
							$.each(a_attr, function (k, z) {
								var t = tmp1.attr(z);
								if(!s.xml_data.get_skip_empty || typeof t !== "undefined") {
									result += " " + z + "=\"" + escape_xml((" " + t || "").replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + "\"";
								}
							});
						}
						if(tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
							result += ' icon="' + escape_xml(tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + '"';
						}
						if(tmp1.children("ins").get(0).style.backgroundImage.length) {
							result += ' icon="' + escape_xml(tmp1.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","").replace(/'/ig,"").replace(/"/ig,"")) + '"';
						}
						result += ">";
						result += "<![CDATA[" + _this.get_text(tmp1, lang) + "]]>";
						result += "</name>";
					});
					result += "</content>";
					tmp2 = li[0].id || true;
					li = li.find("> ul > li");
					if(li.length) { tmp2 = _this.get_xml(tp, li, li_attr, a_attr, tmp2); }
					else { tmp2 = ""; }
					if(tp == "nest") { result += tmp2; }
					result += "</item>";
					if(tp == "flat") { result += tmp2; }
				});
				if(!is_callback) { result += "</root>"; }
				return result;
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree search plugin
 * Enables both sync and async search on the tree
 * DOES NOT WORK WITH JSON PROGRESSIVE RENDER
 */
(function ($) {
	$.expr[':'].jstree_contains = function(a,i,m){
		return (a.textContent || a.innerText || "").toLowerCase().indexOf(m[3].toLowerCase())>=0;
	};
	$.expr[':'].jstree_title_contains = function(a,i,m) {
		return (a.getAttribute("title") || "").toLowerCase().indexOf(m[3].toLowerCase())>=0;
	};
	$.jstree.plugin("search", {
		__init : function () {
			this.data.search.str = "";
			this.data.search.result = $();
			if(this._get_settings().search.show_only_matches) {
				this.get_container()
					.bind("search.jstree", function (e, data) {
						$(this).children("ul").find("li").hide().removeClass("jstree-last");
						data.rslt.nodes.parentsUntil(".jstree").andSelf().show()
							.filter("ul").each(function () { $(this).children("li:visible").eq(-1).addClass("jstree-last"); });
					})
					.bind("clear_search.jstree", function () {
						$(this).children("ul").find("li").css("display","").end().end().jstree("clean_node", -1);
					});
			}
		},
		defaults : {
			ajax : false,
			search_method : "jstree_contains", // for case insensitive - jstree_contains
			show_only_matches : false
		},
		_fn : {
			search : function (str, skip_async) {
				if($.trim(str) === "") { this.clear_search(); return; }
				var s = this.get_settings().search, 
					t = this,
					error_func = function () { },
					success_func = function () { };
				this.data.search.str = str;

				if(!skip_async && s.ajax !== false && this.get_container_ul().find("li.jstree-closed:not(:has(ul)):eq(0)").length > 0) {
					this.search.supress_callback = true;
					error_func = function () { };
					success_func = function (d, t, x) {
						var sf = this.get_settings().search.ajax.success; 
						if(sf) { d = sf.call(this,d,t,x) || d; }
						this.data.search.to_open = d;
						this._search_open();
					};
					s.ajax.context = this;
					s.ajax.error = error_func;
					s.ajax.success = success_func;
					if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, str); }
					if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, str); }
					if(!s.ajax.data) { s.ajax.data = { "search_string" : str }; }
					if(!s.ajax.dataType || /^json/.exec(s.ajax.dataType)) { s.ajax.dataType = "json"; }
					$.ajax(s.ajax);
					return;
				}
				if(this.data.search.result.length) { this.clear_search(); }
				this.data.search.result = this.get_container().find("a" + (this.data.languages ? "." + this.get_lang() : "" ) + ":" + (s.search_method) + "(" + this.data.search.str + ")");
				this.data.search.result.addClass("jstree-search").parent().parents(".jstree-closed").each(function () {
					t.open_node(this, false, true);
				});
				this.__callback({ nodes : this.data.search.result, str : str });
			},
			clear_search : function (str) {
				this.data.search.result.removeClass("jstree-search");
				this.__callback(this.data.search.result);
				this.data.search.result = $();
			},
			_search_open : function (is_callback) {
				var _this = this,
					done = true,
					current = [],
					remaining = [];
				if(this.data.search.to_open.length) {
					$.each(this.data.search.to_open, function (i, val) {
						if(val == "#") { return true; }
						if($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
						else { remaining.push(val); }
					});
					if(current.length) {
						this.data.search.to_open = remaining;
						$.each(current, function (i, val) { 
							_this.open_node(val, function () { _this._search_open(true); }); 
						});
						done = false;
					}
				}
				if(done) { this.search(this.data.search.str, true); }
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree contextmenu plugin
 */
(function ($) {
	$.vakata.context = {
		hide_on_mouseleave : false,

		cnt		: $("<div id='vakata-contextmenu' />"),
		vis		: false,
		tgt		: false,
		par		: false,
		func	: false,
		data	: false,
		rtl		: false,
		show	: function (s, t, x, y, d, p, rtl) {
			$.vakata.context.rtl = !!rtl;
			var html = $.vakata.context.parse(s), h, w;
			if(!html) { return; }
			$.vakata.context.vis = true;
			$.vakata.context.tgt = t;
			$.vakata.context.par = p || t || null;
			$.vakata.context.data = d || null;
			$.vakata.context.cnt
				.html(html)
				.css({ "visibility" : "hidden", "display" : "block", "left" : 0, "top" : 0 });

			if($.vakata.context.hide_on_mouseleave) {
				$.vakata.context.cnt
					.one("mouseleave", function(e) { $.vakata.context.hide(); });
			}

			h = $.vakata.context.cnt.height();
			w = $.vakata.context.cnt.width();
			if(x + w > $(document).width()) { 
				x = $(document).width() - (w + 5); 
				$.vakata.context.cnt.find("li > ul").addClass("right"); 
			}
			if(y + h > $(document).height()) { 
				y = y - (h + t[0].offsetHeight); 
				$.vakata.context.cnt.find("li > ul").addClass("bottom"); 
			}

			$.vakata.context.cnt
				.css({ "left" : x, "top" : y })
				.find("li:has(ul)")
					.bind("mouseenter", function (e) { 
						var w = $(document).width(),
							h = $(document).height(),
							ul = $(this).children("ul").show(); 
						if(w !== $(document).width()) { ul.toggleClass("right"); }
						if(h !== $(document).height()) { ul.toggleClass("bottom"); }
					})
					.bind("mouseleave", function (e) { 
						$(this).children("ul").hide(); 
					})
					.end()
				.css({ "visibility" : "visible" })
				.show();
			$(document).triggerHandler("context_show.vakata");
		},
		hide	: function () {
			$.vakata.context.vis = false;
			$.vakata.context.cnt.attr("class","").css({ "visibility" : "hidden" });
			$(document).triggerHandler("context_hide.vakata");
		},
		parse	: function (s, is_callback) {
			if(!s) { return false; }
			var str = "",
				tmp = false,
				was_sep = true;
			if(!is_callback) { $.vakata.context.func = {}; }
			str += "<ul>";
			$.each(s, function (i, val) {
				if(!val) { return true; }
				$.vakata.context.func[i] = val.action;
				if(!was_sep && val.separator_before) {
					str += "<li class='vakata-separator vakata-separator-before'></li>";
				}
				was_sep = false;
				str += "<li class='" + (val._class || "") + (val._disabled ? " jstree-contextmenu-disabled " : "") + "'><ins ";
				if(val.icon && val.icon.indexOf("/") === -1) { str += " class='" + val.icon + "' "; }
				if(val.icon && val.icon.indexOf("/") !== -1) { str += " style='background:url(" + val.icon + ") center center no-repeat;' "; }
				str += ">&#160;</ins><a href='#' rel='" + i + "'>";
				if(val.submenu) {
					str += "<span style='float:" + ($.vakata.context.rtl ? "left" : "right") + ";'>&raquo;</span>";
				}
				str += val.label + "</a>";
				if(val.submenu) {
					tmp = $.vakata.context.parse(val.submenu, true);
					if(tmp) { str += tmp; }
				}
				str += "</li>";
				if(val.separator_after) {
					str += "<li class='vakata-separator vakata-separator-after'></li>";
					was_sep = true;
				}
			});
			str = str.replace(/<li class\='vakata-separator vakata-separator-after'\><\/li\>$/,"");
			str += "</ul>";
			$(document).triggerHandler("context_parse.vakata");
			return str.length > 10 ? str : false;
		},
		exec	: function (i) {
			if($.isFunction($.vakata.context.func[i])) {
				// if is string - eval and call it!
				$.vakata.context.func[i].call($.vakata.context.data, $.vakata.context.par);
				return true;
			}
			else { return false; }
		}
	};
	$(function () {
		var css_string = '' + 
			'#vakata-contextmenu { display:block; visibility:hidden; left:0; top:-200px; position:absolute; margin:0; padding:0; min-width:180px; background:#ebebeb; border:1px solid silver; z-index:10000; *width:180px; } ' + 
			'#vakata-contextmenu ul { min-width:180px; *width:180px; } ' + 
			'#vakata-contextmenu ul, #vakata-contextmenu li { margin:0; padding:0; list-style-type:none; display:block; } ' + 
			'#vakata-contextmenu li { line-height:20px; min-height:20px; position:relative; padding:0px; } ' + 
			'#vakata-contextmenu li a { padding:1px 6px; line-height:17px; display:block; text-decoration:none; margin:1px 1px 0 1px; } ' + 
			'#vakata-contextmenu li ins { float:left; width:16px; height:16px; text-decoration:none; margin-right:2px; } ' + 
			'#vakata-contextmenu li a:hover, #vakata-contextmenu li.vakata-hover > a { background:gray; color:white; } ' + 
			'#vakata-contextmenu li ul { display:none; position:absolute; top:-2px; left:100%; background:#ebebeb; border:1px solid gray; } ' + 
			'#vakata-contextmenu .right { right:100%; left:auto; } ' + 
			'#vakata-contextmenu .bottom { bottom:-1px; top:auto; } ' + 
			'#vakata-contextmenu li.vakata-separator { min-height:0; height:1px; line-height:1px; font-size:1px; overflow:hidden; margin:0 2px; background:silver; /* border-top:1px solid #fefefe; */ padding:0; } ';
		$.vakata.css.add_sheet({ str : css_string, title : "vakata" });
		$.vakata.context.cnt
			.delegate("a","click", function (e) { e.preventDefault(); })
			.delegate("a","mouseup", function (e) {
				if(!$(this).parent().hasClass("jstree-contextmenu-disabled") && $.vakata.context.exec($(this).attr("rel"))) {
					$.vakata.context.hide();
				}
				else { $(this).blur(); }
			})
			.delegate("a","mouseover", function () {
				$.vakata.context.cnt.find(".vakata-hover").removeClass("vakata-hover");
			})
			.appendTo("body");
		$(document).bind("mousedown", function (e) { if($.vakata.context.vis && !$.contains($.vakata.context.cnt[0], e.target)) { $.vakata.context.hide(); } });
		if(typeof $.hotkeys !== "undefined") {
			$(document)
				.bind("keydown", "up", function (e) { 
					if($.vakata.context.vis) { 
						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").prevAll("li:not(.vakata-separator)").first();
						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").last(); }
						o.addClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "down", function (e) { 
					if($.vakata.context.vis) { 
						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").nextAll("li:not(.vakata-separator)").first();
						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").first(); }
						o.addClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "right", function (e) { 
					if($.vakata.context.vis) { 
						$.vakata.context.cnt.find(".vakata-hover").children("ul").show().children("li:not(.vakata-separator)").removeClass("vakata-hover").first().addClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "left", function (e) { 
					if($.vakata.context.vis) { 
						$.vakata.context.cnt.find(".vakata-hover").children("ul").hide().children(".vakata-separator").removeClass("vakata-hover");
						e.stopImmediatePropagation(); 
						e.preventDefault();
					} 
				})
				.bind("keydown", "esc", function (e) { 
					$.vakata.context.hide(); 
					e.preventDefault();
				})
				.bind("keydown", "space", function (e) { 
					$.vakata.context.cnt.find(".vakata-hover").last().children("a").click();
					e.preventDefault();
				});
		}
	});

	$.jstree.plugin("contextmenu", {
		__init : function () {
			this.get_container()
				.delegate("a", "contextmenu.jstree", $.proxy(function (e) {
						e.preventDefault();
						if(!$(e.currentTarget).hasClass("jstree-loading")) {
							this.show_contextmenu(e.currentTarget, e.pageX, e.pageY);
						}
					}, this))
				.delegate("a", "click.jstree", $.proxy(function (e) {
						if(this.data.contextmenu) {
							$.vakata.context.hide();
						}
					}, this))
				.bind("destroy.jstree", $.proxy(function () {
						// TODO: move this to descruct method
						if(this.data.contextmenu) {
							$.vakata.context.hide();
						}
					}, this));
			$(document).bind("context_hide.vakata", $.proxy(function () { this.data.contextmenu = false; }, this));
		},
		defaults : { 
			select_node : false, // requires UI plugin
			show_at_node : true,
			items : { // Could be a function that should return an object like this one
				"create" : {
					"separator_before"	: false,
					"separator_after"	: true,
					"label"				: "Create",
					"action"			: function (obj) { this.create(obj); }
				},
				"rename" : {
					"separator_before"	: false,
					"separator_after"	: false,
					"label"				: "Rename",
					"action"			: function (obj) { this.rename(obj); }
				},
				"remove" : {
					"separator_before"	: false,
					"icon"				: false,
					"separator_after"	: false,
					"label"				: "Delete",
					"action"			: function (obj) { if(this.is_selected(obj)) { this.remove(); } else { this.remove(obj); } }
				},
				"ccp" : {
					"separator_before"	: true,
					"icon"				: false,
					"separator_after"	: false,
					"label"				: "Edit",
					"action"			: false,
					"submenu" : { 
						"cut" : {
							"separator_before"	: false,
							"separator_after"	: false,
							"label"				: "Cut",
							"action"			: function (obj) { this.cut(obj); }
						},
						"copy" : {
							"separator_before"	: false,
							"icon"				: false,
							"separator_after"	: false,
							"label"				: "Copy",
							"action"			: function (obj) { this.copy(obj); }
						},
						"paste" : {
							"separator_before"	: false,
							"icon"				: false,
							"separator_after"	: false,
							"label"				: "Paste",
							"action"			: function (obj) { this.paste(obj); }
						}
					}
				}
			}
		},
		_fn : {
			show_contextmenu : function (obj, x, y) {
				obj = this._get_node(obj);
				var s = this.get_settings().contextmenu,
					a = obj.children("a:visible:eq(0)"),
					o = false,
					i = false;
				if(s.select_node && this.data.ui && !this.is_selected(obj)) {
					this.deselect_all();
					this.select_node(obj, true);
				}
				if(s.show_at_node || typeof x === "undefined" || typeof y === "undefined") {
					o = a.offset();
					x = o.left;
					y = o.top + this.data.core.li_height;
				}
				i = obj.data("jstree") && obj.data("jstree").contextmenu ? obj.data("jstree").contextmenu : s.items;
				if($.isFunction(i)) { i = i.call(this, obj); }
				this.data.contextmenu = true;
				$.vakata.context.show(i, a, x, y, this, obj, this._get_settings().core.rtl);
				if(this.data.themes) { $.vakata.context.cnt.attr("class", "jstree-" + this.data.themes.theme + "-context"); }
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree types plugin
 * Adds support types of nodes
 * You can set an attribute on each li node, that represents its type.
 * According to the type setting the node may get custom icon/validation rules
 */
(function ($) {
	$.jstree.plugin("types", {
		__init : function () {
			var s = this._get_settings().types;
			this.data.types.attach_to = [];
			this.get_container()
				.bind("init.jstree", $.proxy(function () { 
						var types = s.types, 
							attr  = s.type_attr, 
							icons_css = "", 
							_this = this;

						$.each(types, function (i, tp) {
							$.each(tp, function (k, v) { 
								if(!/^(max_depth|max_children|icon|valid_children)$/.test(k)) { _this.data.types.attach_to.push(k); }
							});
							if(!tp.icon) { return true; }
							if( tp.icon.image || tp.icon.position) {
								if(i == "default")	{ icons_css += '.jstree-' + _this.get_index() + ' a > .jstree-icon { '; }
								else				{ icons_css += '.jstree-' + _this.get_index() + ' li[' + attr + '="' + i + '"] > a > .jstree-icon { '; }
								if(tp.icon.image)	{ icons_css += ' background-image:url(' + tp.icon.image + '); '; }
								if(tp.icon.position){ icons_css += ' background-position:' + tp.icon.position + '; '; }
								else				{ icons_css += ' background-position:0 0; '; }
								icons_css += '} ';
							}
						});
						if(icons_css !== "") { $.vakata.css.add_sheet({ 'str' : icons_css, title : "jstree-types" }); }
					}, this))
				.bind("before.jstree", $.proxy(function (e, data) { 
						var s, t, 
							o = this._get_settings().types.use_data ? this._get_node(data.args[0]) : false, 
							d = o && o !== -1 && o.length ? o.data("jstree") : false;
						if(d && d.types && d.types[data.func] === false) { e.stopImmediatePropagation(); return false; }
						if($.inArray(data.func, this.data.types.attach_to) !== -1) {
							if(!data.args[0] || (!data.args[0].tagName && !data.args[0].jquery)) { return; }
							s = this._get_settings().types.types;
							t = this._get_type(data.args[0]);
							if(
								( 
									(s[t] && typeof s[t][data.func] !== "undefined") || 
									(s["default"] && typeof s["default"][data.func] !== "undefined") 
								) && this._check(data.func, data.args[0]) === false
							) {
								e.stopImmediatePropagation();
								return false;
							}
						}
					}, this));
			if(is_ie6) {
				this.get_container()
					.bind("load_node.jstree set_type.jstree", $.proxy(function (e, data) {
							var r = data && data.rslt && data.rslt.obj && data.rslt.obj !== -1 ? this._get_node(data.rslt.obj).parent() : this.get_container_ul(),
								c = false,
								s = this._get_settings().types;
							$.each(s.types, function (i, tp) {
								if(tp.icon && (tp.icon.image || tp.icon.position)) {
									c = i === "default" ? r.find("li > a > .jstree-icon") : r.find("li[" + s.type_attr + "='" + i + "'] > a > .jstree-icon");
									if(tp.icon.image) { c.css("backgroundImage","url(" + tp.icon.image + ")"); }
									c.css("backgroundPosition", tp.icon.position || "0 0");
								}
							});
						}, this));
			}
		},
		defaults : {
			// defines maximum number of root nodes (-1 means unlimited, -2 means disable max_children checking)
			max_children		: -1,
			// defines the maximum depth of the tree (-1 means unlimited, -2 means disable max_depth checking)
			max_depth			: -1,
			// defines valid node types for the root nodes
			valid_children		: "all",

			// whether to use $.data
			use_data : false, 
			// where is the type stores (the rel attribute of the LI element)
			type_attr : "rel",
			// a list of types
			types : {
				// the default type
				"default" : {
					"max_children"	: -1,
					"max_depth"		: -1,
					"valid_children": "all"

					// Bound functions - you can bind any other function here (using boolean or function)
					//"select_node"	: true
				}
			}
		},
		_fn : {
			_types_notify : function (n, data) {
				if(data.type && this._get_settings().types.use_data) {
					this.set_type(data.type, n);
				}
			},
			_get_type : function (obj) {
				obj = this._get_node(obj);
				return (!obj || !obj.length) ? false : obj.attr(this._get_settings().types.type_attr) || "default";
			},
			set_type : function (str, obj) {
				obj = this._get_node(obj);
				var ret = (!obj.length || !str) ? false : obj.attr(this._get_settings().types.type_attr, str);
				if(ret) { this.__callback({ obj : obj, type : str}); }
				return ret;
			},
			_check : function (rule, obj, opts) {
				obj = this._get_node(obj);
				var v = false, t = this._get_type(obj), d = 0, _this = this, s = this._get_settings().types, data = false;
				if(obj === -1) { 
					if(!!s[rule]) { v = s[rule]; }
					else { return; }
				}
				else {
					if(t === false) { return; }
					data = s.use_data ? obj.data("jstree") : false;
					if(data && data.types && typeof data.types[rule] !== "undefined") { v = data.types[rule]; }
					else if(!!s.types[t] && typeof s.types[t][rule] !== "undefined") { v = s.types[t][rule]; }
					else if(!!s.types["default"] && typeof s.types["default"][rule] !== "undefined") { v = s.types["default"][rule]; }
				}
				if($.isFunction(v)) { v = v.call(this, obj); }
				if(rule === "max_depth" && obj !== -1 && opts !== false && s.max_depth !== -2 && v !== 0) {
					// also include the node itself - otherwise if root node it is not checked
					obj.children("a:eq(0)").parentsUntil(".jstree","li").each(function (i) {
						// check if current depth already exceeds global tree depth
						if(s.max_depth !== -1 && s.max_depth - (i + 1) <= 0) { v = 0; return false; }
						d = (i === 0) ? v : _this._check(rule, this, false);
						// check if current node max depth is already matched or exceeded
						if(d !== -1 && d - (i + 1) <= 0) { v = 0; return false; }
						// otherwise - set the max depth to the current value minus current depth
						if(d >= 0 && (d - (i + 1) < v || v < 0) ) { v = d - (i + 1); }
						// if the global tree depth exists and it minus the nodes calculated so far is less than `v` or `v` is unlimited
						if(s.max_depth >= 0 && (s.max_depth - (i + 1) < v || v < 0) ) { v = s.max_depth - (i + 1); }
					});
				}
				return v;
			},
			check_move : function () {
				if(!this.__call_old()) { return false; }
				var m  = this._get_move(),
					s  = m.rt._get_settings().types,
					mc = m.rt._check("max_children", m.cr),
					md = m.rt._check("max_depth", m.cr),
					vc = m.rt._check("valid_children", m.cr),
					ch = 0, d = 1, t;

				if(vc === "none") { return false; } 
				if($.isArray(vc) && m.ot && m.ot._get_type) {
					m.o.each(function () {
						if($.inArray(m.ot._get_type(this), vc) === -1) { d = false; return false; }
					});
					if(d === false) { return false; }
				}
				if(s.max_children !== -2 && mc !== -1) {
					ch = m.cr === -1 ? this.get_container().find("> ul > li").not(m.o).length : m.cr.find("> ul > li").not(m.o).length;
					if(ch + m.o.length > mc) { return false; }
				}
				if(s.max_depth !== -2 && md !== -1) {
					d = 0;
					if(md === 0) { return false; }
					if(typeof m.o.d === "undefined") {
						// TODO: deal with progressive rendering and async when checking max_depth (how to know the depth of the moved node)
						t = m.o;
						while(t.length > 0) {
							t = t.find("> ul > li");
							d ++;
						}
						m.o.d = d;
					}
					if(md - m.o.d < 0) { return false; }
				}
				return true;
			},
			create_node : function (obj, position, js, callback, is_loaded, skip_check) {
				if(!skip_check && (is_loaded || this._is_loaded(obj))) {
					var p  = (typeof position == "string" && position.match(/^before|after$/i) && obj !== -1) ? this._get_parent(obj) : this._get_node(obj),
						s  = this._get_settings().types,
						mc = this._check("max_children", p),
						md = this._check("max_depth", p),
						vc = this._check("valid_children", p),
						ch;
					if(typeof js === "string") { js = { data : js }; }
					if(!js) { js = {}; }
					if(vc === "none") { return false; } 
					if($.isArray(vc)) {
						if(!js.attr || !js.attr[s.type_attr]) { 
							if(!js.attr) { js.attr = {}; }
							js.attr[s.type_attr] = vc[0]; 
						}
						else {
							if($.inArray(js.attr[s.type_attr], vc) === -1) { return false; }
						}
					}
					if(s.max_children !== -2 && mc !== -1) {
						ch = p === -1 ? this.get_container().find("> ul > li").length : p.find("> ul > li").length;
						if(ch + 1 > mc) { return false; }
					}
					if(s.max_depth !== -2 && md !== -1 && (md - 1) < 0) { return false; }
				}
				return this.__call_old(true, obj, position, js, callback, is_loaded, skip_check);
			}
		}
	});
})(jQuery);
//*/

/* 
 * jsTree HTML plugin
 * The HTML data store. Datastores are build by replacing the `load_node` and `_is_loaded` functions.
 */
(function ($) {
	$.jstree.plugin("html_data", {
		__init : function () { 
			// this used to use html() and clean the whitespace, but this way any attached data was lost
			this.data.html_data.original_container_html = this.get_container().find(" > ul > li").clone(true);
			// remove white space from LI node - otherwise nodes appear a bit to the right
			this.data.html_data.original_container_html.find("li").andSelf().contents().filter(function() { return this.nodeType == 3; }).remove();
		},
		defaults : { 
			data : false,
			ajax : false,
			correct_state : true
		},
		_fn : {
			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_html(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
			_is_loaded : function (obj) { 
				obj = this._get_node(obj); 
				return obj == -1 || !obj || (!this._get_settings().html_data.ajax && !$.isFunction(this._get_settings().html_data.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
			},
			load_node_html : function (obj, s_call, e_call) {
				var d,
					s = this.get_settings().html_data,
					error_func = function () {},
					success_func = function () {};
				obj = this._get_node(obj);
				if(obj && obj !== -1) {
					if(obj.data("jstree-is-loading")) { return; }
					else { obj.data("jstree-is-loading",true); }
				}
				switch(!0) {
					case ($.isFunction(s.data)):
						s.data.call(this, obj, $.proxy(function (d) {
							if(d && d !== "" && d.toString && d.toString().replace(/^[\s\n]+$/,"") !== "") {
								d = $(d);
								if(!d.is("ul")) { d = $("<ul />").append(d); }
								if(obj == -1 || !obj) { this.get_container().children("ul").empty().append(d.children()).find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); }
								else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d).children("ul").find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); obj.removeData("jstree-is-loading"); }
								this.clean_node(obj);
								if(s_call) { s_call.call(this); }
							}
							else {
								if(obj && obj !== -1) {
									obj.children("a.jstree-loading").removeClass("jstree-loading");
									obj.removeData("jstree-is-loading");
									if(s.correct_state) { 
										this.correct_state(obj);
										if(s_call) { s_call.call(this); } 
									}
								}
								else {
									if(s.correct_state) { 
										this.get_container().children("ul").empty();
										if(s_call) { s_call.call(this); } 
									}
								}
							}
						}, this));
						break;
					case (!s.data && !s.ajax):
						if(!obj || obj == -1) {
							this.get_container()
								.children("ul").empty()
								.append(this.data.html_data.original_container_html)
								.find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end()
								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
							this.clean_node();
						}
						if(s_call) { s_call.call(this); }
						break;
					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
						if(!obj || obj == -1) {
							d = $(s.data);
							if(!d.is("ul")) { d = $("<ul />").append(d); }
							this.get_container()
								.children("ul").empty().append(d.children())
								.find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end()
								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
							this.clean_node();
						}
						if(s_call) { s_call.call(this); }
						break;
					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
						obj = this._get_node(obj);
						error_func = function (x, t, e) {
							var ef = this.get_settings().html_data.ajax.error; 
							if(ef) { ef.call(this, x, t, e); }
							if(obj != -1 && obj.length) {
								obj.children("a.jstree-loading").removeClass("jstree-loading");
								obj.removeData("jstree-is-loading");
								if(t === "success" && s.correct_state) { this.correct_state(obj); }
							}
							else {
								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
							}
							if(e_call) { e_call.call(this); }
						};
						success_func = function (d, t, x) {
							var sf = this.get_settings().html_data.ajax.success; 
							if(sf) { d = sf.call(this,d,t,x) || d; }
							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "")) {
								return error_func.call(this, x, t, "");
							}
							if(d) {
								d = $(d);
								if(!d.is("ul")) { d = $("<ul />").append(d); }
								if(obj == -1 || !obj) { this.get_container().children("ul").empty().append(d.children()).find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); }
								else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d).children("ul").find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); obj.removeData("jstree-is-loading"); }
								this.clean_node(obj);
								if(s_call) { s_call.call(this); }
							}
							else {
								if(obj && obj !== -1) {
									obj.children("a.jstree-loading").removeClass("jstree-loading");
									obj.removeData("jstree-is-loading");
									if(s.correct_state) { 
										this.correct_state(obj);
										if(s_call) { s_call.call(this); } 
									}
								}
								else {
									if(s.correct_state) { 
										this.get_container().children("ul").empty();
										if(s_call) { s_call.call(this); } 
									}
								}
							}
						};
						s.ajax.context = this;
						s.ajax.error = error_func;
						s.ajax.success = success_func;
						if(!s.ajax.dataType) { s.ajax.dataType = "html"; }
						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
						$.ajax(s.ajax);
						break;
				}
			}
		}
	});
	// include the HTML data plugin by default
	$.jstree.defaults.plugins.push("html_data");
})(jQuery);
//*/

/* 
 * jsTree themeroller plugin
 * Adds support for jQuery UI themes. Include this at the end of your plugins list, also make sure "themes" is not included.
 */
(function ($) {
	$.jstree.plugin("themeroller", {
		__init : function () {
			var s = this._get_settings().themeroller;
			this.get_container()
				.addClass("ui-widget-content")
				.addClass("jstree-themeroller")
				.delegate("a","mouseenter.jstree", function (e) {
					if(!$(e.currentTarget).hasClass("jstree-loading")) {
						$(this).addClass(s.item_h);
					}
				})
				.delegate("a","mouseleave.jstree", function () {
					$(this).removeClass(s.item_h);
				})
				.bind("init.jstree", $.proxy(function (e, data) { 
						data.inst.get_container().find("> ul > li > .jstree-loading > ins").addClass("ui-icon-refresh");
						this._themeroller(data.inst.get_container().find("> ul > li"));
					}, this))
				.bind("open_node.jstree create_node.jstree", $.proxy(function (e, data) { 
						this._themeroller(data.rslt.obj);
					}, this))
				.bind("loaded.jstree refresh.jstree", $.proxy(function (e) {
						this._themeroller();
					}, this))
				.bind("close_node.jstree", $.proxy(function (e, data) {
						this._themeroller(data.rslt.obj);
					}, this))
				.bind("delete_node.jstree", $.proxy(function (e, data) {
						this._themeroller(data.rslt.parent);
					}, this))
				.bind("correct_state.jstree", $.proxy(function (e, data) {
						data.rslt.obj
							.children("ins.jstree-icon").removeClass(s.opened + " " + s.closed + " ui-icon").end()
							.find("> a > ins.ui-icon")
								.filter(function() { 
									return this.className.toString()
										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
										.indexOf("ui-icon-") === -1; 
								}).removeClass(s.item_open + " " + s.item_clsd).addClass(s.item_leaf || "jstree-no-icon");
					}, this))
				.bind("select_node.jstree", $.proxy(function (e, data) {
						data.rslt.obj.children("a").addClass(s.item_a);
					}, this))
				.bind("deselect_node.jstree deselect_all.jstree", $.proxy(function (e, data) {
						this.get_container()
							.find("a." + s.item_a).removeClass(s.item_a).end()
							.find("a.jstree-clicked").addClass(s.item_a);
					}, this))
				.bind("dehover_node.jstree", $.proxy(function (e, data) {
						data.rslt.obj.children("a").removeClass(s.item_h);
					}, this))
				.bind("hover_node.jstree", $.proxy(function (e, data) {
						this.get_container()
							.find("a." + s.item_h).not(data.rslt.obj).removeClass(s.item_h);
						data.rslt.obj.children("a").addClass(s.item_h);
					}, this))
				.bind("move_node.jstree", $.proxy(function (e, data) {
						this._themeroller(data.rslt.o);
						this._themeroller(data.rslt.op);
					}, this));
		},
		__destroy : function () {
			var s = this._get_settings().themeroller,
				c = [ "ui-icon" ];
			$.each(s, function (i, v) {
				v = v.split(" ");
				if(v.length) { c = c.concat(v); }
			});
			this.get_container()
				.removeClass("ui-widget-content")
				.find("." + c.join(", .")).removeClass(c.join(" "));
		},
		_fn : {
			_themeroller : function (obj) {
				var s = this._get_settings().themeroller;
				obj = !obj || obj == -1 ? this.get_container_ul() : this._get_node(obj).parent();
				obj
					.find("li.jstree-closed")
						.children("ins.jstree-icon").removeClass(s.opened).addClass("ui-icon " + s.closed).end()
						.children("a").addClass(s.item)
							.children("ins.jstree-icon").addClass("ui-icon")
								.filter(function() { 
									return this.className.toString()
										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
										.indexOf("ui-icon-") === -1; 
								}).removeClass(s.item_leaf + " " + s.item_open).addClass(s.item_clsd || "jstree-no-icon")
								.end()
							.end()
						.end()
					.end()
					.find("li.jstree-open")
						.children("ins.jstree-icon").removeClass(s.closed).addClass("ui-icon " + s.opened).end()
						.children("a").addClass(s.item)
							.children("ins.jstree-icon").addClass("ui-icon")
								.filter(function() { 
									return this.className.toString()
										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
										.indexOf("ui-icon-") === -1; 
								}).removeClass(s.item_leaf + " " + s.item_clsd).addClass(s.item_open || "jstree-no-icon")
								.end()
							.end()
						.end()
					.end()
					.find("li.jstree-leaf")
						.children("ins.jstree-icon").removeClass(s.closed + " ui-icon " + s.opened).end()
						.children("a").addClass(s.item)
							.children("ins.jstree-icon").addClass("ui-icon")
								.filter(function() { 
									return this.className.toString()
										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
										.indexOf("ui-icon-") === -1; 
								}).removeClass(s.item_clsd + " " + s.item_open).addClass(s.item_leaf || "jstree-no-icon");
			}
		},
		defaults : {
			"opened"	: "ui-icon-triangle-1-se",
			"closed"	: "ui-icon-triangle-1-e",
			"item"		: "ui-state-default",
			"item_h"	: "ui-state-hover",
			"item_a"	: "ui-state-active",
			"item_open"	: "ui-icon-folder-open",
			"item_clsd"	: "ui-icon-folder-collapsed",
			"item_leaf"	: "ui-icon-document"
		}
	});
	$(function() {
		var css_string = '' + 
			'.jstree-themeroller .ui-icon { overflow:visible; } ' + 
			'.jstree-themeroller a { padding:0 2px; } ' + 
			'.jstree-themeroller .jstree-no-icon { display:none; }';
		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
	});
})(jQuery);
//*/

/* 
 * jsTree unique plugin
 * Forces different names amongst siblings (still a bit experimental)
 * NOTE: does not check language versions (it will not be possible to have nodes with the same title, even in different languages)
 */
(function ($) {
	$.jstree.plugin("unique", {
		__init : function () {
			this.get_container()
				.bind("before.jstree", $.proxy(function (e, data) { 
						var nms = [], res = true, p, t;
						if(data.func == "move_node") {
							// obj, ref, position, is_copy, is_prepared, skip_check
							if(data.args[4] === true) {
								if(data.args[0].o && data.args[0].o.length) {
									data.args[0].o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
									res = this._check_unique(nms, data.args[0].np.find("> ul > li").not(data.args[0].o), "move_node");
								}
							}
						}
						if(data.func == "create_node") {
							// obj, position, js, callback, is_loaded
							if(data.args[4] || this._is_loaded(data.args[0])) {
								p = this._get_node(data.args[0]);
								if(data.args[1] && (data.args[1] === "before" || data.args[1] === "after")) {
									p = this._get_parent(data.args[0]);
									if(!p || p === -1) { p = this.get_container(); }
								}
								if(typeof data.args[2] === "string") { nms.push(data.args[2]); }
								else if(!data.args[2] || !data.args[2].data) { nms.push(this._get_string("new_node")); }
								else { nms.push(data.args[2].data); }
								res = this._check_unique(nms, p.find("> ul > li"), "create_node");
							}
						}
						if(data.func == "rename_node") {
							// obj, val
							nms.push(data.args[1]);
							t = this._get_node(data.args[0]);
							p = this._get_parent(t);
							if(!p || p === -1) { p = this.get_container(); }
							res = this._check_unique(nms, p.find("> ul > li").not(t), "rename_node");
						}
						if(!res) {
							e.stopPropagation();
							return false;
						}
					}, this));
		},
		defaults : { 
			error_callback : $.noop
		},
		_fn : { 
			_check_unique : function (nms, p, func) {
				var cnms = [];
				p.children("a").each(function () { cnms.push($(this).text().replace(/^\s+/g,"")); });
				if(!cnms.length || !nms.length) { return true; }
				cnms = cnms.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");
				if((cnms.length + nms.length) != cnms.concat(nms).sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",").length) {
					this._get_settings().unique.error_callback.call(null, nms, p, func);
					return false;
				}
				return true;
			},
			check_move : function () {
				if(!this.__call_old()) { return false; }
				var p = this._get_move(), nms = [];
				if(p.o && p.o.length) {
					p.o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
					return this._check_unique(nms, p.np.find("> ul > li").not(p.o), "check_move");
				}
				return true;
			}
		}
	});
})(jQuery);
//*/

/*
 * jsTree wholerow plugin
 * Makes select and hover work on the entire width of the node
 * MAY BE HEAVY IN LARGE DOM
 */
(function ($) {
	$.jstree.plugin("wholerow", {
		__init : function () {
			if(!this.data.ui) { throw "jsTree wholerow: jsTree UI plugin not included."; }
			this.data.wholerow.html = false;
			this.data.wholerow.to = false;
			this.get_container()
				.bind("init.jstree", $.proxy(function (e, data) { 
						this._get_settings().core.animation = 0;
					}, this))
				.bind("open_node.jstree create_node.jstree clean_node.jstree loaded.jstree", $.proxy(function (e, data) { 
						this._prepare_wholerow_span( data && data.rslt && data.rslt.obj ? data.rslt.obj : -1 );
					}, this))
				.bind("search.jstree clear_search.jstree reopen.jstree after_open.jstree after_close.jstree create_node.jstree delete_node.jstree clean_node.jstree", $.proxy(function (e, data) { 
						if(this.data.to) { clearTimeout(this.data.to); }
						this.data.to = setTimeout( (function (t, o) { return function() { t._prepare_wholerow_ul(o); }; })(this,  data && data.rslt && data.rslt.obj ? data.rslt.obj : -1), 0);
					}, this))
				.bind("deselect_all.jstree", $.proxy(function (e, data) { 
						this.get_container().find(" > .jstree-wholerow .jstree-clicked").removeClass("jstree-clicked " + (this.data.themeroller ? this._get_settings().themeroller.item_a : "" ));
					}, this))
				.bind("select_node.jstree deselect_node.jstree ", $.proxy(function (e, data) { 
						data.rslt.obj.each(function () { 
							var ref = data.inst.get_container().find(" > .jstree-wholerow li:visible:eq(" + ( parseInt((($(this).offset().top - data.inst.get_container().offset().top + data.inst.get_container()[0].scrollTop) / data.inst.data.core.li_height),10)) + ")");
							// ref.children("a")[e.type === "select_node" ? "addClass" : "removeClass"]("jstree-clicked");
							ref.children("a").attr("class",data.rslt.obj.children("a").attr("class"));
						});
					}, this))
				.bind("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) { 
						this.get_container().find(" > .jstree-wholerow .jstree-hovered").removeClass("jstree-hovered " + (this.data.themeroller ? this._get_settings().themeroller.item_h : "" ));
						if(e.type === "hover_node") {
							var ref = this.get_container().find(" > .jstree-wholerow li:visible:eq(" + ( parseInt(((data.rslt.obj.offset().top - this.get_container().offset().top + this.get_container()[0].scrollTop) / this.data.core.li_height),10)) + ")");
							// ref.children("a").addClass("jstree-hovered");
							ref.children("a").attr("class",data.rslt.obj.children(".jstree-hovered").attr("class"));
						}
					}, this))
				.delegate(".jstree-wholerow-span, ins.jstree-icon, li", "click.jstree", function (e) {
						var n = $(e.currentTarget);
						if(e.target.tagName === "A" || (e.target.tagName === "INS" && n.closest("li").is(".jstree-open, .jstree-closed"))) { return; }
						n.closest("li").children("a:visible:eq(0)").click();
						e.stopImmediatePropagation();
					})
				.delegate("li", "mouseover.jstree", $.proxy(function (e) {
						e.stopImmediatePropagation();
						if($(e.currentTarget).children(".jstree-hovered, .jstree-clicked").length) { return false; }
						this.hover_node(e.currentTarget);
						return false;
					}, this))
				.delegate("li", "mouseleave.jstree", $.proxy(function (e) {
						if($(e.currentTarget).children("a").hasClass("jstree-hovered").length) { return; }
						this.dehover_node(e.currentTarget);
					}, this));
			if(is_ie7 || is_ie6) {
				$.vakata.css.add_sheet({ str : ".jstree-" + this.get_index() + " { position:relative; } ", title : "jstree" });
			}
		},
		defaults : {
		},
		__destroy : function () {
			this.get_container().children(".jstree-wholerow").remove();
			this.get_container().find(".jstree-wholerow-span").remove();
		},
		_fn : {
			_prepare_wholerow_span : function (obj) {
				obj = !obj || obj == -1 ? this.get_container().find("> ul > li") : this._get_node(obj);
				if(obj === false) { return; } // added for removing root nodes
				obj.each(function () {
					$(this).find("li").andSelf().each(function () {
						var $t = $(this);
						if($t.children(".jstree-wholerow-span").length) { return true; }
						$t.prepend("<span class='jstree-wholerow-span' style='width:" + ($t.parentsUntil(".jstree","li").length * 18) + "px;'>&#160;</span>");
					});
				});
			},
			_prepare_wholerow_ul : function () {
				var o = this.get_container().children("ul").eq(0), h = o.html();
				o.addClass("jstree-wholerow-real");
				if(this.data.wholerow.last_html !== h) {
					this.data.wholerow.last_html = h;
					this.get_container().children(".jstree-wholerow").remove();
					this.get_container().append(
						o.clone().removeClass("jstree-wholerow-real")
							.wrapAll("<div class='jstree-wholerow' />").parent()
							.width(o.parent()[0].scrollWidth)
							.css("top", (o.height() + ( is_ie7 ? 5 : 0)) * -1 )
							.find("li[id]").each(function () { this.removeAttribute("id"); }).end()
					);
				}
			}
		}
	});
	$(function() {
		var css_string = '' + 
			'.jstree .jstree-wholerow-real { position:relative; z-index:1; } ' + 
			'.jstree .jstree-wholerow-real li { cursor:pointer; } ' + 
			'.jstree .jstree-wholerow-real a { border-left-color:transparent !important; border-right-color:transparent !important; } ' + 
			'.jstree .jstree-wholerow { position:relative; z-index:0; height:0; } ' + 
			'.jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { width:100%; } ' + 
			'.jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li, .jstree .jstree-wholerow a { margin:0 !important; padding:0 !important; } ' + 
			'.jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { background:transparent !important; }' + 
			'.jstree .jstree-wholerow ins, .jstree .jstree-wholerow span, .jstree .jstree-wholerow input { display:none !important; }' + 
			'.jstree .jstree-wholerow a, .jstree .jstree-wholerow a:hover { text-indent:-9999px; !important; width:100%; padding:0 !important; border-right-width:0px !important; border-left-width:0px !important; } ' + 
			'.jstree .jstree-wholerow-span { position:absolute; left:0; margin:0px; padding:0; height:18px; border-width:0; padding:0; z-index:0; }';
		if(is_ff2) {
			css_string += '' + 
				'.jstree .jstree-wholerow a { display:block; height:18px; margin:0; padding:0; border:0; } ' + 
				'.jstree .jstree-wholerow-real a { border-color:transparent !important; } ';
		}
		if(is_ie7 || is_ie6) {
			css_string += '' + 
				'.jstree .jstree-wholerow, .jstree .jstree-wholerow li, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow a { margin:0; padding:0; line-height:18px; } ' + 
				'.jstree .jstree-wholerow a { display:block; height:18px; line-height:18px; overflow:hidden; } ';
		}
		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
	});
})(jQuery);
//*/

/*
* jsTree model plugin
* This plugin gets jstree to use a class model to retrieve data, creating great dynamism
*/
(function ($) {
	var nodeInterface = ["getChildren","getChildrenCount","getAttr","getName","getProps"],
		validateInterface = function(obj, inter) {
			var valid = true;
			obj = obj || {};
			inter = [].concat(inter);
			$.each(inter, function (i, v) {
				if(!$.isFunction(obj[v])) { valid = false; return false; }
			});
			return valid;
		};
	$.jstree.plugin("model", {
		__init : function () {
			if(!this.data.json_data) { throw "jsTree model: jsTree json_data plugin not included."; }
			this._get_settings().json_data.data = function (n, b) {
				var obj = (n == -1) ? this._get_settings().model.object : n.data("jstree_model");
				if(!validateInterface(obj, nodeInterface)) { return b.call(null, false); }
				if(this._get_settings().model.async) {
					obj.getChildren($.proxy(function (data) {
						this.model_done(data, b);
					}, this));
				}
				else {
					this.model_done(obj.getChildren(), b);
				}
			};
		},
		defaults : {
			object : false,
			id_prefix : false,
			async : false
		},
		_fn : {
			model_done : function (data, callback) {
				var ret = [], 
					s = this._get_settings(),
					_this = this;

				if(!$.isArray(data)) { data = [data]; }
				$.each(data, function (i, nd) {
					var r = nd.getProps() || {};
					r.attr = nd.getAttr() || {};
					if(nd.getChildrenCount()) { r.state = "closed"; }
					r.data = nd.getName();
					if(!$.isArray(r.data)) { r.data = [r.data]; }
					if(_this.data.types && $.isFunction(nd.getType)) {
						r.attr[s.types.type_attr] = nd.getType();
					}
					if(r.attr.id && s.model.id_prefix) { r.attr.id = s.model.id_prefix + r.attr.id; }
					if(!r.metadata) { r.metadata = { }; }
					r.metadata.jstree_model = nd;
					ret.push(r);
				});
				callback.call(null, ret);
			}
		}
	});
})(jQuery);
//*/

})();

}); // define()
;
define('jqgrid',
['jquery', 'jqgrid-locale-' + (window && window.__DEPS__ && window.__DEPS__.lang || 'en'), 'jqueryui'],
function (jQuery) {
var $ = jQuery;

/* 
* jqGrid  4.0.0 - jQuery Grid 
* Copyright (c) 2008, Tony Tomov, tony@trirand.com 
* Dual licensed under the MIT and GPL licenses 
* http://www.opensource.org/licenses/mit-license.php 
* http://www.gnu.org/licenses/gpl-2.0.html 
* Date:2011-04-09 
* Modules: grid.base.js; jquery.fmatter.js; grid.custom.js; grid.common.js; grid.formedit.js; grid.filter.js; grid.inlinedit.js; grid.celledit.js; jqModal.js; jqDnR.js; grid.subgrid.js; grid.grouping.js; grid.treegrid.js; grid.import.js; JsonXml.js; grid.tbltogrid.js; grid.jqueryui.js; 
*/
/*
 jqGrid  4.0  - jQuery Grid
 Copyright (c) 2008, Tony Tomov, tony@trirand.com
 Dual licensed under the MIT and GPL licenses
 http://www.opensource.org/licenses/mit-license.php
 http://www.gnu.org/licenses/gpl-2.0.html
 Date: 2011-04-09
*/
(function(b){b.jgrid=b.jgrid||{};b.extend(b.jgrid,{htmlDecode:function(f){if(f&&(f=="&nbsp;"||f=="&#160;"||f.length==1&&f.charCodeAt(0)==160))return"";return!f?f:String(f).replace(/&amp;/g,"&").replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&quot;/g,'"')},htmlEncode:function(f){return!f?f:String(f).replace(/&/g,"&amp;").replace(/>/g,"&gt;").replace(/</g,"&lt;").replace(/\"/g,"&quot;")},format:function(f){var j=b.makeArray(arguments).slice(1);if(f===undefined)f="";return f.replace(/\{(\d+)\}/g,
function(h,c){return j[c]})},getCellIndex:function(f){f=b(f);if(f.is("tr"))return-1;f=(!f.is("td")&&!f.is("th")?f.closest("td,th"):f)[0];if(b.browser.msie)return b.inArray(f,f.parentNode.cells);return f.cellIndex},stripHtml:function(f){f+="";var j=/<("[^"]*"|'[^']*'|[^'">])*>/gi;if(f)return(f=f.replace(j,""))&&f!=="&nbsp;"&&f!=="&#160;"?f.replace(/\"/g,"'"):"";else return f},stringToDoc:function(f){var j;if(typeof f!=="string")return f;try{j=(new DOMParser).parseFromString(f,"text/xml")}catch(h){j=
new ActiveXObject("Microsoft.XMLDOM");j.async=false;j.loadXML(f)}return j&&j.documentElement&&j.documentElement.tagName!="parsererror"?j:null},parse:function(f){if(f.substr(0,9)=="while(1);")f=f.substr(9);if(f.substr(0,2)=="/*")f=f.substr(2,f.length-4);f||(f="{}");return b.jgrid.useJSON===true&&typeof JSON==="object"&&typeof JSON.parse==="function"?JSON.parse(f):eval("("+f+")")},parseDate:function(f,j){var h={m:1,d:1,y:1970,h:0,i:0,s:0},c,g,k;c=/[\\\/:_;.,\t\T\s-]/;if(j&&j!==null&&j!==undefined){j=
b.trim(j);j=j.split(c);f=f.split(c);var l=b.jgrid.formatter.date.monthNames,a=b.jgrid.formatter.date.AmPm,r=function(v,z){if(v===0){if(z==12)z=0}else if(z!=12)z+=12;return z};c=0;for(g=f.length;c<g;c++){if(f[c]=="M"){k=b.inArray(j[c],l);if(k!==-1&&k<12)j[c]=k+1}if(f[c]=="F"){k=b.inArray(j[c],l);if(k!==-1&&k>11)j[c]=k+1-12}if(f[c]=="a"){k=b.inArray(j[c],a);if(k!==-1&&k<2&&j[c]==a[k]){j[c]=k;h.h=r(j[c],h.h)}}if(f[c]=="A"){k=b.inArray(j[c],a);if(k!==-1&&k>1&&j[c]==a[k]){j[c]=k-2;h.h=r(j[c],h.h)}}if(j[c]!==
undefined)h[f[c].toLowerCase()]=parseInt(j[c],10)}h.m=parseInt(h.m,10)-1;c=h.y;if(c>=70&&c<=99)h.y=1900+h.y;else if(c>=0&&c<=69)h.y=2E3+h.y}return new Date(h.y,h.m,h.d,h.h,h.i,h.s,0)},jqID:function(f){f+="";return f.replace(/([\.\:\[\]])/g,"\\$1")},guid:1,uidPref:"jqg",randId:function(f){return(f?f:b.jgrid.uidPref)+b.jgrid.guid++},getAccessor:function(f,j){var h,c,g=[],k;if(typeof j==="function")return j(f);h=f[j];if(h===undefined)try{if(typeof j==="string")g=j.split(".");if(k=g.length)for(h=f;h&&
k--;){c=g.shift();h=h[c]}}catch(l){}return h},ajaxOptions:{},from:function(f){return new function(j,h){if(typeof j=="string")j=b.data(j);var c=this,g=j,k=true,l=false,a=h,r=/[\$,%]/g,v=null,z=null,E=false,P="",K=[],O=true;if(typeof j=="object"&&j.push){if(j.length>0)O=typeof j[0]!="object"?false:true}else throw"data provides is not an array";this._hasData=function(){return g===null?false:g.length===0?false:true};this._getStr=function(m){var o=[];l&&o.push("jQuery.trim(");o.push("String("+m+")");l&&
o.push(")");k||o.push(".toLowerCase()");return o.join("")};this._strComp=function(m){return typeof m=="string"?".toString()":""};this._group=function(m,o){return{field:m.toString(),unique:o,items:[]}};this._toStr=function(m){if(l)m=b.trim(m);k||(m=m.toLowerCase());return m=m.toString().replace(/\\/g,"\\\\").replace(/\"/g,'\\"')};this._funcLoop=function(m){var o=[];b.each(g,function(q,B){o.push(m(B))});return o};this._append=function(m){if(a===null)a="";else a+=P===""?" && ":P;if(E)a+="!";a+="("+m+
")";E=false;P=""};this._setCommand=function(m,o){v=m;z=o};this._resetNegate=function(){E=false};this._repeatCommand=function(m,o){if(v===null)return c;if(m!==null&&o!==null)return v(m,o);if(z===null)return v(m);if(!O)return v(m);return v(z,m)};this._equals=function(m,o){return c._compare(m,o,1)===0};this._compare=function(m,o,q){if(q===undefined)q=1;if(m===undefined)m=null;if(o===undefined)o=null;if(m===null&&o===null)return 0;if(m===null&&o!==null)return 1;if(m!==null&&o===null)return-1;if(!k&&typeof m!==
"number"&&typeof o!=="number"){m=String(m).toLowerCase();o=String(o).toLowerCase()}if(m<o)return-q;if(m>o)return q;return 0};this._performSort=function(){if(K.length!==0)g=c._doSort(g,0)};this._doSort=function(m,o){var q=K[o].by,B=K[o].dir,T=K[o].type,J=K[o].datefmt;if(o==K.length-1)return c._getOrder(m,q,B,T,J);o++;q=c._getGroup(m,q,B,T,J);B=[];for(T=0;T<q.length;T++){J=c._doSort(q[T].items,o);for(var D=0;D<J.length;D++)B.push(J[D])}return B};this._getOrder=function(m,o,q,B,T){var J=[],D=[],W=q==
"a"?1:-1,U,ga;if(B===undefined)B="text";ga=B=="float"||B=="number"||B=="currency"||B=="numeric"?function(S){S=parseFloat(String(S).replace(r,""));return isNaN(S)?0:S}:B=="int"||B=="integer"?function(S){return S?parseFloat(String(S).replace(r,"")):0}:B=="date"||B=="datetime"?function(S){return b.jgrid.parseDate(T,S).getTime()}:b.isFunction(B)?B:function(S){S||(S="");return b.trim(String(S).toUpperCase())};b.each(m,function(S,aa){U=o!==""?b.jgrid.getAccessor(aa,o):aa;if(U===undefined)U="";U=ga(U,aa);
D.push({vSort:U,index:S})});D.sort(function(S,aa){S=S.vSort;aa=aa.vSort;return c._compare(S,aa,W)});B=0;for(var ca=m.length;B<ca;){q=D[B].index;J.push(m[q]);B++}return J};this._getGroup=function(m,o,q,B,T){var J=[],D=null,W=null,U;b.each(c._getOrder(m,o,q,B,T),function(ga,ca){U=b.jgrid.getAccessor(ca,o);if(U===undefined)U="";if(!c._equals(W,U)){W=U;D!==null&&J.push(D);D=c._group(o,U)}D.items.push(ca)});D!==null&&J.push(D);return J};this.ignoreCase=function(){k=false;return c};this.useCase=function(){k=
true;return c};this.trim=function(){l=true;return c};this.noTrim=function(){l=false;return c};this.execute=function(){var m=a,o=[];if(m===null)return c;b.each(g,function(){eval(m)&&o.push(this)});g=o;return c};this.data=function(){return g};this.select=function(m){c._performSort();if(!c._hasData())return[];c.execute();if(b.isFunction(m)){var o=[];b.each(g,function(q,B){o.push(m(B))});return o}return g};this.hasMatch=function(){if(!c._hasData())return false;c.execute();return g.length>0};this.andNot=
function(m,o,q){E=!E;return c.and(m,o,q)};this.orNot=function(m,o,q){E=!E;return c.or(m,o,q)};this.not=function(m,o,q){return c.andNot(m,o,q)};this.and=function(m,o,q){P=" && ";if(m===undefined)return c;return c._repeatCommand(m,o,q)};this.or=function(m,o,q){P=" || ";if(m===undefined)return c;return c._repeatCommand(m,o,q)};this.isNot=function(m){E=!E;return c.is(m)};this.is=function(m){c._append("this."+m);c._resetNegate();return c};this._compareValues=function(m,o,q,B,T){var J;J=O?"jQuery.jgrid.getAccessor(this,'"+
o+"')":"this";if(q===undefined)q=null;var D=q,W=T.stype===undefined?"text":T.stype;if(q!==null)switch(W){case "int":case "integer":D=isNaN(Number(D))||D===""?"0":D;J="parseInt("+J+",10)";D="parseInt("+D+",10)";break;case "float":case "number":case "numeric":D=String(D).replace(r,"");D=isNaN(Number(D))||D===""?"0":D;J="parseFloat("+J+")";D="parseFloat("+D+")";break;case "date":case "datetime":D=String(b.jgrid.parseDate(T.newfmt||"Y-m-d",D).getTime());J='jQuery.jgrid.parseDate("'+T.srcfmt+'",'+J+").getTime()";
break;default:J=c._getStr(J);D=c._getStr('"'+c._toStr(D)+'"')}c._append(J+" "+B+" "+D);c._setCommand(m,o);c._resetNegate();return c};this.equals=function(m,o,q){return c._compareValues(c.equals,m,o,"==",q)};this.notEquals=function(m,o,q){return c._compareValues(c.equals,m,o,"!==",q)};this.isNull=function(m,o,q){return c._compareValues(c.equals,m,null,"===",q)};this.greater=function(m,o,q){return c._compareValues(c.greater,m,o,">",q)};this.less=function(m,o,q){return c._compareValues(c.less,m,o,"<",
q)};this.greaterOrEquals=function(m,o,q){return c._compareValues(c.greaterOrEquals,m,o,">=",q)};this.lessOrEquals=function(m,o,q){return c._compareValues(c.lessOrEquals,m,o,"<=",q)};this.startsWith=function(m,o){var q=o===undefined||o===null?m:o;q=l?b.trim(q.toString()).length:q.toString().length;if(O)c._append(c._getStr("jQuery.jgrid.getAccessor(this,'"+m+"')")+".substr(0,"+q+") == "+c._getStr('"'+c._toStr(o)+'"'));else{q=l?b.trim(o.toString()).length:o.toString().length;c._append(c._getStr("this")+
".substr(0,"+q+") == "+c._getStr('"'+c._toStr(m)+'"'))}c._setCommand(c.startsWith,m);c._resetNegate();return c};this.endsWith=function(m,o){var q=o===undefined||o===null?m:o;q=l?b.trim(q.toString()).length:q.toString().length;O?c._append(c._getStr("jQuery.jgrid.getAccessor(this,'"+m+"')")+".substr("+c._getStr("jQuery.jgrid.getAccessor(this,'"+m+"')")+".length-"+q+","+q+') == "'+c._toStr(o)+'"'):c._append(c._getStr("this")+".substr("+c._getStr("this")+'.length-"'+c._toStr(m)+'".length,"'+c._toStr(m)+
'".length) == "'+c._toStr(m)+'"');c._setCommand(c.endsWith,m);c._resetNegate();return c};this.contains=function(m,o){O?c._append(c._getStr("jQuery.jgrid.getAccessor(this,'"+m+"')")+'.indexOf("'+c._toStr(o)+'",0) > -1'):c._append(c._getStr("this")+'.indexOf("'+c._toStr(m)+'",0) > -1');c._setCommand(c.contains,m);c._resetNegate();return c};this.groupBy=function(m,o,q,B){if(!c._hasData())return null;return c._getGroup(g,m,o,q,B)};this.orderBy=function(m,o,q,B){o=o===undefined||o===null?"a":b.trim(o.toString().toLowerCase());
if(q===null||q===undefined)q="text";if(B===null||B===undefined)B="Y-m-d";if(o=="desc"||o=="descending")o="d";if(o=="asc"||o=="ascending")o="a";K.push({by:m,dir:o,type:q,datefmt:B});return c};return c}(f,null)},extend:function(f){b.extend(b.fn.jqGrid,f);this.no_legacy_api||b.fn.extend(f)}});b.fn.jqGrid=function(f){if(typeof f=="string"){var j=b.jgrid.getAccessor(b.fn.jqGrid,f);if(!j)throw"jqGrid - No such method: "+f;var h=b.makeArray(arguments).slice(1);return j.apply(this,h)}return this.each(function(){if(!this.grid){var c=
b.extend(true,{url:"",height:150,page:1,rowNum:20,rowTotal:null,records:0,pager:"",pgbuttons:true,pginput:true,colModel:[],rowList:[],colNames:[],sortorder:"asc",sortname:"",datatype:"xml",mtype:"GET",altRows:false,selarrrow:[],savedRow:[],shrinkToFit:true,xmlReader:{},jsonReader:{},subGrid:false,subGridModel:[],reccount:0,lastpage:0,lastsort:0,selrow:null,beforeSelectRow:null,onSelectRow:null,onSortCol:null,ondblClickRow:null,onRightClickRow:null,onPaging:null,onSelectAll:null,loadComplete:null,
gridComplete:null,loadError:null,loadBeforeSend:null,afterInsertRow:null,beforeRequest:null,onHeaderClick:null,viewrecords:false,loadonce:false,multiselect:false,multikey:false,editurl:null,search:false,caption:"",hidegrid:true,hiddengrid:false,postData:{},userData:{},treeGrid:false,treeGridModel:"nested",treeReader:{},treeANode:-1,ExpandColumn:null,tree_root_level:0,prmNames:{page:"page",rows:"rows",sort:"sidx",order:"sord",search:"_search",nd:"nd",id:"id",oper:"oper",editoper:"edit",addoper:"add",
deloper:"del",subgridid:"id",npage:null,totalrows:"totalrows"},forceFit:false,gridstate:"visible",cellEdit:false,cellsubmit:"remote",nv:0,loadui:"enable",toolbar:[false,""],scroll:false,multiboxonly:false,deselectAfterSort:true,scrollrows:false,autowidth:false,scrollOffset:18,cellLayout:5,subGridWidth:20,multiselectWidth:20,gridview:false,rownumWidth:25,rownumbers:false,pagerpos:"center",recordpos:"right",footerrow:false,userDataOnFooter:false,hoverrows:true,altclass:"ui-priority-secondary",viewsortcols:[false,
"vertical",true],resizeclass:"",autoencode:false,remapColumns:[],ajaxGridOptions:{},direction:"ltr",toppager:false,headertitles:false,scrollTimeout:40,data:[],_index:{},grouping:false,groupingView:{groupField:[],groupOrder:[],groupText:[],groupColumnShow:[],groupSummary:[],showSummaryOnHide:false,sortitems:[],sortnames:[],groupDataSorted:false,summary:[],summaryval:[],plusicon:"ui-icon-circlesmall-plus",minusicon:"ui-icon-circlesmall-minus"},ignoreCase:false,cmTemplate:{}},b.jgrid.defaults,f||{}),
g={headers:[],cols:[],footers:[],dragStart:function(e,d,i){this.resizing={idx:e,startX:d.clientX,sOL:i[0]};this.hDiv.style.cursor="col-resize";this.curGbox=b("#rs_m"+b.jgrid.jqID(c.id),"#gbox_"+b.jgrid.jqID(c.id));this.curGbox.css({display:"block",left:i[0],top:i[1],height:i[2]});b.isFunction(c.resizeStart)&&c.resizeStart.call(this,d,e);document.onselectstart=function(){return false}},dragMove:function(e){if(this.resizing){var d=e.clientX-this.resizing.startX;e=this.headers[this.resizing.idx];var i=
c.direction==="ltr"?e.width+d:e.width-d,n;if(i>33){this.curGbox.css({left:this.resizing.sOL+d});if(c.forceFit===true){n=this.headers[this.resizing.idx+c.nv];d=c.direction==="ltr"?n.width-d:n.width+d;if(d>33){e.newWidth=i;n.newWidth=d}}else{this.newWidth=c.direction==="ltr"?c.tblwidth+d:c.tblwidth-d;e.newWidth=i}}}},dragEnd:function(){this.hDiv.style.cursor="default";if(this.resizing){var e=this.resizing.idx,d=this.headers[e].newWidth||this.headers[e].width;d=parseInt(d,10);this.resizing=false;b("#rs_m"+
b.jgrid.jqID(c.id)).css("display","none");c.colModel[e].width=d;this.headers[e].width=d;this.headers[e].el.style.width=d+"px";this.cols[e].style.width=d+"px";if(this.footers.length>0)this.footers[e].style.width=d+"px";if(c.forceFit===true){d=this.headers[e+c.nv].newWidth||this.headers[e+c.nv].width;this.headers[e+c.nv].width=d;this.headers[e+c.nv].el.style.width=d+"px";this.cols[e+c.nv].style.width=d+"px";if(this.footers.length>0)this.footers[e+c.nv].style.width=d+"px";c.colModel[e+c.nv].width=d}else{c.tblwidth=
this.newWidth||c.tblwidth;b("table:first",this.bDiv).css("width",c.tblwidth+"px");b("table:first",this.hDiv).css("width",c.tblwidth+"px");this.hDiv.scrollLeft=this.bDiv.scrollLeft;if(c.footerrow){b("table:first",this.sDiv).css("width",c.tblwidth+"px");this.sDiv.scrollLeft=this.bDiv.scrollLeft}}b.isFunction(c.resizeStop)&&c.resizeStop.call(this,d,e)}this.curGbox=null;document.onselectstart=function(){return true}},populateVisible:function(){g.timer&&clearTimeout(g.timer);g.timer=null;var e=b(g.bDiv).height();
if(e){var d=b("table:first",g.bDiv),i,n;if(d[0].rows.length)try{n=(i=d[0].rows[1])?b(i).outerHeight()||g.prevRowHeight:g.prevRowHeight}catch(p){n=g.prevRowHeight}if(n){g.prevRowHeight=n;var A=c.rowNum;i=g.scrollTop=g.bDiv.scrollTop;var s=Math.round(d.position().top)-i,F=s+d.height();n*=A;var t,y,x;if(F<e&&s<=0&&(c.lastpage===undefined||parseInt((F+i+n-1)/n,10)<=c.lastpage)){y=parseInt((e-F+n-1)/n,10);if(F>=0||y<2||c.scroll===true){t=Math.round((F+i)/n)+1;s=-1}else s=1}if(s>0){t=parseInt(i/n,10)+1;
y=parseInt((i+e)/n,10)+2-t;x=true}if(y)if(!(c.lastpage&&t>c.lastpage||c.lastpage==1||t===c.page&&t===c.lastpage))if(g.hDiv.loading)g.timer=setTimeout(g.populateVisible,c.scrollTimeout);else{c.page=t;if(x){g.selectionPreserver(d[0]);g.emptyRows(g.bDiv,false,false)}g.populate(y)}}}},scrollGrid:function(e){if(c.scroll){var d=g.bDiv.scrollTop;if(g.scrollTop===undefined)g.scrollTop=0;if(d!=g.scrollTop){g.scrollTop=d;g.timer&&clearTimeout(g.timer);g.timer=setTimeout(g.populateVisible,c.scrollTimeout)}}g.hDiv.scrollLeft=
g.bDiv.scrollLeft;if(c.footerrow)g.sDiv.scrollLeft=g.bDiv.scrollLeft;e.stopPropagation()},selectionPreserver:function(e){var d=e.p,i=d.selrow,n=d.selarrrow?b.makeArray(d.selarrrow):null,p=e.grid.bDiv.scrollLeft,A=d.gridComplete;d.gridComplete=function(){d.selrow=null;d.selarrrow=[];if(d.multiselect&&n&&n.length>0)for(var s=0;s<n.length;s++)n[s]!=i&&b(e).jqGrid("setSelection",n[s],false);i&&b(e).jqGrid("setSelection",i,false);e.grid.bDiv.scrollLeft=p;d.gridComplete=A;d.gridComplete&&A()}}};if(this.tagName!=
"TABLE")alert("Element is not a table");else{b(this).empty().attr("tabindex","1");this.p=c;var k,l,a;if(this.p.colNames.length===0)for(k=0;k<this.p.colModel.length;k++)this.p.colNames[k]=this.p.colModel[k].label||this.p.colModel[k].name;if(this.p.colNames.length!==this.p.colModel.length)alert(b.jgrid.errors.model);else{var r=b("<div class='ui-jqgrid-view'></div>"),v,z=b.browser.msie?true:false,E=b.browser.webkit||b.browser.safari?true:false;a=this;a.p.direction=b.trim(a.p.direction.toLowerCase());
if(b.inArray(a.p.direction,["ltr","rtl"])==-1)a.p.direction="ltr";l=a.p.direction;b(r).insertBefore(this);b(this).appendTo(r).removeClass("scroll");var P=b("<div class='ui-jqgrid ui-widget ui-widget-content ui-corner-all'></div>");b(P).insertBefore(r).attr({id:"gbox_"+this.id,dir:l});b(r).appendTo(P).attr("id","gview_"+this.id);v=z&&b.browser.version<=6?'<iframe style="display:block;position:absolute;z-index:-1;filter:Alpha(Opacity=\'0\');" src="javascript:false;"></iframe>':"";b("<div class='ui-widget-overlay jqgrid-overlay' id='lui_"+
this.id+"'></div>").append(v).insertBefore(r);b("<div class='loading ui-state-default ui-state-active' id='load_"+this.id+"'>"+this.p.loadtext+"</div>").insertBefore(r);b(this).attr({cellspacing:"0",cellpadding:"0",border:"0",role:"grid","aria-multiselectable":!!this.p.multiselect,"aria-labelledby":"gbox_"+this.id});var K=function(e,d){e=parseInt(e,10);return isNaN(e)?d?d:0:e},O=function(e,d,i,n,p,A){var s=a.p.colModel[e],F=s.align,t='style="',y=s.classes,x=s.name,w=[];if(F)t+="text-align:"+F+";";
if(s.hidden===true)t+="display:none;";if(d===0)t+="width: "+g.headers[e].width+"px;";else if(s.cellattr&&b.isFunction(s.cellattr))if((e=s.cellattr.call(a,p,i,n,s,A))&&typeof e==="string"){e=e.replace(/style/i,"style").replace(/title/i,"title");if(e.indexOf("title")>-1)s.title=false;if(e.indexOf("class")>-1)y=undefined;w=e.split("style");if(w.length===2){w[1]=b.trim(w[1].replace("=",""));if(w[1].indexOf("'")===0||w[1].indexOf('"')===0)w[1]=w[1].substring(1);t+=w[1].replace(/'/gi,'"')}else t+='"'}if(!w.length){w[0]=
"";t+='"'}t+=(y!==undefined?' class="'+y+'"':"")+(s.title&&i?' title="'+b.jgrid.stripHtml(i)+'"':"");t+=' aria-describedby="'+a.p.id+"_"+x+'"';return t+w[0]},m=function(e){return e===undefined||e===null||e===""?"&#160;":a.p.autoencode?b.jgrid.htmlEncode(e):e+""},o=function(e,d,i,n,p){var A=a.p.colModel[i];if(typeof A.formatter!=="undefined"){e={rowId:e,colModel:A,gid:a.p.id,pos:i};d=b.isFunction(A.formatter)?A.formatter.call(a,d,e,n,p):b.fmatter?b.fn.fmatter(A.formatter,d,e,n,p):m(d)}else d=m(d);
return d},q=function(e,d,i,n,p){d=o(e,d,i,p,"add");return'<td role="gridcell" '+O(i,n,d,p,e,true)+">"+d+"</td>"},B=function(e,d,i){var n='<input role="checkbox" type="checkbox" id="jqg_'+a.p.id+"_"+e+'" class="cbox" name="jqg_'+a.p.id+"_"+e+'"/>';return'<td role="gridcell" '+O(d,i,"",null,e,true)+">"+n+"</td>"},T=function(e,d,i,n){i=(parseInt(i,10)-1)*parseInt(n,10)+1+d;return'<td role="gridcell" class="ui-state-default jqgrid-rownum" '+O(e,d,i,null,d,true)+">"+i+"</td>"},J=function(e){var d,i=[],
n=0,p;for(p=0;p<a.p.colModel.length;p++){d=a.p.colModel[p];if(d.name!=="cb"&&d.name!=="subgrid"&&d.name!=="rn"){i[n]=e=="local"?d.name:e=="xml"?d.xmlmap||d.name:d.jsonmap||d.name;n++}}return i},D=function(e){var d=a.p.remapColumns;if(!d||!d.length)d=b.map(a.p.colModel,function(i,n){return n});if(e)d=b.map(d,function(i){return i<e?null:i-e});return d},W=function(e,d,i){if(a.p.deepempty)b("#"+b.jgrid.jqID(a.p.id)+" tbody:first tr:gt(0)").remove();else{var n=b("#"+b.jgrid.jqID(a.p.id)+" tbody:first tr:first")[0];
b("#"+b.jgrid.jqID(a.p.id)+" tbody:first").empty().append(n)}if(d&&a.p.scroll){b(">div:first",e).css({height:"auto"}).children("div:first").css({height:0,display:"none"});e.scrollTop=0}if(i===true)if(a.p.treeGrid===true){a.p.data=[];a.p._index={}}},U=function(){var e=a.p.data.length,d,i,n;d=a.p.rownumbers===true?1:0;i=a.p.multiselect===true?1:0;n=a.p.subGrid===true?1:0;d=a.p.keyIndex===false||a.p.loadonce===true?a.p.localReader.id:a.p.colModel[a.p.keyIndex+i+n+d].name;for(i=0;i<e;i++){n=b.jgrid.getAccessor(a.p.data[i],
d);a.p._index[n]=i}},ga=function(e,d,i,n,p){var A=new Date,s=a.p.datatype!="local"&&a.p.loadonce||a.p.datatype=="xmlstring",F=a.p.datatype=="local"?"local":"xml";if(s){a.p.data=[];a.p._index={};a.p.localReader.id="_id_"}a.p.reccount=0;if(b.isXMLDoc(e)){if(a.p.treeANode===-1&&!a.p.scroll){W(d,false,true);i=1}else i=i>1?i:1;var t,y,x=0,w,G=0,Q=0,M=0,L,N=[],Y,I={},u,C,H=[],ha=a.p.altRows===true?" "+a.p.altclass:"";a.p.xmlReader.repeatitems||(N=J(F));L=a.p.keyIndex===false?a.p.xmlReader.id:a.p.keyIndex;
if(N.length>0&&!isNaN(L)){if(a.p.remapColumns&&a.p.remapColumns.length)L=b.inArray(L,a.p.remapColumns);L=N[L]}F=(L+"").indexOf("[")===-1?N.length?function(ea,$){return b(L,ea).text()||$}:function(ea,$){return b(a.p.xmlReader.cell,ea).eq(L).text()||$}:function(ea,$){return ea.getAttribute(L.replace(/[\[\]]/g,""))||$};a.p.userData={};b(a.p.xmlReader.page,e).each(function(){a.p.page=this.textContent||this.text||0});b(a.p.xmlReader.total,e).each(function(){a.p.lastpage=this.textContent||this.text;if(a.p.lastpage===
undefined)a.p.lastpage=1});b(a.p.xmlReader.records,e).each(function(){a.p.records=this.textContent||this.text||0});b(a.p.xmlReader.userdata,e).each(function(){a.p.userData[this.getAttribute("name")]=this.textContent||this.text});(e=b(a.p.xmlReader.root+" "+a.p.xmlReader.row,e))||(e=[]);var V=e.length,Z=0,ia={},fa;if(e&&V){fa=parseInt(a.p.rowNum,10);var ra=a.p.scroll?b.jgrid.randId():1;if(p)fa*=p+1;p=b.isFunction(a.p.afterInsertRow);var oa="";if(a.p.grouping&&a.p.groupingView.groupCollapse===true)oa=
' style="display:none;"';for(;Z<V;){u=e[Z];C=F(u,ra+Z);t=i===0?0:i+1;t=(t+Z)%2==1?ha:"";H.push("<tr"+oa+' id="'+C+'" tabindex="-1" role="row" class ="ui-widget-content jqgrow ui-row-'+a.p.direction+""+t+'">');if(a.p.rownumbers===true){H.push(T(0,Z,a.p.page,a.p.rowNum));M=1}if(a.p.multiselect===true){H.push(B(C,M,Z));G=1}if(a.p.subGrid===true){H.push(b(a).jqGrid("addSubGridCell",G+M,Z+i));Q=1}if(a.p.xmlReader.repeatitems){Y||(Y=D(G+Q+M));var Ba=b(a.p.xmlReader.cell,u);b.each(Y,function(ea){var $=Ba[this];
if(!$)return false;w=$.textContent||$.text;I[a.p.colModel[ea+G+Q+M].name]=w;H.push(q(C,w,ea+G+Q+M,Z+i,u))})}else for(t=0;t<N.length;t++){w=b(N[t],u).text();I[a.p.colModel[t+G+Q+M].name]=w;H.push(q(C,w,t+G+Q+M,Z+i,u))}H.push("</tr>");if(a.p.grouping){t=a.p.groupingView.groupField.length;for(var xa=[],ya=0;ya<t;ya++)xa.push(I[a.p.groupingView.groupField[ya]]);ia=b(a).jqGrid("groupingPrepare",H,xa,ia,I);H=[]}if(s||a.p.treeGrid===true){I._id_=C;a.p.data.push(I);a.p._index[C]=a.p.data.length-1}if(a.p.gridview===
false){b("tbody:first",d).append(H.join(""));p&&a.p.afterInsertRow.call(a,C,I,u);H=[]}I={};x++;Z++;if(x==fa)break}}if(a.p.gridview===true){y=a.p.treeANode>-1?a.p.treeANode:0;if(a.p.grouping){b(a).jqGrid("groupingRender",ia,a.p.colModel.length);ia=null}else a.p.treeGrid===true&&y>0?b(a.rows[y]).after(H.join("")):b("tbody:first",d).append(H.join(""))}if(a.p.subGrid===true)try{b(a).jqGrid("addSubGrid",G+M)}catch(Ha){}a.p.totaltime=new Date-A;if(x>0)if(a.p.records===0)a.p.records=V;H=null;if(a.p.treeGrid===
true)try{b(a).jqGrid("setTreeNode",y+1,x+y+1)}catch(Ia){}if(!a.p.treeGrid&&!a.p.scroll)a.grid.bDiv.scrollTop=0;a.p.reccount=x;a.p.treeANode=-1;a.p.userDataOnFooter&&b(a).jqGrid("footerData","set",a.p.userData,true);if(s){a.p.records=V;a.p.lastpage=Math.ceil(V/fa)}n||a.updatepager(false,true);if(s)for(;x<V;){u=e[x];C=F(u,x);if(a.p.xmlReader.repeatitems){Y||(Y=D(G+Q+M));var Ea=b(a.p.xmlReader.cell,u);b.each(Y,function(ea){var $=Ea[this];if(!$)return false;w=$.textContent||$.text;I[a.p.colModel[ea+G+
Q+M].name]=w})}else for(t=0;t<N.length;t++){w=b(N[t],u).text();I[a.p.colModel[t+G+Q+M].name]=w}I._id_=C;a.p.data.push(I);a.p._index[C]=a.p.data.length-1;I={};x++}}},ca=function(e,d,i,n,p){var A=new Date;if(e){if(a.p.treeANode===-1&&!a.p.scroll){W(d,false,true);i=1}else i=i>1?i:1;var s,F=a.p.datatype!="local"&&a.p.loadonce||a.p.datatype=="jsonstring";if(F){a.p.data=[];a.p._index={};a.p.localReader.id="_id_"}a.p.reccount=0;if(a.p.datatype=="local"){d=a.p.localReader;s="local"}else{d=a.p.jsonReader;
s="json"}var t=0,y,x,w=[],G,Q=0,M=0,L=0,N,Y,I={},u,C,H=[],ha=a.p.altRows===true?" "+a.p.altclass:"";a.p.page=b.jgrid.getAccessor(e,d.page)||0;N=b.jgrid.getAccessor(e,d.total);a.p.lastpage=N===undefined?1:N;a.p.records=b.jgrid.getAccessor(e,d.records)||0;a.p.userData=b.jgrid.getAccessor(e,d.userdata)||{};d.repeatitems||(G=w=J(s));s=a.p.keyIndex===false?d.id:a.p.keyIndex;if(w.length>0&&!isNaN(s)){if(a.p.remapColumns&&a.p.remapColumns.length)s=b.inArray(s,a.p.remapColumns);s=w[s]}(Y=b.jgrid.getAccessor(e,
d.root))||(Y=[]);N=Y.length;e=0;var V=parseInt(a.p.rowNum,10),Z=a.p.scroll?b.jgrid.randId():1;if(p)V*=p+1;var ia=b.isFunction(a.p.afterInsertRow),fa={},ra="";if(a.p.grouping&&a.p.groupingView.groupCollapse===true)ra=' style="display:none;"';for(;e<N;){p=Y[e];C=b.jgrid.getAccessor(p,s);if(C===undefined){C=Z+e;if(w.length===0)if(d.cell)C=b.jgrid.getAccessor(p,d.cell)[s]||C}y=i===1?0:i;y=(y+e)%2==1?ha:"";H.push("<tr"+ra+' id="'+C+'" tabindex="-1" role="row" class= "ui-widget-content jqgrow ui-row-'+
a.p.direction+""+y+'">');if(a.p.rownumbers===true){H.push(T(0,e,a.p.page,a.p.rowNum));L=1}if(a.p.multiselect){H.push(B(C,L,e));Q=1}if(a.p.subGrid){H.push(b(a).jqGrid("addSubGridCell",Q+L,e+i));M=1}if(d.repeatitems){if(d.cell)p=b.jgrid.getAccessor(p,d.cell);G||(G=D(Q+M+L))}for(x=0;x<G.length;x++){y=b.jgrid.getAccessor(p,G[x]);H.push(q(C,y,x+Q+M+L,e+i,p));I[a.p.colModel[x+Q+M+L].name]=y}H.push("</tr>");if(a.p.grouping){y=a.p.groupingView.groupField.length;x=[];for(var oa=0;oa<y;oa++)x.push(I[a.p.groupingView.groupField[oa]]);
fa=b(a).jqGrid("groupingPrepare",H,x,fa,I);H=[]}if(F||a.p.treeGrid===true){I._id_=C;a.p.data.push(I);a.p._index[C]=a.p.data.length-1}if(a.p.gridview===false){b("#"+b.jgrid.jqID(a.p.id)+" tbody:first").append(H.join(""));ia&&a.p.afterInsertRow.call(a,C,I,p);H=[]}I={};t++;e++;if(t==V)break}if(a.p.gridview===true){u=a.p.treeANode>-1?a.p.treeANode:0;if(a.p.grouping)b(a).jqGrid("groupingRender",fa,a.p.colModel.length);else a.p.treeGrid===true&&u>0?b(a.rows[u]).after(H.join("")):b("#"+b.jgrid.jqID(a.p.id)+
" tbody:first").append(H.join(""))}if(a.p.subGrid===true)try{b(a).jqGrid("addSubGrid",Q+L)}catch(Ba){}a.p.totaltime=new Date-A;if(t>0)if(a.p.records===0)a.p.records=N;if(a.p.treeGrid===true)try{b(a).jqGrid("setTreeNode",u+1,t+u+1)}catch(xa){}if(!a.p.treeGrid&&!a.p.scroll)a.grid.bDiv.scrollTop=0;a.p.reccount=t;a.p.treeANode=-1;a.p.userDataOnFooter&&b(a).jqGrid("footerData","set",a.p.userData,true);if(F){a.p.records=N;a.p.lastpage=Math.ceil(N/V)}n||a.updatepager(false,true);if(F)for(;t<N&&Y[t];){p=
Y[t];C=b.jgrid.getAccessor(p,s);if(C===undefined){C=Z+t;if(w.length===0)if(d.cell)C=b.jgrid.getAccessor(p,d.cell)[s]||C}if(p){if(d.repeatitems){if(d.cell)p=b.jgrid.getAccessor(p,d.cell);G||(G=D(Q+M+L))}for(x=0;x<G.length;x++){y=b.jgrid.getAccessor(p,G[x]);I[a.p.colModel[x+Q+M+L].name]=y}I._id_=C;a.p.data.push(I);a.p._index[C]=a.p.data.length-1;I={}}t++}}},S=function(){function e(u){var C=0,H,ha,V;if(u.groups!==undefined)for(H=0;H<u.groups.length;H++){try{e(u.groups[H])}catch(Z){alert(Z)}C++}if(u.rules!==
undefined){if(C>0){var ia=w.select();w=b.jgrid.from(ia)}try{for(H=0;H<u.rules.length;H++){V=u.rules[H];ha=u.groupOp.toString().toUpperCase();if(x[V.op]&&V.field){if(C>0&&ha&&ha==="OR")w=w.or();w=x[V.op](w,ha)(V.field,V.data,n[V.field])}C++}}catch(fa){alert(fa)}}}var d,i=false,n={},p=[],A=[],s,F,t;if(b.isArray(a.p.data)){var y=a.p.grouping?a.p.groupingView:false;b.each(a.p.colModel,function(){F=this.sorttype||"text";if(F=="date"||F=="datetime"){if(this.formatter&&typeof this.formatter==="string"&&
this.formatter=="date"){s=this.formatoptions&&this.formatoptions.srcformat?this.formatoptions.srcformat:b.jgrid.formatter.date.srcformat;t=this.formatoptions&&this.formatoptions.newformat?this.formatoptions.newformat:b.jgrid.formatter.date.newformat}else s=t=this.datefmt||"Y-m-d";n[this.name]={stype:F,srcfmt:s,newfmt:t}}else n[this.name]={stype:F,srcfmt:"",newfmt:""};if(a.p.grouping&&this.name==y.groupField[0]){var u=this.name;if(typeof this.index!="undefined")u=this.index;p[0]=n[u];A.push(u)}if(!i&&
(this.index==a.p.sortname||this.name==a.p.sortname)){d=this.name;i=true}});if(a.p.treeGrid)b(a).jqGrid("SortTree",d,a.p.sortorder,n[d].stype,n[d].srcfmt);else{var x={eq:function(u){return u.equals},ne:function(u){return u.notEquals},lt:function(u){return u.less},le:function(u){return u.lessOrEquals},gt:function(u){return u.greater},ge:function(u){return u.greaterOrEquals},cn:function(u){return u.contains},nc:function(u,C){return C==="OR"?u.orNot().contains:u.andNot().contains},bw:function(u){return u.startsWith},
bn:function(u,C){return C==="OR"?u.orNot().startsWith:u.andNot().startsWith},en:function(u,C){return C==="OR"?u.orNot().endsWith:u.andNot().endsWith},ew:function(u){return u.endsWith},ni:function(u,C){return C==="OR"?u.orNot().equals:u.andNot().equals},"in":function(u){return u.equals},nu:function(u){return u.isNull},nn:function(u,C){return C==="OR"?u.orNot().isNull:u.andNot().isNull}},w=b.jgrid.from(a.p.data);if(a.p.ignoreCase)w=w.ignoreCase();if(a.p.search===true){var G=a.p.postData.filters;if(G){if(typeof G==
"string")G=b.jgrid.parse(G);e(G)}else try{w=x[a.p.postData.searchOper](w)(a.p.postData.searchField,a.p.postData.searchString,n[a.p.postData.searchField])}catch(Q){}}if(a.p.grouping){w.orderBy(A,y.groupOrder[0],p[0].stype,p[0].srcfmt);y.groupDataSorted=true}if(d&&a.p.sortorder&&i)a.p.sortorder.toUpperCase()=="DESC"?w.orderBy(a.p.sortname,"d",n[d].stype,n[d].srcfmt):w.orderBy(a.p.sortname,"a",n[d].stype,n[d].srcfmt);G=w.select();var M=parseInt(a.p.rowNum,10),L=G.length,N=parseInt(a.p.page,10),Y=Math.ceil(L/
M),I={};G=G.slice((N-1)*M,N*M);n=w=null;I[a.p.localReader.total]=Y;I[a.p.localReader.page]=N;I[a.p.localReader.records]=L;I[a.p.localReader.root]=G;G=null;return I}}},aa=function(){a.grid.hDiv.loading=true;if(!a.p.hiddengrid)switch(a.p.loadui){case "enable":b("#load_"+b.jgrid.jqID(a.p.id)).show();break;case "block":b("#lui_"+b.jgrid.jqID(a.p.id)).show();b("#load_"+b.jgrid.jqID(a.p.id)).show()}},pa=function(){a.grid.hDiv.loading=false;switch(a.p.loadui){case "enable":b("#load_"+b.jgrid.jqID(a.p.id)).hide();
break;case "block":b("#lui_"+b.jgrid.jqID(a.p.id)).hide();b("#load_"+b.jgrid.jqID(a.p.id)).hide()}},ja=function(e){if(!a.grid.hDiv.loading){var d=a.p.scroll&&e===false,i={},n,p=a.p.prmNames;if(a.p.page<=0)a.p.page=1;if(p.search!==null)i[p.search]=a.p.search;if(p.nd!==null)i[p.nd]=(new Date).getTime();if(p.rows!==null)i[p.rows]=a.p.rowNum;if(p.page!==null)i[p.page]=a.p.page;if(p.sort!==null)i[p.sort]=a.p.sortname;if(p.order!==null)i[p.order]=a.p.sortorder;if(a.p.rowTotal!==null&&p.totalrows!==null)i[p.totalrows]=
a.p.rowTotal;var A=a.p.loadComplete,s=b.isFunction(A);s||(A=null);var F=0;e=e||1;if(e>1)if(p.npage!==null){i[p.npage]=e;F=e-1;e=1}else A=function(y){a.p.page++;a.grid.hDiv.loading=false;s&&a.p.loadComplete.call(a,y);ja(e-1)};else p.npage!==null&&delete a.p.postData[p.npage];if(a.p.grouping){b(a).jqGrid("groupingSetup");if(a.p.groupingView.groupDataSorted===true)i[p.sort]=a.p.groupingView.groupField[0]+" "+a.p.groupingView.groupOrder[0]+", "+i[p.sort]}b.extend(a.p.postData,i);var t=!a.p.scroll?1:a.rows.length-
1;if(b.isFunction(a.p.datatype))a.p.datatype.call(a,a.p.postData,"load_"+a.p.id);else{b.isFunction(a.p.beforeRequest)&&a.p.beforeRequest.call(a);n=a.p.datatype.toLowerCase();switch(n){case "json":case "jsonp":case "xml":case "script":b.ajax(b.extend({url:a.p.url,type:a.p.mtype,dataType:n,data:b.isFunction(a.p.serializeGridData)?a.p.serializeGridData.call(a,a.p.postData):a.p.postData,success:function(y){n==="xml"?ga(y,a.grid.bDiv,t,e>1,F):ca(y,a.grid.bDiv,t,e>1,F);A&&A.call(a,y);d&&a.grid.populateVisible();
if(a.p.loadonce||a.p.treeGrid)a.p.datatype="local";pa()},error:function(y,x,w){b.isFunction(a.p.loadError)&&a.p.loadError.call(a,y,x,w);pa()},beforeSend:function(y){aa();b.isFunction(a.p.loadBeforeSend)&&a.p.loadBeforeSend.call(a,y)}},b.jgrid.ajaxOptions,a.p.ajaxGridOptions));break;case "xmlstring":aa();i=b.jgrid.stringToDoc(a.p.datastr);ga(i,a.grid.bDiv);s&&a.p.loadComplete.call(a,i);a.p.datatype="local";a.p.datastr=null;pa();break;case "jsonstring":aa();i=typeof a.p.datastr=="string"?b.jgrid.parse(a.p.datastr):
a.p.datastr;ca(i,a.grid.bDiv);s&&a.p.loadComplete.call(a,i);a.p.datatype="local";a.p.datastr=null;pa();break;case "local":case "clientside":aa();a.p.datatype="local";i=S();ca(i,a.grid.bDiv,t,e>1,F);A&&A.call(a,i);d&&a.grid.populateVisible();pa()}}}};v=function(e,d){var i="",n="<table cellspacing='0' cellpadding='0' border='0' style='table-layout:auto;' class='ui-pg-table'><tbody><tr>",p="",A,s,F,t,y=function(x){var w;if(b.isFunction(a.p.onPaging))w=a.p.onPaging.call(a,x);a.p.selrow=null;if(a.p.multiselect){a.p.selarrrow=
[];b("#cb_"+b.jgrid.jqID(a.p.id),a.grid.hDiv).attr("checked",false)}a.p.savedRow=[];if(w=="stop")return false;return true};e=e.substr(1);A="pg_"+e;s=e+"_left";F=e+"_center";t=e+"_right";b("#"+b.jgrid.jqID(e)).append("<div id='"+A+"' class='ui-pager-control' role='group'><table cellspacing='0' cellpadding='0' border='0' class='ui-pg-table' style='width:100%;table-layout:fixed;height:100%;' role='row'><tbody><tr><td id='"+s+"' align='left'></td><td id='"+F+"' align='center' style='white-space:pre;'></td><td id='"+
t+"' align='right'></td></tr></tbody></table></div>").attr("dir","ltr");if(a.p.rowList.length>0){p="<td dir='"+l+"'>";p+="<select class='ui-pg-selbox' role='listbox'>";for(s=0;s<a.p.rowList.length;s++)p+='<option role="option" value="'+a.p.rowList[s]+'"'+(a.p.rowNum==a.p.rowList[s]?' selected="selected"':"")+">"+a.p.rowList[s]+"</option>";p+="</select></td>"}if(l=="rtl")n+=p;if(a.p.pginput===true)i="<td dir='"+l+"'>"+b.jgrid.format(a.p.pgtext||"","<input class='ui-pg-input' type='text' size='2' maxlength='7' value='0' role='textbox'/>",
"<span id='sp_1'></span>")+"</td>";if(a.p.pgbuttons===true){s=["first"+d,"prev"+d,"next"+d,"last"+d];l=="rtl"&&s.reverse();n+="<td id='"+s[0]+"' class='ui-pg-button ui-corner-all'><span class='ui-icon ui-icon-seek-first'></span></td>";n+="<td id='"+s[1]+"' class='ui-pg-button ui-corner-all'><span class='ui-icon ui-icon-seek-prev'></span></td>";n+=i!==""?"<td class='ui-pg-button ui-state-disabled' style='width:4px;'><span class='ui-separator'></span></td>"+i+"<td class='ui-pg-button ui-state-disabled' style='width:4px;'><span class='ui-separator'></span></td>":
"";n+="<td id='"+s[2]+"' class='ui-pg-button ui-corner-all'><span class='ui-icon ui-icon-seek-next'></span></td>";n+="<td id='"+s[3]+"' class='ui-pg-button ui-corner-all'><span class='ui-icon ui-icon-seek-end'></span></td>"}else if(i!=="")n+=i;if(l=="ltr")n+=p;n+="</tr></tbody></table>";a.p.viewrecords===true&&b("td#"+e+"_"+a.p.recordpos,"#"+A).append("<div dir='"+l+"' style='text-align:"+a.p.recordpos+"' class='ui-paging-info'></div>");b("td#"+e+"_"+a.p.pagerpos,"#"+A).append(n);p=b(".ui-jqgrid").css("font-size")||
"11px";b(document.body).append("<div id='testpg' class='ui-jqgrid ui-widget ui-widget-content' style='font-size:"+p+";visibility:hidden;' ></div>");n=b(n).clone().appendTo("#testpg").width();b("#testpg").remove();if(n>0){if(i!=="")n+=50;b("td#"+e+"_"+a.p.pagerpos,"#"+A).width(n)}a.p._nvtd=[];a.p._nvtd[0]=n?Math.floor((a.p.width-n)/2):Math.floor(a.p.width/3);a.p._nvtd[1]=0;n=null;b(".ui-pg-selbox","#"+A).bind("change",function(){a.p.page=Math.round(a.p.rowNum*(a.p.page-1)/this.value-0.5)+1;a.p.rowNum=
this.value;if(d)b(".ui-pg-selbox",a.p.pager).val(this.value);else a.p.toppager&&b(".ui-pg-selbox",a.p.toppager).val(this.value);if(!y("records"))return false;ja();return false});if(a.p.pgbuttons===true){b(".ui-pg-button","#"+A).hover(function(){if(b(this).hasClass("ui-state-disabled"))this.style.cursor="default";else{b(this).addClass("ui-state-hover");this.style.cursor="pointer"}},function(){if(!b(this).hasClass("ui-state-disabled")){b(this).removeClass("ui-state-hover");this.style.cursor="default"}});
b("#first"+d+", #prev"+d+", #next"+d+", #last"+d,"#"+e).click(function(){var x=K(a.p.page,1),w=K(a.p.lastpage,1),G=false,Q=true,M=true,L=true,N=true;if(w===0||w===1)N=L=M=Q=false;else if(w>1&&x>=1)if(x===1)M=Q=false;else{if(!(x>1&&x<w))if(x===w)N=L=false}else if(w>1&&x===0){N=L=false;x=w-1}if(this.id==="first"+d&&Q){a.p.page=1;G=true}if(this.id==="prev"+d&&M){a.p.page=x-1;G=true}if(this.id==="next"+d&&L){a.p.page=x+1;G=true}if(this.id==="last"+d&&N){a.p.page=w;G=true}if(G){if(!y(this.id))return false;
ja()}return false})}a.p.pginput===true&&b("input.ui-pg-input","#"+A).keypress(function(x){if((x.charCode?x.charCode:x.keyCode?x.keyCode:0)==13){a.p.page=b(this).val()>0?b(this).val():a.p.page;if(!y("user"))return false;ja();return false}return this})};var Ca=function(e,d,i,n){if(a.p.colModel[d].sortable)if(!(a.p.savedRow.length>0)){if(!i){if(a.p.lastsort==d)if(a.p.sortorder=="asc")a.p.sortorder="desc";else{if(a.p.sortorder=="desc")a.p.sortorder="asc"}else a.p.sortorder=a.p.colModel[d].firstsortorder||
"asc";a.p.page=1}if(n)if(a.p.lastsort==d&&a.p.sortorder==n&&!i)return;else a.p.sortorder=n;i=b("thead:first",a.grid.hDiv).get(0);b("tr th:eq("+a.p.lastsort+") span.ui-grid-ico-sort",i).addClass("ui-state-disabled");b("tr th:eq("+a.p.lastsort+")",i).attr("aria-selected","false");b("tr th:eq("+d+") span.ui-icon-"+a.p.sortorder,i).removeClass("ui-state-disabled");b("tr th:eq("+d+")",i).attr("aria-selected","true");if(!a.p.viewsortcols[0])if(a.p.lastsort!=d){b("tr th:eq("+a.p.lastsort+") span.s-ico",
i).hide();b("tr th:eq("+d+") span.s-ico",i).show()}e=e.substring(5);a.p.sortname=a.p.colModel[d].index||e;i=a.p.sortorder;if(b.isFunction(a.p.onSortCol))if(a.p.onSortCol.call(a,e,d,i)=="stop"){a.p.lastsort=d;return}if(a.p.datatype=="local")a.p.deselectAfterSort&&b(a).jqGrid("resetSelection");else{a.p.selrow=null;a.p.multiselect&&b("#cb_"+b.jgrid.jqID(a.p.id),a.grid.hDiv).attr("checked",false);a.p.selarrrow=[];a.p.savedRow=[]}if(a.p.scroll){i=a.grid.bDiv.scrollLeft;W(a.grid.bDiv,true,false);a.grid.hDiv.scrollLeft=
i}a.p.subGrid&&a.p.datatype=="local"&&b("td.sgexpanded","#"+b.jgrid.jqID(a.p.id)).each(function(){b(this).trigger("click")});ja();a.p.lastsort=d;if(a.p.sortname!=e&&d)a.p.lastsort=d}},Fa=function(e){var d,i={},n=E?0:a.p.cellLayout;for(d=i[0]=i[1]=i[2]=0;d<=e;d++)if(a.p.colModel[d].hidden===false)i[0]+=a.p.colModel[d].width+n;if(a.p.direction=="rtl")i[0]=a.p.width-i[0];i[0]-=a.grid.bDiv.scrollLeft;if(b(a.grid.cDiv).is(":visible"))i[1]+=b(a.grid.cDiv).height()+parseInt(b(a.grid.cDiv).css("padding-top"),
10)+parseInt(b(a.grid.cDiv).css("padding-bottom"),10);if(a.p.toolbar[0]===true&&(a.p.toolbar[1]=="top"||a.p.toolbar[1]=="both"))i[1]+=b(a.grid.uDiv).height()+parseInt(b(a.grid.uDiv).css("border-top-width"),10)+parseInt(b(a.grid.uDiv).css("border-bottom-width"),10);if(a.p.toppager)i[1]+=b(a.grid.topDiv).height()+parseInt(b(a.grid.topDiv).css("border-bottom-width"),10);i[2]+=b(a.grid.bDiv).height()+b(a.grid.hDiv).height();return i};this.p.id=this.id;if(b.inArray(a.p.multikey,["shiftKey","altKey","ctrlKey"])==
-1)a.p.multikey=false;a.p.keyIndex=false;for(k=0;k<a.p.colModel.length;k++){a.p.colModel[k]=b.extend(true,{},a.p.cmTemplate,a.p.colModel[k].template||{},a.p.colModel[k]);if(a.p.keyIndex===false&&a.p.colModel[k].key===true)a.p.keyIndex=k}a.p.sortorder=a.p.sortorder.toLowerCase();if(a.p.grouping===true){a.p.scroll=false;a.p.rownumbers=false;a.p.subGrid=false;a.p.treeGrid=false;a.p.gridview=true}if(this.p.treeGrid===true){try{b(this).jqGrid("setTreeGrid")}catch(Ja){}if(a.p.datatype!="local")a.p.localReader=
{id:"_id_"}}if(this.p.subGrid)try{b(a).jqGrid("setSubGrid")}catch(Ka){}if(this.p.multiselect){this.p.colNames.unshift("<input role='checkbox' id='cb_"+this.p.id+"' class='cbox' type='checkbox'/>");this.p.colModel.unshift({name:"cb",width:E?a.p.multiselectWidth+a.p.cellLayout:a.p.multiselectWidth,sortable:false,resizable:false,hidedlg:true,search:false,align:"center",fixed:true})}if(this.p.rownumbers){this.p.colNames.unshift("");this.p.colModel.unshift({name:"rn",width:a.p.rownumWidth,sortable:false,
resizable:false,hidedlg:true,search:false,align:"center",fixed:true})}a.p.xmlReader=b.extend(true,{root:"rows",row:"row",page:"rows>page",total:"rows>total",records:"rows>records",repeatitems:true,cell:"cell",id:"[id]",userdata:"userdata",subgrid:{root:"rows",row:"row",repeatitems:true,cell:"cell"}},a.p.xmlReader);a.p.jsonReader=b.extend(true,{root:"rows",page:"page",total:"total",records:"records",repeatitems:true,cell:"cell",id:"id",userdata:"userdata",subgrid:{root:"rows",repeatitems:true,cell:"cell"}},
a.p.jsonReader);a.p.localReader=b.extend(true,{root:"rows",page:"page",total:"total",records:"records",repeatitems:false,cell:"cell",id:"id",userdata:"userdata",subgrid:{root:"rows",repeatitems:true,cell:"cell"}},a.p.localReader);if(a.p.scroll){a.p.pgbuttons=false;a.p.pginput=false;a.p.rowList=[]}a.p.data.length&&U();var ba="<thead><tr class='ui-jqgrid-labels' role='rowheader'>",Da,ma,sa,qa,ta,X,R,na;ma=na="";if(a.p.shrinkToFit===true&&a.p.forceFit===true)for(k=a.p.colModel.length-1;k>=0;k--)if(!a.p.colModel[k].hidden){a.p.colModel[k].resizable=
false;break}if(a.p.viewsortcols[1]=="horizontal"){na=" ui-i-asc";ma=" ui-i-desc"}Da=z?"class='ui-th-div-ie'":"";na="<span class='s-ico' style='display:none'><span sort='asc' class='ui-grid-ico-sort ui-icon-asc"+na+" ui-state-disabled ui-icon ui-icon-triangle-1-n ui-sort-"+l+"'></span>";na+="<span sort='desc' class='ui-grid-ico-sort ui-icon-desc"+ma+" ui-state-disabled ui-icon ui-icon-triangle-1-s ui-sort-"+l+"'></span></span>";for(k=0;k<this.p.colNames.length;k++){ma=a.p.headertitles?' title="'+b.jgrid.stripHtml(a.p.colNames[k])+
'"':"";ba+="<th id='"+a.p.id+"_"+a.p.colModel[k].name+"' role='columnheader' class='ui-state-default ui-th-column ui-th-"+l+"'"+ma+">";ma=a.p.colModel[k].index||a.p.colModel[k].name;ba+="<div id='jqgh_"+a.p.colModel[k].name+"' "+Da+">"+a.p.colNames[k];a.p.colModel[k].width=a.p.colModel[k].width?parseInt(a.p.colModel[k].width,10):150;if(typeof a.p.colModel[k].title!=="boolean")a.p.colModel[k].title=true;if(ma==a.p.sortname)a.p.lastsort=k;ba+=na+"</div></th>"}ba+="</tr></thead>";na=null;b(this).append(ba);
b("thead tr:first th",this).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});if(this.p.multiselect){var za=[],ua;b("#cb_"+b.jgrid.jqID(a.p.id),this).bind("click",function(){a.p.selarrrow=[];if(this.checked){b(a.rows).each(function(e){if(e>0)if(!b(this).hasClass("subgrid")&&!b(this).hasClass("jqgroup")&&!b(this).hasClass("ui-state-disabled")){b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+b.jgrid.jqID(this.id)).attr("checked","checked");b(this).addClass("ui-state-highlight").attr("aria-selected",
"true");a.p.selarrrow.push(this.id);a.p.selrow=this.id}});ua=true;za=[]}else{b(a.rows).each(function(e){if(e>0)if(!b(this).hasClass("subgrid")&&!b(this).hasClass("ui-state-disabled")){b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+b.jgrid.jqID(this.id)).removeAttr("checked");b(this).removeClass("ui-state-highlight").attr("aria-selected","false");za.push(this.id)}});a.p.selrow=null;ua=false}if(b.isFunction(a.p.onSelectAll))a.p.onSelectAll.call(a,ua?a.p.selarrrow:za,ua)})}if(a.p.autowidth===true){ba=b(P).innerWidth();
a.p.width=ba>0?ba:"nw"}(function(){var e=0,d=E?0:a.p.cellLayout,i=0,n,p=a.p.scrollOffset,A,s=false,F,t=0,y=0,x;b.each(a.p.colModel,function(){if(typeof this.hidden==="undefined")this.hidden=false;this.widthOrg=A=K(this.width,0);if(this.hidden===false){e+=A+d;if(this.fixed)t+=A+d;else i++;y++}});if(isNaN(a.p.width))a.p.width=g.width=e;else g.width=a.p.width;a.p.tblwidth=e;if(a.p.shrinkToFit===false&&a.p.forceFit===true)a.p.forceFit=false;if(a.p.shrinkToFit===true&&i>0){F=g.width-d*i-t;if(!isNaN(a.p.height)){F-=
p;s=true}e=0;b.each(a.p.colModel,function(w){if(this.hidden===false&&!this.fixed){this.width=A=Math.round(F*this.width/(a.p.tblwidth-d*i-t));e+=A;n=w}});x=0;if(s){if(g.width-t-(e+d*i)!==p)x=g.width-t-(e+d*i)-p}else if(!s&&Math.abs(g.width-t-(e+d*i))!==1)x=g.width-t-(e+d*i);a.p.colModel[n].width+=x;a.p.tblwidth=e+x+d*i+t;if(a.p.tblwidth>a.p.width){a.p.colModel[n].width-=a.p.tblwidth-parseInt(a.p.width,10);a.p.tblwidth=a.p.width}}})();b(P).css("width",g.width+"px").append("<div class='ui-jqgrid-resize-mark' id='rs_m"+
a.p.id+"'>&#160;</div>");b(r).css("width",g.width+"px");ba=b("thead:first",a).get(0);var va="";if(a.p.footerrow)va+="<table role='grid' style='width:"+a.p.tblwidth+"px' class='ui-jqgrid-ftable' cellspacing='0' cellpadding='0' border='0'><tbody><tr role='row' class='ui-widget-content footrow footrow-"+l+"'>";r=b("tr:first",ba);var wa="<tr class='jqgfirstrow' role='row' style='height:auto'>";a.p.disableClick=false;b("th",r).each(function(e){sa=a.p.colModel[e].width;if(typeof a.p.colModel[e].resizable===
"undefined")a.p.colModel[e].resizable=true;if(a.p.colModel[e].resizable){qa=document.createElement("span");b(qa).html("&#160;").addClass("ui-jqgrid-resize ui-jqgrid-resize-"+l);b.browser.opera||b(qa).css("cursor","col-resize");b(this).addClass(a.p.resizeclass)}else qa="";b(this).css("width",sa+"px").prepend(qa);var d="";if(a.p.colModel[e].hidden){b(this).css("display","none");d="display:none;"}wa+="<td role='gridcell' style='height:0px;width:"+sa+"px;"+d+"'></td>";g.headers[e]={width:sa,el:this};
ta=a.p.colModel[e].sortable;if(typeof ta!=="boolean")ta=a.p.colModel[e].sortable=true;d=a.p.colModel[e].name;d=="cb"||d=="subgrid"||d=="rn"||a.p.viewsortcols[2]&&b("div",this).addClass("ui-jqgrid-sortable");if(ta)if(a.p.viewsortcols[0]){b("div span.s-ico",this).show();e==a.p.lastsort&&b("div span.ui-icon-"+a.p.sortorder,this).removeClass("ui-state-disabled")}else if(e==a.p.lastsort){b("div span.s-ico",this).show();b("div span.ui-icon-"+a.p.sortorder,this).removeClass("ui-state-disabled")}if(a.p.footerrow)va+=
"<td role='gridcell' "+O(e,0,"",null,"",false)+">&#160;</td>"}).mousedown(function(e){if(b(e.target).closest("th>span.ui-jqgrid-resize").length==1){var d=b.jgrid.getCellIndex(this);if(a.p.forceFit===true){var i=a.p,n=d,p;for(p=d+1;p<a.p.colModel.length;p++)if(a.p.colModel[p].hidden!==true){n=p;break}i.nv=n-d}g.dragStart(d,e,Fa(d));return false}}).click(function(e){if(a.p.disableClick)return a.p.disableClick=false;var d="th>div.ui-jqgrid-sortable",i,n;a.p.viewsortcols[2]||(d="th>div>span>span.ui-grid-ico-sort");
e=b(e.target).closest(d);if(e.length==1){d=b.jgrid.getCellIndex(this);if(!a.p.viewsortcols[2]){i=true;n=e.attr("sort")}Ca(b("div",this)[0].id,d,i,n);return false}});if(a.p.sortable&&b.fn.sortable)try{b(a).jqGrid("sortableColumns",r)}catch(La){}if(a.p.footerrow)va+="</tr></tbody></table>";wa+="</tr>";this.appendChild(document.createElement("tbody"));b(this).addClass("ui-jqgrid-btable").append(wa);wa=null;r=b("<table class='ui-jqgrid-htable' style='width:"+a.p.tblwidth+"px' role='grid' aria-labelledby='gbox_"+
this.id+"' cellspacing='0' cellpadding='0' border='0'></table>").append(ba);var da=a.p.caption&&a.p.hiddengrid===true?true:false;k=b("<div class='ui-jqgrid-hbox"+(l=="rtl"?"-rtl":"")+"'></div>");ba=null;g.hDiv=document.createElement("div");b(g.hDiv).css({width:g.width+"px"}).addClass("ui-state-default ui-jqgrid-hdiv").append(k);b(k).append(r);r=null;da&&b(g.hDiv).hide();if(a.p.pager){if(typeof a.p.pager=="string"){if(a.p.pager.substr(0,1)!="#")a.p.pager="#"+a.p.pager}else a.p.pager="#"+b(a.p.pager).attr("id");
b(a.p.pager).css({width:g.width+"px"}).appendTo(P).addClass("ui-state-default ui-jqgrid-pager ui-corner-bottom");da&&b(a.p.pager).hide();v(a.p.pager,"")}a.p.cellEdit===false&&a.p.hoverrows===true&&b(a).bind("mouseover",function(e){R=b(e.target).closest("tr.jqgrow");b(R).attr("class")!=="subgrid"&&b(R).addClass("ui-state-hover");return false}).bind("mouseout",function(e){R=b(e.target).closest("tr.jqgrow");b(R).removeClass("ui-state-hover");return false});var ka,la;b(a).before(g.hDiv).click(function(e){X=
e.target;R=b(X,a.rows).closest("tr.jqgrow");if(b(R).length===0||R[0].className.indexOf("ui-state-disabled")>-1)return this;var d=b(X).hasClass("cbox"),i=true;if(b.isFunction(a.p.beforeSelectRow))i=a.p.beforeSelectRow.call(a,R[0].id,e);if(X.tagName=="A"||(X.tagName=="INPUT"||X.tagName=="TEXTAREA"||X.tagName=="OPTION"||X.tagName=="SELECT")&&!d)return this;if(i===true){if(a.p.cellEdit===true)if(a.p.multiselect&&d)b(a).jqGrid("setSelection",R[0].id,true);else{ka=R[0].rowIndex;la=b.jgrid.getCellIndex(X);
try{b(a).jqGrid("editCell",ka,la,true)}catch(n){}}else if(a.p.multikey)if(e[a.p.multikey])b(a).jqGrid("setSelection",R[0].id,true);else{if(a.p.multiselect&&d){d=b("[id^=jqg_"+b.jgrid.jqID(a.p.id)+"_]").attr("checked");b("[id^=jqg_"+b.jgrid.jqID(a.p.id)+"_]").attr("checked",!d)}}else{if(a.p.multiselect&&a.p.multiboxonly)if(!d){b(a.p.selarrrow).each(function(p,A){var s=a.rows.namedItem(A);b(s).removeClass("ui-state-highlight");b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+b.jgrid.jqID(A)).attr("checked",false)});
a.p.selarrrow=[];b("#cb_"+b.jgrid.jqID(a.p.id),a.grid.hDiv).attr("checked",false)}b(a).jqGrid("setSelection",R[0].id,true)}if(b.isFunction(a.p.onCellSelect)){ka=R[0].id;la=b.jgrid.getCellIndex(X);a.p.onCellSelect.call(a,ka,la,b(X).html(),e)}e.stopPropagation()}else return this}).bind("reloadGrid",function(e,d){if(a.p.treeGrid===true)a.p.datatype=a.p.treedatatype;d&&d.current&&a.grid.selectionPreserver(a);if(a.p.datatype=="local"){b(a).jqGrid("resetSelection");a.p.data.length&&U()}else if(!a.p.treeGrid){a.p.selrow=
null;if(a.p.multiselect){a.p.selarrrow=[];b("#cb_"+b.jgrid.jqID(a.p.id),a.grid.hDiv).attr("checked",false)}a.p.savedRow=[]}a.p.scroll&&W(a.grid.bDiv,true,false);if(d&&d.page){var i=d.page;if(i>a.p.lastpage)i=a.p.lastpage;if(i<1)i=1;a.p.page=i;a.grid.bDiv.scrollTop=a.grid.prevRowHeight?(i-1)*a.grid.prevRowHeight*a.p.rowNum:0}if(a.grid.prevRowHeight&&a.p.scroll){delete a.p.lastpage;a.grid.populateVisible()}else a.grid.populate();return false});b.isFunction(this.p.ondblClickRow)&&b(this).dblclick(function(e){X=
e.target;R=b(X,a.rows).closest("tr.jqgrow");if(b(R).length===0)return false;ka=R[0].rowIndex;la=b.jgrid.getCellIndex(X);a.p.ondblClickRow.call(a,b(R).attr("id"),ka,la,e);return false});b.isFunction(this.p.onRightClickRow)&&b(this).bind("contextmenu",function(e){X=e.target;R=b(X,a.rows).closest("tr.jqgrow");if(b(R).length===0)return false;a.p.multiselect||b(a).jqGrid("setSelection",R[0].id,true);ka=R[0].rowIndex;la=b.jgrid.getCellIndex(X);a.p.onRightClickRow.call(a,b(R).attr("id"),ka,la,e);return false});
g.bDiv=document.createElement("div");if(z)if(String(a.p.height).toLowerCase()==="auto")a.p.height="100%";b(g.bDiv).append(b('<div style="position:relative;'+(z&&b.browser.version<8?"height:0.01%;":"")+'"></div>').append("<div></div>").append(this)).addClass("ui-jqgrid-bdiv").css({height:a.p.height+(isNaN(a.p.height)?"":"px"),width:g.width+"px"}).scroll(g.scrollGrid);b("table:first",g.bDiv).css({width:a.p.tblwidth+"px"});if(z){b("tbody",this).size()==2&&b("tbody:gt(0)",this).remove();a.p.multikey&&
b(g.bDiv).bind("selectstart",function(){return false})}else a.p.multikey&&b(g.bDiv).bind("mousedown",function(){return false});da&&b(g.bDiv).hide();g.cDiv=document.createElement("div");var Aa=a.p.hidegrid===true?b("<a role='link' href='javascript:void(0)'/>").addClass("ui-jqgrid-titlebar-close HeaderButton").hover(function(){Aa.addClass("ui-state-hover")},function(){Aa.removeClass("ui-state-hover")}).append("<span class='ui-icon ui-icon-circle-triangle-n'></span>").css(l=="rtl"?"left":"right","0px"):
"";b(g.cDiv).append(Aa).append("<span class='ui-jqgrid-title"+(l=="rtl"?"-rtl":"")+"'>"+a.p.caption+"</span>").addClass("ui-jqgrid-titlebar ui-widget-header ui-corner-top ui-helper-clearfix");b(g.cDiv).insertBefore(g.hDiv);if(a.p.toolbar[0]){g.uDiv=document.createElement("div");if(a.p.toolbar[1]=="top")b(g.uDiv).insertBefore(g.hDiv);else a.p.toolbar[1]=="bottom"&&b(g.uDiv).insertAfter(g.hDiv);if(a.p.toolbar[1]=="both"){g.ubDiv=document.createElement("div");b(g.uDiv).insertBefore(g.hDiv).addClass("ui-userdata ui-state-default").attr("id",
"t_"+this.id);b(g.ubDiv).insertAfter(g.hDiv).addClass("ui-userdata ui-state-default").attr("id","tb_"+this.id);da&&b(g.ubDiv).hide()}else b(g.uDiv).width(g.width).addClass("ui-userdata ui-state-default").attr("id","t_"+this.id);da&&b(g.uDiv).hide()}if(a.p.toppager){a.p.toppager=b.jgrid.jqID(a.p.id)+"_toppager";g.topDiv=b("<div id='"+a.p.toppager+"'></div>")[0];a.p.toppager="#"+a.p.toppager;b(g.topDiv).insertBefore(g.hDiv).addClass("ui-state-default ui-jqgrid-toppager").width(g.width);v(a.p.toppager,
"_t")}if(a.p.footerrow){g.sDiv=b("<div class='ui-jqgrid-sdiv'></div>")[0];k=b("<div class='ui-jqgrid-hbox"+(l=="rtl"?"-rtl":"")+"'></div>");b(g.sDiv).append(k).insertAfter(g.hDiv).width(g.width);b(k).append(va);g.footers=b(".ui-jqgrid-ftable",g.sDiv)[0].rows[0].cells;if(a.p.rownumbers)g.footers[0].className="ui-state-default jqgrid-rownum";da&&b(g.sDiv).hide()}k=null;if(a.p.caption){var Ga=a.p.datatype;if(a.p.hidegrid===true){b(".ui-jqgrid-titlebar-close",g.cDiv).click(function(e){var d=b.isFunction(a.p.onHeaderClick),
i=".ui-jqgrid-bdiv, .ui-jqgrid-hdiv, .ui-jqgrid-pager, .ui-jqgrid-sdiv",n,p=this;if(a.p.toolbar[0]===true){if(a.p.toolbar[1]=="both")i+=", #"+b(g.ubDiv).attr("id");i+=", #"+b(g.uDiv).attr("id")}n=b(i,"#gview_"+b.jgrid.jqID(a.p.id)).length;if(a.p.gridstate=="visible")b(i,"#gbox_"+b.jgrid.jqID(a.p.id)).slideUp("fast",function(){n--;if(n===0){b("span",p).removeClass("ui-icon-circle-triangle-n").addClass("ui-icon-circle-triangle-s");a.p.gridstate="hidden";b("#gbox_"+b.jgrid.jqID(a.p.id)).hasClass("ui-resizable")&&
b(".ui-resizable-handle","#gbox_"+b.jgrid.jqID(a.p.id)).hide();if(d)da||a.p.onHeaderClick.call(a,a.p.gridstate,e)}});else a.p.gridstate=="hidden"&&b(i,"#gbox_"+b.jgrid.jqID(a.p.id)).slideDown("fast",function(){n--;if(n===0){b("span",p).removeClass("ui-icon-circle-triangle-s").addClass("ui-icon-circle-triangle-n");if(da){a.p.datatype=Ga;ja();da=false}a.p.gridstate="visible";b("#gbox_"+b.jgrid.jqID(a.p.id)).hasClass("ui-resizable")&&b(".ui-resizable-handle","#gbox_"+b.jgrid.jqID(a.p.id)).show();if(d)da||
a.p.onHeaderClick.call(a,a.p.gridstate,e)}});return false});if(da){a.p.datatype="local";b(".ui-jqgrid-titlebar-close",g.cDiv).trigger("click")}}}else b(g.cDiv).hide();b(g.hDiv).after(g.bDiv).mousemove(function(e){if(g.resizing){g.dragMove(e);return false}});b(".ui-jqgrid-labels",g.hDiv).bind("selectstart",function(){return false});b(document).mouseup(function(){if(g.resizing){g.dragEnd();return false}return true});a.formatCol=O;a.sortData=Ca;a.updatepager=function(e,d){var i,n,p,A,s,F,t,y="";p=parseInt(a.p.page,
10)-1;if(p<0)p=0;p*=parseInt(a.p.rowNum,10);s=p+a.p.reccount;if(a.p.scroll){i=b("tbody:first > tr:gt(0)",a.grid.bDiv);p=s-i.length;a.p.reccount=i.length;if(n=i.outerHeight()||a.grid.prevRowHeight){i=p*n;n*=parseInt(a.p.records,10);b(">div:first",a.grid.bDiv).css({height:n}).children("div:first").css({height:i,display:i?"":"none"})}a.grid.bDiv.scrollLeft=a.grid.hDiv.scrollLeft}y=a.p.pager?a.p.pager:"";y+=a.p.toppager?y?","+a.p.toppager:a.p.toppager:"";if(y){t=b.jgrid.formatter.integer||{};i=K(a.p.page);
n=K(a.p.lastpage);b(".selbox",y).attr("disabled",false);if(a.p.pginput===true){b(".ui-pg-input",y).val(a.p.page);b("#sp_1",y).html(b.fmatter?b.fmatter.util.NumberFormat(a.p.lastpage,t):a.p.lastpage)}if(a.p.viewrecords)if(a.p.reccount===0)b(".ui-paging-info",y).html(a.p.emptyrecords);else{A=p+1;F=a.p.records;if(b.fmatter){A=b.fmatter.util.NumberFormat(A,t);s=b.fmatter.util.NumberFormat(s,t);F=b.fmatter.util.NumberFormat(F,t)}b(".ui-paging-info",y).html(b.jgrid.format(a.p.recordtext,A,s,F))}if(a.p.pgbuttons===
true){if(i<=0)i=n=0;if(i==1||i===0){b("#first, #prev",a.p.pager).addClass("ui-state-disabled").removeClass("ui-state-hover");a.p.toppager&&b("#first_t, #prev_t",a.p.toppager).addClass("ui-state-disabled").removeClass("ui-state-hover")}else{b("#first, #prev",a.p.pager).removeClass("ui-state-disabled");a.p.toppager&&b("#first_t, #prev_t",a.p.toppager).removeClass("ui-state-disabled")}if(i==n||i===0){b("#next, #last",a.p.pager).addClass("ui-state-disabled").removeClass("ui-state-hover");a.p.toppager&&
b("#next_t, #last_t",a.p.toppager).addClass("ui-state-disabled").removeClass("ui-state-hover")}else{b("#next, #last",a.p.pager).removeClass("ui-state-disabled");a.p.toppager&&b("#next_t, #last_t",a.p.toppager).removeClass("ui-state-disabled")}}}e===true&&a.p.rownumbers===true&&b("td.jqgrid-rownum",a.rows).each(function(x){b(this).html(p+1+x)});d&&a.p.jqgdnd&&b(a).jqGrid("gridDnD","updateDnD");b.isFunction(a.p.gridComplete)&&a.p.gridComplete.call(a)};a.refreshIndex=U;a.formatter=function(e,d,i,n,p){return o(e,
d,i,n,p)};b.extend(g,{populate:ja,emptyRows:W});this.grid=g;a.addXmlData=function(e){ga(e,a.grid.bDiv)};a.addJSONData=function(e){ca(e,a.grid.bDiv)};this.grid.cols=this.rows[0].cells;ja();a.p.hiddengrid=false;b(window).unload(function(){a=null})}}}})};b.jgrid.extend({getGridParam:function(f){var j=this[0];if(j&&j.grid)return f?typeof j.p[f]!="undefined"?j.p[f]:null:j.p},setGridParam:function(f){return this.each(function(){this.grid&&typeof f==="object"&&b.extend(true,this.p,f)})},getDataIDs:function(){var f=
[],j=0,h,c=0;this.each(function(){if((h=this.rows.length)&&h>0)for(;j<h;){if(b(this.rows[j]).hasClass("jqgrow")){f[c]=this.rows[j].id;c++}j++}});return f},setSelection:function(f,j){return this.each(function(){function h(a){var r=b(c.grid.bDiv)[0].clientHeight,v=b(c.grid.bDiv)[0].scrollTop,z=c.rows[a].offsetTop;a=c.rows[a].clientHeight;if(z+a>=r+v)b(c.grid.bDiv)[0].scrollTop=z-(r+v)+a+v;else if(z<r+v)if(z<v)b(c.grid.bDiv)[0].scrollTop=z}var c=this,g,k,l;if(f!==undefined){j=j===false?false:true;k=
c.rows.namedItem(f+"");if(!(!k||k.className.indexOf("ui-state-disabled")>-1)){if(c.p.scrollrows===true){g=c.rows.namedItem(f).rowIndex;g>=0&&h(g)}if(c.p.multiselect){c.p.selrow=k.id;l=b.inArray(c.p.selrow,c.p.selarrrow);if(l===-1){k.className!=="ui-subgrid"&&b(k).addClass("ui-state-highlight").attr("aria-selected","true");g=true;b("#jqg_"+b.jgrid.jqID(c.p.id)+"_"+b.jgrid.jqID(c.p.selrow)).attr("checked",g);c.p.selarrrow.push(c.p.selrow)}else{k.className!=="ui-subgrid"&&b(k).removeClass("ui-state-highlight").attr("aria-selected",
"false");g=false;b("#jqg_"+b.jgrid.jqID(c.p.id)+"_"+b.jgrid.jqID(c.p.selrow)).attr("checked",g);c.p.selarrrow.splice(l,1);l=c.p.selarrrow[0];c.p.selrow=l===undefined?null:l}c.p.onSelectRow&&j&&c.p.onSelectRow.call(c,k.id,g)}else if(k.className!=="ui-subgrid"){if(c.p.selrow!=k.id){b(c.rows.namedItem(c.p.selrow)).removeClass("ui-state-highlight").attr({"aria-selected":"false",tabindex:"-1"});b(k).addClass("ui-state-highlight").attr({"aria-selected":true,tabindex:"0"});g=true}else g=false;c.p.selrow=
k.id;c.p.onSelectRow&&j&&c.p.onSelectRow.call(c,k.id,g)}}}})},resetSelection:function(f){return this.each(function(){var j=this,h,c;if(typeof f!=="undefined"){c=f===j.p.selrow?j.p.selrow:f;b("#"+b.jgrid.jqID(j.p.id)+" tbody:first tr#"+b.jgrid.jqID(c)).removeClass("ui-state-highlight").attr("aria-selected","false");if(j.p.multiselect){b("#jqg_"+b.jgrid.jqID(j.p.id)+"_"+b.jgrid.jqID(c)).attr("checked",false);b("#cb_"+b.jgrid.jqID(j.p.id)).attr("checked",false)}c=null}else if(j.p.multiselect){b(j.p.selarrrow).each(function(g,
k){h=j.rows.namedItem(k);b(h).removeClass("ui-state-highlight").attr("aria-selected","false");b("#jqg_"+b.jgrid.jqID(j.p.id)+"_"+b.jgrid.jqID(k)).attr("checked",false)});b("#cb_"+b.jgrid.jqID(j.p.id)).attr("checked",false);j.p.selarrrow=[]}else if(j.p.selrow){b("#"+b.jgrid.jqID(j.p.id)+" tbody:first tr#"+b.jgrid.jqID(j.p.selrow)).removeClass("ui-state-highlight").attr("aria-selected","false");j.p.selrow=null}j.p.savedRow=[]})},getRowData:function(f){var j={},h,c=false,g,k=0;this.each(function(){var l=
this,a,r;if(typeof f=="undefined"){c=true;h=[];g=l.rows.length}else{r=l.rows.namedItem(f);if(!r)return j;g=2}for(;k<g;){if(c)r=l.rows[k];if(b(r).hasClass("jqgrow")){b("td",r).each(function(v){a=l.p.colModel[v].name;if(a!=="cb"&&a!=="subgrid"&&a!=="rn")if(l.p.treeGrid===true&&a==l.p.ExpandColumn)j[a]=b.jgrid.htmlDecode(b("span:first",this).html());else try{j[a]=b.unformat(this,{rowId:r.id,colModel:l.p.colModel[v]},v)}catch(z){j[a]=b.jgrid.htmlDecode(b(this).html())}});if(c){h.push(j);j={}}}k++}});
return h?h:j},delRowData:function(f){var j=false,h,c;this.each(function(){if(h=this.rows.namedItem(f)){b(h).remove();this.p.records--;this.p.reccount--;this.updatepager(true,false);j=true;if(this.p.multiselect){c=b.inArray(f,this.p.selarrrow);c!=-1&&this.p.selarrrow.splice(c,1)}if(f==this.p.selrow)this.p.selrow=null}else return false;if(this.p.datatype=="local"){var g=this.p._index[f];if(typeof g!="undefined"){this.p.data.splice(g,1);this.refreshIndex()}}if(this.p.altRows===true&&j){var k=this.p.altclass;
b(this.rows).each(function(l){l%2==1?b(this).addClass(k):b(this).removeClass(k)})}});return j},setRowData:function(f,j,h){var c,g=true,k;this.each(function(){if(!this.grid)return false;var l=this,a,r,v=typeof h,z={};r=l.rows.namedItem(f);if(!r)return false;if(j)try{b(this.p.colModel).each(function(O){c=this.name;if(j[c]!==undefined){z[c]=this.formatter&&typeof this.formatter==="string"&&this.formatter=="date"?b.unformat.date(j[c],this):j[c];a=l.formatter(f,j[c],O,j,"edit");k=this.title?{title:b.jgrid.stripHtml(a)}:
{};l.p.treeGrid===true&&c==l.p.ExpandColumn?b("td:eq("+O+") > span:first",r).html(a).attr(k):b("td:eq("+O+")",r).html(a).attr(k)}});if(l.p.datatype=="local"){var E=l.p._index[f];if(l.p.treeGrid)for(var P in l.p.treeReader)z.hasOwnProperty(l.p.treeReader[P])&&delete z[l.p.treeReader[P]];if(typeof E!="undefined")l.p.data[E]=b.extend(true,l.p.data[E],z);z=null}}catch(K){g=false}if(g)if(v==="string")b(r).addClass(h);else v==="object"&&b(r).css(h)});return g},addRowData:function(f,j,h,c){h||(h="last");
var g=false,k,l,a,r,v,z,E,P,K="",O,m,o,q,B;if(j){if(b.isArray(j)){O=true;h="last";m=f}else{j=[j];O=false}this.each(function(){var T=j.length;v=this.p.rownumbers===true?1:0;a=this.p.multiselect===true?1:0;r=this.p.subGrid===true?1:0;if(!O)if(typeof f!="undefined")f+="";else{f=b.jgrid.randId();if(this.p.keyIndex!==false){m=this.p.colModel[this.p.keyIndex+a+r+v].name;if(typeof j[0][m]!="undefined")f=j[0][m]}}o=this.p.altclass;for(var J=0,D="",W={},U=b.isFunction(this.p.afterInsertRow)?true:false;J<T;){q=
j[J];l="";if(O){try{f=q[m]}catch(ga){f=b.jgrid.randId()}D=this.p.altRows===true?(this.rows.length-1)%2===0?o:"":""}if(v){K=this.formatCol(0,1,"",null,f,true);l+='<td role="gridcell" aria-describedby="'+this.p.id+'_rn" class="ui-state-default jqgrid-rownum" '+K+">0</td>"}if(a){P='<input role="checkbox" type="checkbox" id="jqg_'+this.p.id+"_"+f+'" class="cbox"/>';K=this.formatCol(v,1,"",null,f,true);l+='<td role="gridcell" aria-describedby="'+this.p.id+'_cb" '+K+">"+P+"</td>"}if(r)l+=b(this).jqGrid("addSubGridCell",
a+v,1);for(E=a+r+v;E<this.p.colModel.length;E++){B=this.p.colModel[E];k=B.name;W[k]=B.formatter&&typeof B.formatter==="string"&&B.formatter=="date"?b.unformat.date(q[k],B):q[k];P=this.formatter(f,b.jgrid.getAccessor(q,k),E,q,"edit");K=this.formatCol(E,1,P,f,q,true);l+='<td role="gridcell" aria-describedby="'+this.p.id+"_"+k+'" '+K+">"+P+"</td>"}l='<tr id="'+f+'" role="row" tabindex="-1" class="ui-widget-content jqgrow ui-row-'+this.p.direction+" "+D+'">'+l+"</tr>";if(this.p.subGrid===true){l=b(l)[0];
b(this).jqGrid("addSubGrid",l,a+v)}if(this.rows.length===0)b("table:first",this.grid.bDiv).append(l);else switch(h){case "last":b(this.rows[this.rows.length-1]).after(l);break;case "first":b(this.rows[0]).after(l);break;case "after":if(z=this.rows.namedItem(c))b(this.rows[z.rowIndex+1]).hasClass("ui-subgrid")?b(this.rows[z.rowIndex+1]).after(l):b(z).after(l);break;case "before":if(z=this.rows.namedItem(c)){b(z).before(l);z=z.rowIndex}}this.p.records++;this.p.reccount++;U&&this.p.afterInsertRow.call(this,
f,q,q);J++;if(this.p.datatype=="local"){W[this.p.localReader.id]=f;this.p._index[f]=this.p.data.length;this.p.data.push(W);W={}}}if(this.p.altRows===true&&!O)if(h=="last")(this.rows.length-1)%2==1&&b(this.rows[this.rows.length-1]).addClass(o);else b(this.rows).each(function(ca){ca%2==1?b(this).addClass(o):b(this).removeClass(o)});this.updatepager(true,true);g=true})}return g},footerData:function(f,j,h){function c(r){for(var v in r)if(r.hasOwnProperty(v))return false;return true}var g,k=false,l={},
a;if(typeof f=="undefined")f="get";if(typeof h!="boolean")h=true;f=f.toLowerCase();this.each(function(){var r=this,v;if(!r.grid||!r.p.footerrow)return false;if(f=="set")if(c(j))return false;k=true;b(this.p.colModel).each(function(z){g=this.name;if(f=="set"){if(j[g]!==undefined){v=h?r.formatter("",j[g],z,j,"edit"):j[g];a=this.title?{title:b.jgrid.stripHtml(v)}:{};b("tr.footrow td:eq("+z+")",r.grid.sDiv).html(v).attr(a);k=true}}else if(f=="get")l[g]=b("tr.footrow td:eq("+z+")",r.grid.sDiv).html()})});
return f=="get"?l:k},showHideCol:function(f,j){return this.each(function(){var h=this,c=false,g=b.browser.webkit||b.browser.safari?0:h.p.cellLayout,k;if(h.grid){if(typeof f==="string")f=[f];j=j!="none"?"":"none";var l=j===""?true:false;b(this.p.colModel).each(function(a){if(b.inArray(this.name,f)!==-1&&this.hidden===l){b("tr",h.grid.hDiv).each(function(){b(this).children("th:eq("+a+")").css("display",j)});b(h.rows).each(function(){b(this).children("td:eq("+a+")").css("display",j)});h.p.footerrow&&
b(h.grid.sDiv).children("td:eq("+a+")").css("display",j);k=this.widthOrg?this.widthOrg:parseInt(this.width,10);if(j==="none")h.p.tblwidth-=k+g;else h.p.tblwidth+=k+g;this.hidden=!l;c=true}});if(c===true)if(h.grid.width!==h.p.tblwidth)b(h).jqGrid("setGridWidth",h.p.shrinkToFit===true?h.grid.width:h.p.tblwidth,true)}})},hideCol:function(f){return this.each(function(){b(this).jqGrid("showHideCol",f,"none")})},showCol:function(f){return this.each(function(){b(this).jqGrid("showHideCol",f,"")})},remapColumns:function(f,
j,h){function c(l){var a;a=l.length?b.makeArray(l):b.extend({},l);b.each(f,function(r){l[r]=a[this]})}function g(l,a){b(">tr"+(a||""),l).each(function(){var r=this,v=b.makeArray(r.cells);b.each(f,function(){var z=v[this];z&&r.appendChild(z)})})}var k=this.get(0);c(k.p.colModel);c(k.p.colNames);c(k.grid.headers);g(b("thead:first",k.grid.hDiv),h&&":not(.ui-jqgrid-labels)");j&&g(b("#"+b.jgrid.jqID(k.p.id)+" tbody:first"),".jqgfirstrow, tr.jqgrow, tr.jqfoot");k.p.footerrow&&g(b("tbody:first",k.grid.sDiv));
if(k.p.remapColumns)if(k.p.remapColumns.length)c(k.p.remapColumns);else k.p.remapColumns=b.makeArray(f);k.p.lastsort=b.inArray(k.p.lastsort,f);if(k.p.treeGrid)k.p.expColInd=b.inArray(k.p.expColInd,f)},setGridWidth:function(f,j){return this.each(function(){if(this.grid){var h=this,c,g=0,k=b.browser.webkit||b.browser.safari?0:h.p.cellLayout,l,a=0,r=false,v=h.p.scrollOffset,z,E=0,P=0,K;if(typeof j!="boolean")j=h.p.shrinkToFit;if(!isNaN(f)){f=parseInt(f,10);h.grid.width=h.p.width=f;b("#gbox_"+b.jgrid.jqID(h.p.id)).css("width",
f+"px");b("#gview_"+b.jgrid.jqID(h.p.id)).css("width",f+"px");b(h.grid.bDiv).css("width",f+"px");b(h.grid.hDiv).css("width",f+"px");h.p.pager&&b(h.p.pager).css("width",f+"px");h.p.toppager&&b(h.p.toppager).css("width",f+"px");if(h.p.toolbar[0]===true){b(h.grid.uDiv).css("width",f+"px");h.p.toolbar[1]=="both"&&b(h.grid.ubDiv).css("width",f+"px")}h.p.footerrow&&b(h.grid.sDiv).css("width",f+"px");if(j===false&&h.p.forceFit===true)h.p.forceFit=false;if(j===true){b.each(h.p.colModel,function(){if(this.hidden===
false){c=this.widthOrg?this.widthOrg:parseInt(this.width,10);g+=c+k;if(this.fixed)E+=c+k;else a++;P++}});if(a!==0){h.p.tblwidth=g;z=f-k*a-E;if(!isNaN(h.p.height))if(b(h.grid.bDiv)[0].clientHeight<b(h.grid.bDiv)[0].scrollHeight||h.rows.length===1){r=true;z-=v}g=0;var O=h.grid.cols.length>0;b.each(h.p.colModel,function(m){if(this.hidden===false&&!this.fixed){c=this.widthOrg?this.widthOrg:parseInt(this.width,10);c=Math.round(z*c/(h.p.tblwidth-k*a-E));if(!(c<0)){this.width=c;g+=c;h.grid.headers[m].width=
c;h.grid.headers[m].el.style.width=c+"px";if(h.p.footerrow)h.grid.footers[m].style.width=c+"px";if(O)h.grid.cols[m].style.width=c+"px";l=m}}});K=0;if(r){if(f-E-(g+k*a)!==v)K=f-E-(g+k*a)-v}else if(Math.abs(f-E-(g+k*a))!==1)K=f-E-(g+k*a);h.p.colModel[l].width+=K;h.p.tblwidth=g+K+k*a+E;if(h.p.tblwidth>f){r=h.p.tblwidth-parseInt(f,10);h.p.tblwidth=f;c=h.p.colModel[l].width-=r}else c=h.p.colModel[l].width;h.grid.headers[l].width=c;h.grid.headers[l].el.style.width=c+"px";if(O)h.grid.cols[l].style.width=
c+"px";b("table:first",h.grid.bDiv).css("width",h.p.tblwidth+"px");b("table:first",h.grid.hDiv).css("width",h.p.tblwidth+"px");h.grid.hDiv.scrollLeft=h.grid.bDiv.scrollLeft;if(h.p.footerrow){h.grid.footers[l].style.width=c+"px";b("table:first",h.grid.sDiv).css("width",h.p.tblwidth+"px")}}}}}})},setGridHeight:function(f){return this.each(function(){if(this.grid){b(this.grid.bDiv).css({height:f+(isNaN(f)?"":"px")});this.p.height=f;this.p.scroll&&this.grid.populateVisible()}})},setCaption:function(f){return this.each(function(){this.p.caption=
f;b("span.ui-jqgrid-title",this.grid.cDiv).html(f);b(this.grid.cDiv).show()})},setLabel:function(f,j,h,c){return this.each(function(){var g=-1;if(this.grid){if(isNaN(f))b(this.p.colModel).each(function(a){if(this.name==f){g=a;return false}});else g=parseInt(f,10);if(g>=0){var k=b("tr.ui-jqgrid-labels th:eq("+g+")",this.grid.hDiv);if(j){var l=b(".s-ico",k);b("[id^=jqgh_]",k).empty().html(j).append(l);this.p.colNames[g]=j}if(h)typeof h==="string"?b(k).addClass(h):b(k).css(h);typeof c==="object"&&b(k).attr(c)}}})},
setCell:function(f,j,h,c,g,k){return this.each(function(){var l=-1,a,r;if(this.grid){if(isNaN(j))b(this.p.colModel).each(function(z){if(this.name==j){l=z;return false}});else l=parseInt(j,10);if(l>=0)if(a=this.rows.namedItem(f)){var v=b("td:eq("+l+")",a);if(h!==""||k===true){a=this.formatter(f,h,l,a,"edit");r=this.p.colModel[l].title?{title:b.jgrid.stripHtml(a)}:{};this.p.treeGrid&&b(".tree-wrap",b(v)).length>0?b("span",b(v)).html(a).attr(r):b(v).html(a).attr(r);if(this.p.datatype=="local"){a=this.p.colModel[l];
h=a.formatter&&typeof a.formatter==="string"&&a.formatter=="date"?b.unformat.date(h,a):h;r=this.p._index[f];if(typeof r!="undefined")this.p.data[r][a.name]=h}}if(typeof c==="string")b(v).addClass(c);else c&&b(v).css(c);typeof g==="object"&&b(v).attr(g)}}})},getCell:function(f,j){var h=false;this.each(function(){var c=-1;if(this.grid){if(isNaN(j))b(this.p.colModel).each(function(l){if(this.name===j){c=l;return false}});else c=parseInt(j,10);if(c>=0){var g=this.rows.namedItem(f);if(g)try{h=b.unformat(b("td:eq("+
c+")",g),{rowId:g.id,colModel:this.p.colModel[c]},c)}catch(k){h=b.jgrid.htmlDecode(b("td:eq("+c+")",g).html())}}}});return h},getCol:function(f,j,h){var c=[],g,k=0,l=0,a=0,r;j=typeof j!="boolean"?false:j;if(typeof h=="undefined")h=false;this.each(function(){var v=-1;if(this.grid){if(isNaN(f))b(this.p.colModel).each(function(K){if(this.name===f){v=K;return false}});else v=parseInt(f,10);if(v>=0){var z=this.rows.length,E=0;if(z&&z>0){for(;E<z;){if(b(this.rows[E]).hasClass("jqgrow")){try{g=b.unformat(b(this.rows[E].cells[v]),
{rowId:this.rows[E].id,colModel:this.p.colModel[v]},v)}catch(P){g=b.jgrid.htmlDecode(this.rows[E].cells[v].innerHTML)}if(h){r=parseFloat(g);k+=r;l=Math.min(l,r);a=Math.max(l,r)}else j?c.push({id:this.rows[E].id,value:g}):c.push(g)}E++}if(h)switch(h.toLowerCase()){case "sum":c=k;break;case "avg":c=k/z;break;case "count":c=z;break;case "min":c=l;break;case "max":c=a}}}}});return c},clearGridData:function(f){return this.each(function(){if(this.grid){if(typeof f!="boolean")f=false;if(this.p.deepempty)b("#"+
b.jgrid.jqID(this.p.id)+" tbody:first tr:gt(0)").remove();else{var j=b("#"+b.jgrid.jqID(this.p.id)+" tbody:first tr:first")[0];b("#"+b.jgrid.jqID(this.p.id)+" tbody:first").empty().append(j)}this.p.footerrow&&f&&b(".ui-jqgrid-ftable td",this.grid.sDiv).html("&#160;");this.p.selrow=null;this.p.selarrrow=[];this.p.savedRow=[];this.p.records=0;this.p.page=1;this.p.lastpage=0;this.p.reccount=0;this.p.data=[];this.p_index={};this.updatepager(true,false)}})},getInd:function(f,j){var h=false,c;this.each(function(){if(c=
this.rows.namedItem(f))h=j===true?c:c.rowIndex});return h},bindKeys:function(f){var j=b.extend({onEnter:null,onSpace:null,onLeftKey:null,onRightKey:null,scrollingRows:true},f||{});return this.each(function(){var h=this;b("body").is("[role]")||b("body").attr("role","application");h.p.scrollrows=j.scrollingRows;b(h).keydown(function(c){var g=b(h).find("tr[tabindex=0]")[0],k,l,a,r=h.p.treeReader.expanded_field;if(g){a=h.p._index[g.id];if(c.keyCode===37||c.keyCode===38||c.keyCode===39||c.keyCode===40){if(c.keyCode===
38){l=g.previousSibling;k="";if(l)if(b(l).is(":hidden"))for(;l;){l=l.previousSibling;if(!b(l).is(":hidden")&&b(l).hasClass("jqgrow")){k=l.id;break}}else k=l.id;b(h).jqGrid("setSelection",k)}if(c.keyCode===40){l=g.nextSibling;k="";if(l)if(b(l).is(":hidden"))for(;l;){l=l.nextSibling;if(!b(l).is(":hidden")&&b(l).hasClass("jqgrow")){k=l.id;break}}else k=l.id;b(h).jqGrid("setSelection",k)}if(c.keyCode===37){h.p.treeGrid&&h.p.data[a][r]&&b(g).find("div.treeclick").trigger("click");b.isFunction(j.onLeftKey)&&
j.onLeftKey.call(h,h.p.selrow)}if(c.keyCode===39){h.p.treeGrid&&!h.p.data[a][r]&&b(g).find("div.treeclick").trigger("click");b.isFunction(j.onRightKey)&&j.onRightKey.call(h,h.p.selrow)}return false}else if(c.keyCode===13){b.isFunction(j.onEnter)&&j.onEnter.call(h,h.p.selrow);return false}else if(c.keyCode===32){b.isFunction(j.onSpace)&&j.onSpace.call(h,h.p.selrow);return false}}})})},unbindKeys:function(){return this.each(function(){b(this).unbind("keydown")})},getLocalRow:function(f){var j=false,
h;this.each(function(){if(typeof f!=="undefined"){h=this.p._index[f];if(h>=0)j=this.p.data[h]}});return j}})})(jQuery);
(function(b){b.fmatter={};b.extend(b.fmatter,{isBoolean:function(a){return typeof a==="boolean"},isObject:function(a){return a&&(typeof a==="object"||b.isFunction(a))||false},isString:function(a){return typeof a==="string"},isNumber:function(a){return typeof a==="number"&&isFinite(a)},isNull:function(a){return a===null},isUndefined:function(a){return typeof a==="undefined"},isValue:function(a){return this.isObject(a)||this.isString(a)||this.isNumber(a)||this.isBoolean(a)},isEmpty:function(a){if(!this.isString(a)&&
this.isValue(a))return false;else if(!this.isValue(a))return true;a=b.trim(a).replace(/\&nbsp\;/ig,"").replace(/\&#160\;/ig,"");return a===""}});b.fn.fmatter=function(a,c,d,e,f){var g=c;d=b.extend({},b.jgrid.formatter,d);if(b.fn.fmatter[a])g=b.fn.fmatter[a](c,d,e,f);return g};b.fmatter.util={NumberFormat:function(a,c){b.fmatter.isNumber(a)||(a*=1);if(b.fmatter.isNumber(a)){var d=a<0,e=a+"",f=c.decimalSeparator?c.decimalSeparator:".",g;if(b.fmatter.isNumber(c.decimalPlaces)){var h=c.decimalPlaces;
e=Math.pow(10,h);e=Math.round(a*e)/e+"";g=e.lastIndexOf(".");if(h>0){if(g<0){e+=f;g=e.length-1}else if(f!==".")e=e.replace(".",f);for(;e.length-1-g<h;)e+="0"}}if(c.thousandsSeparator){h=c.thousandsSeparator;g=e.lastIndexOf(f);g=g>-1?g:e.length;f=e.substring(g);for(var i=-1,j=g;j>0;j--){i++;if(i%3===0&&j!==g&&(!d||j>1))f=h+f;f=e.charAt(j-1)+f}e=f}e=c.prefix?c.prefix+e:e;return e=c.suffix?e+c.suffix:e}else return a},DateFormat:function(a,c,d,e){var f=/^\/Date\((([-+])?[0-9]+)(([-+])([0-9]{2})([0-9]{2}))?\)\/$/,
g=typeof c==="string"?c.match(f):null;f=function(m,r){m=String(m);for(r=parseInt(r,10)||2;m.length<r;)m="0"+m;return m};var h={m:1,d:1,y:1970,h:0,i:0,s:0,u:0},i=0,j,k=["i18n"];k.i18n={dayNames:e.dayNames,monthNames:e.monthNames};if(a in e.masks)a=e.masks[a];if(c.constructor===Number){if(String(a).toLowerCase()=="u")c*=1E3;i=new Date(c)}else if(c.constructor===Date)i=c;else if(g!==null){i=new Date(parseInt(g[1],10));if(g[3]){a=Number(g[5])*60+Number(g[6]);a*=g[4]=="-"?1:-1;a-=i.getTimezoneOffset();
i.setTime(Number(Number(i)+a*6E4))}}else{c=String(c).split(/[\\\/:_;.,\t\T\s-]/);a=a.split(/[\\\/:_;.,\t\T\s-]/);g=0;for(j=a.length;g<j;g++){if(a[g]=="M"){i=b.inArray(c[g],k.i18n.monthNames);if(i!==-1&&i<12)c[g]=i+1}if(a[g]=="F"){i=b.inArray(c[g],k.i18n.monthNames);if(i!==-1&&i>11)c[g]=i+1-12}if(c[g])h[a[g].toLowerCase()]=parseInt(c[g],10)}if(h.f)h.m=h.f;if(h.m===0&&h.y===0&&h.d===0)return"&#160;";h.m=parseInt(h.m,10)-1;i=h.y;if(i>=70&&i<=99)h.y=1900+h.y;else if(i>=0&&i<=69)h.y=2E3+h.y;i=new Date(h.y,
h.m,h.d,h.h,h.i,h.s,h.u)}if(d in e.masks)d=e.masks[d];else d||(d="Y-m-d");a=i.getHours();c=i.getMinutes();h=i.getDate();g=i.getMonth()+1;j=i.getTimezoneOffset();var l=i.getSeconds(),o=i.getMilliseconds(),n=i.getDay(),p=i.getFullYear(),q=(n+6)%7+1,s=(new Date(p,g-1,h)-new Date(p,0,1))/864E5,t={d:f(h),D:k.i18n.dayNames[n],j:h,l:k.i18n.dayNames[n+7],N:q,S:e.S(h),w:n,z:s,W:q<5?Math.floor((s+q-1)/7)+1:Math.floor((s+q-1)/7)||(((new Date(p-1,0,1)).getDay()+6)%7<4?53:52),F:k.i18n.monthNames[g-1+12],m:f(g),
M:k.i18n.monthNames[g-1],n:g,t:"?",L:"?",o:"?",Y:p,y:String(p).substring(2),a:a<12?e.AmPm[0]:e.AmPm[1],A:a<12?e.AmPm[2]:e.AmPm[3],B:"?",g:a%12||12,G:a,h:f(a%12||12),H:f(a),i:f(c),s:f(l),u:o,e:"?",I:"?",O:(j>0?"-":"+")+f(Math.floor(Math.abs(j)/60)*100+Math.abs(j)%60,4),P:"?",T:(String(i).match(/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g)||[""]).pop().replace(/[^-+\dA-Z]/g,""),Z:"?",c:"?",r:"?",U:Math.floor(i/
1E3)};return d.replace(/\\.|[dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU]/g,function(m){return m in t?t[m]:m.substring(1)})}};b.fn.fmatter.defaultFormat=function(a,c){return b.fmatter.isValue(a)&&a!==""?a:c.defaultValue?c.defaultValue:"&#160;"};b.fn.fmatter.email=function(a,c){return b.fmatter.isEmpty(a)?b.fn.fmatter.defaultFormat(a,c):'<a href="mailto:'+a+'">'+a+"</a>"};b.fn.fmatter.checkbox=function(a,c){var d=b.extend({},c.checkbox),e;b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend({},d,
c.colModel.formatoptions));e=d.disabled===true?'disabled="disabled"':"";if(b.fmatter.isEmpty(a)||b.fmatter.isUndefined(a))a=b.fn.fmatter.defaultFormat(a,d);a+="";a=a.toLowerCase();return'<input type="checkbox" '+(a.search(/(false|0|no|off)/i)<0?" checked='checked' ":"")+' value="'+a+'" offval="no" '+e+"/>"};b.fn.fmatter.link=function(a,c){var d={target:c.target},e="";b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend({},d,c.colModel.formatoptions));if(d.target)e="target="+d.target;return b.fmatter.isEmpty(a)?
b.fn.fmatter.defaultFormat(a,c):"<a "+e+' href="'+a+'">'+a+"</a>"};b.fn.fmatter.showlink=function(a,c){var d={baseLinkUrl:c.baseLinkUrl,showAction:c.showAction,addParam:c.addParam||"",target:c.target,idName:c.idName},e="";b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend({},d,c.colModel.formatoptions));if(d.target)e="target="+d.target;d=d.baseLinkUrl+d.showAction+"?"+d.idName+"="+c.rowId+d.addParam;return b.fmatter.isString(a)||b.fmatter.isNumber(a)?"<a "+e+' href="'+d+'">'+a+"</a>":b.fn.fmatter.defaultFormat(a,
c)};b.fn.fmatter.integer=function(a,c){var d=b.extend({},c.integer);b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend({},d,c.colModel.formatoptions));if(b.fmatter.isEmpty(a))return d.defaultValue;return b.fmatter.util.NumberFormat(a,d)};b.fn.fmatter.number=function(a,c){var d=b.extend({},c.number);b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend({},d,c.colModel.formatoptions));if(b.fmatter.isEmpty(a))return d.defaultValue;return b.fmatter.util.NumberFormat(a,d)};b.fn.fmatter.currency=
function(a,c){var d=b.extend({},c.currency);b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend({},d,c.colModel.formatoptions));if(b.fmatter.isEmpty(a))return d.defaultValue;return b.fmatter.util.NumberFormat(a,d)};b.fn.fmatter.date=function(a,c,d,e){d=b.extend({},c.date);b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend({},d,c.colModel.formatoptions));return!d.reformatAfterEdit&&e=="edit"?b.fn.fmatter.defaultFormat(a,c):b.fmatter.isEmpty(a)?b.fn.fmatter.defaultFormat(a,c):b.fmatter.util.DateFormat(d.srcformat,
a,d.newformat,d)};b.fn.fmatter.select=function(a,c){a+="";var d=false,e=[];if(b.fmatter.isUndefined(c.colModel.formatoptions)){if(!b.fmatter.isUndefined(c.colModel.editoptions))d=c.colModel.editoptions.value}else d=c.colModel.formatoptions.value;if(d){var f=c.colModel.editoptions.multiple===true?true:false,g=[],h;if(f){g=a.split(",");g=b.map(g,function(l){return b.trim(l)})}if(b.fmatter.isString(d))for(var i=d.split(";"),j=0,k=0;k<i.length;k++){h=i[k].split(":");if(h.length>2)h[1]=jQuery.map(h,function(l,
o){if(o>0)return l}).join(":");if(f){if(jQuery.inArray(h[0],g)>-1){e[j]=h[1];j++}}else if(b.trim(h[0])==b.trim(a)){e[0]=h[1];break}}else if(b.fmatter.isObject(d))if(f)e=jQuery.map(g,function(l){return d[l]});else e[0]=d[a]||""}a=e.join(", ");return a===""?b.fn.fmatter.defaultFormat(a,c):a};b.fn.fmatter.rowactions=function(a,c,d,e){var f={keys:false,editbutton:true,delbutton:true,onEdit:null,onSuccess:null,afterSave:null,onError:null,afterRestore:null,extraparam:{oper:"edit"},url:null,delOptions:{}};
e=b("#"+c)[0].p.colModel[e];b.fmatter.isUndefined(e.formatoptions)||(f=b.extend(f,e.formatoptions));e=function(h){f.afterSave&&f.afterSave(h);b("tr#"+a+" div.ui-inline-edit, tr#"+a+" div.ui-inline-del","#"+c).show();b("tr#"+a+" div.ui-inline-save, tr#"+a+" div.ui-inline-cancel","#"+c).hide()};var g=function(h){f.afterRestore&&f.afterRestore(h);b("tr#"+a+" div.ui-inline-edit, tr#"+a+" div.ui-inline-del","#"+c).show();b("tr#"+a+" div.ui-inline-save, tr#"+a+" div.ui-inline-cancel","#"+c).hide()};switch(d){case "edit":b("#"+
c).jqGrid("editRow",a,f.keys,f.onEdit,f.onSuccess,f.url,f.extraparam,e,f.onError,g);b("tr#"+a+" div.ui-inline-edit, tr#"+a+" div.ui-inline-del","#"+c).hide();b("tr#"+a+" div.ui-inline-save, tr#"+a+" div.ui-inline-cancel","#"+c).show();break;case "save":if(b("#"+c).jqGrid("saveRow",a,f.onSuccess,f.url,f.extraparam,e,f.onError,g)){b("tr#"+a+" div.ui-inline-edit, tr#"+a+" div.ui-inline-del","#"+c).show();b("tr#"+a+" div.ui-inline-save, tr#"+a+" div.ui-inline-cancel","#"+c).hide()}break;case "cancel":b("#"+
c).jqGrid("restoreRow",a,g);b("tr#"+a+" div.ui-inline-edit, tr#"+a+" div.ui-inline-del","#"+c).show();b("tr#"+a+" div.ui-inline-save, tr#"+a+" div.ui-inline-cancel","#"+c).hide();break;case "del":b("#"+c).jqGrid("delGridRow",a,f.delOptions)}};b.fn.fmatter.actions=function(a,c){var d={keys:false,editbutton:true,delbutton:true};b.fmatter.isUndefined(c.colModel.formatoptions)||(d=b.extend(d,c.colModel.formatoptions));var e=c.rowId,f="",g;if(typeof e=="undefined"||b.fmatter.isEmpty(e))return"";if(d.editbutton){g=
"onclick=$.fn.fmatter.rowactions('"+e+"','"+c.gid+"','edit',"+c.pos+");";f=f+"<div style='margin-left:8px;'><div title='"+b.jgrid.nav.edittitle+"' style='float:left;cursor:pointer;' class='ui-pg-div ui-inline-edit' "+g+"><span class='ui-icon ui-icon-pencil'></span></div>"}if(d.delbutton){g="onclick=$.fn.fmatter.rowactions('"+e+"','"+c.gid+"','del',"+c.pos+");";f=f+"<div title='"+b.jgrid.nav.deltitle+"' style='float:left;margin-left:5px;' class='ui-pg-div ui-inline-del' "+g+"><span class='ui-icon ui-icon-trash'></span></div>"}g=
"onclick=$.fn.fmatter.rowactions('"+e+"','"+c.gid+"','save',"+c.pos+");";f=f+"<div title='"+b.jgrid.edit.bSubmit+"' style='float:left;display:none' class='ui-pg-div ui-inline-save'><span class='ui-icon ui-icon-disk' "+g+"></span></div>";g="onclick=$.fn.fmatter.rowactions('"+e+"','"+c.gid+"','cancel',"+c.pos+");";return f=f+"<div title='"+b.jgrid.edit.bCancel+"' style='float:left;display:none;margin-left:5px;' class='ui-pg-div ui-inline-cancel'><span class='ui-icon ui-icon-cancel' "+g+"></span></div></div>"};
b.unformat=function(a,c,d,e){var f,g=c.colModel.formatter,h=c.colModel.formatoptions||{},i=/([\.\*\_\'\(\)\{\}\+\?\\])/g,j=c.colModel.unformat||b.fn.fmatter[g]&&b.fn.fmatter[g].unformat;if(typeof j!=="undefined"&&b.isFunction(j))f=j(b(a).text(),c,a);else if(!b.fmatter.isUndefined(g)&&b.fmatter.isString(g)){f=b.jgrid.formatter||{};switch(g){case "integer":h=b.extend({},f.integer,h);c=h.thousandsSeparator.replace(i,"\\$1");f=b(a).text().replace(RegExp(c,"g"),"");break;case "number":h=b.extend({},f.number,
h);c=h.thousandsSeparator.replace(i,"\\$1");f=b(a).text().replace(RegExp(c,"g"),"").replace(h.decimalSeparator,".");break;case "currency":h=b.extend({},f.currency,h);c=h.thousandsSeparator.replace(i,"\\$1");f=b(a).text().replace(RegExp(c,"g"),"").replace(h.decimalSeparator,".").replace(h.prefix,"").replace(h.suffix,"");break;case "checkbox":h=c.colModel.editoptions?c.colModel.editoptions.value.split(":"):["Yes","No"];f=b("input",a).attr("checked")?h[0]:h[1];break;case "select":f=b.unformat.select(a,
c,d,e);break;case "actions":return"";default:f=b(a).text()}}return f?f:e===true?b(a).text():b.jgrid.htmlDecode(b(a).html())};b.unformat.select=function(a,c,d,e){d=[];a=b(a).text();if(e===true)return a;c=b.extend({},c.colModel.editoptions);if(c.value){var f=c.value;c=c.multiple===true?true:false;e=[];var g;if(c){e=a.split(",");e=b.map(e,function(k){return b.trim(k)})}if(b.fmatter.isString(f))for(var h=f.split(";"),i=0,j=0;j<h.length;j++){g=h[j].split(":");if(g.length>2)g[1]=jQuery.map(g,function(k,
l){if(l>0)return k}).join(":");if(c){if(jQuery.inArray(g[1],e)>-1){d[i]=g[0];i++}}else if(b.trim(g[1])==b.trim(a)){d[0]=g[0];break}}else if(b.fmatter.isObject(f)||b.isArray(f)){c||(e[0]=a);d=jQuery.map(e,function(k){var l;b.each(f,function(o,n){if(n==k){l=o;return false}});if(typeof l!="undefined")return l})}return d.join(", ")}else return a||""};b.unformat.date=function(a,c){var d=b.jgrid.formatter.date||{};b.fmatter.isUndefined(c.formatoptions)||(d=b.extend({},d,c.formatoptions));return b.fmatter.isEmpty(a)?
b.fn.fmatter.defaultFormat(a,c):b.fmatter.util.DateFormat(d.newformat,a,d.srcformat,d)}})(jQuery);
(function(a){a.jgrid.extend({getColProp:function(c){var h={},b=this[0];if(!b.grid)return false;b=b.p.colModel;for(var i=0;i<b.length;i++)if(b[i].name==c){h=b[i];break}return h},setColProp:function(c,h){return this.each(function(){if(this.grid)if(h)for(var b=this.p.colModel,i=0;i<b.length;i++)if(b[i].name==c){a.extend(this.p.colModel[i],h);break}})},sortGrid:function(c,h,b){return this.each(function(){var i=-1;if(this.grid){if(!c)c=this.p.sortname;for(var o=0;o<this.p.colModel.length;o++)if(this.p.colModel[o].index==
c||this.p.colModel[o].name==c){i=o;break}if(i!=-1){o=this.p.colModel[i].sortable;if(typeof o!=="boolean")o=true;if(typeof h!=="boolean")h=false;o&&this.sortData("jqgh_"+c,i,h,b)}}})},GridDestroy:function(){return this.each(function(){if(this.grid){this.p.pager&&a(this.p.pager).remove();var c=this.id;try{a("#gbox_"+c).remove()}catch(h){}}})},GridUnload:function(){return this.each(function(){if(this.grid){var c={id:a(this).attr("id"),cl:a(this).attr("class")};this.p.pager&&a(this.p.pager).empty().removeClass("ui-state-default ui-jqgrid-pager corner-bottom");
var h=document.createElement("table");a(h).attr({id:c.id});h.className=c.cl;c=this.id;a(h).removeClass("ui-jqgrid-btable");if(a(this.p.pager).parents("#gbox_"+c).length===1){a(h).insertBefore("#gbox_"+c).show();a(this.p.pager).insertBefore("#gbox_"+c)}else a(h).insertBefore("#gbox_"+c).show();a("#gbox_"+c).remove()}})},setGridState:function(c){return this.each(function(){if(this.grid)if(c=="hidden"){a(".ui-jqgrid-bdiv, .ui-jqgrid-hdiv","#gview_"+this.p.id).slideUp("fast");this.p.pager&&a(this.p.pager).slideUp("fast");
this.p.toppager&&a(this.p.toppager).slideUp("fast");if(this.p.toolbar[0]===true){this.p.toolbar[1]=="both"&&a(this.grid.ubDiv).slideUp("fast");a(this.grid.uDiv).slideUp("fast")}this.p.footerrow&&a(".ui-jqgrid-sdiv","#gbox_"+this.p.id).slideUp("fast");a(".ui-jqgrid-titlebar-close span",this.grid.cDiv).removeClass("ui-icon-circle-triangle-n").addClass("ui-icon-circle-triangle-s");this.p.gridstate="hidden"}else if(c=="visible"){a(".ui-jqgrid-hdiv, .ui-jqgrid-bdiv","#gview_"+this.p.id).slideDown("fast");
this.p.pager&&a(this.p.pager).slideDown("fast");this.p.toppager&&a(this.p.toppager).slideDown("fast");if(this.p.toolbar[0]===true){this.p.toolbar[1]=="both"&&a(this.grid.ubDiv).slideDown("fast");a(this.grid.uDiv).slideDown("fast")}this.p.footerrow&&a(".ui-jqgrid-sdiv","#gbox_"+this.p.id).slideDown("fast");a(".ui-jqgrid-titlebar-close span",this.grid.cDiv).removeClass("ui-icon-circle-triangle-s").addClass("ui-icon-circle-triangle-n");this.p.gridstate="visible"}})},filterToolbar:function(c){c=a.extend({autosearch:true,
searchOnEnter:true,beforeSearch:null,afterSearch:null,beforeClear:null,afterClear:null,searchurl:"",stringResult:false,groupOp:"AND",defaultSearch:"bw"},c||{});return this.each(function(){function h(e,f){var j=a(e);j[0]&&jQuery.each(f,function(){this.data!==undefined?j.bind(this.type,this.data,this.fn):j.bind(this.type,this.fn)})}var b=this;if(!this.ftoolbar){var i=function(){var e={},f=0,j,d,g={},k;a.each(b.p.colModel,function(){d=this.index||this.name;switch(this.stype){case "select":k=this.searchoptions&&
this.searchoptions.sopt?this.searchoptions.sopt[0]:"eq";if(j=a("#gs_"+a.jgrid.jqID(d),b.grid.hDiv).val()){e[d]=j;g[d]=k;f++}else try{delete b.p.postData[d]}catch(p){}break;case "text":k=this.searchoptions&&this.searchoptions.sopt?this.searchoptions.sopt[0]:c.defaultSearch;if(j=a("#gs_"+a.jgrid.jqID(d),b.grid.hDiv).val()){e[d]=j;g[d]=k;f++}else try{delete b.p.postData[d]}catch(s){}}});var n=f>0?true:false;if(c.stringResult===true||b.p.datatype=="local"){var m='{"groupOp":"'+c.groupOp+'","rules":[',
q=0;a.each(e,function(p,s){if(q>0)m+=",";m+='{"field":"'+p+'",';m+='"op":"'+g[p]+'",';s+="";m+='"data":"'+s.replace(/\\/g,"\\\\").replace(/\"/g,'\\"')+'"}';q++});m+="]}";a.extend(b.p.postData,{filters:m});a.each(["searchField","searchString","searchOper"],function(p,s){b.p.postData.hasOwnProperty(s)&&delete b.p.postData[s]})}else a.extend(b.p.postData,e);var l;if(b.p.searchurl){l=b.p.url;a(b).jqGrid("setGridParam",{url:b.p.searchurl})}var r=false;if(a.isFunction(c.beforeSearch))r=c.beforeSearch.call(b);
r||a(b).jqGrid("setGridParam",{search:n}).trigger("reloadGrid",[{page:1}]);l&&a(b).jqGrid("setGridParam",{url:l});a.isFunction(c.afterSearch)&&c.afterSearch()},o=a("<tr class='ui-search-toolbar' role='rowheader'></tr>"),t;a.each(b.p.colModel,function(){var e=this,f,j,d,g;j=a("<th role='columnheader' class='ui-state-default ui-th-column ui-th-"+b.p.direction+"'></th>");f=a("<div style='width:100%;position:relative;height:100%;padding-right:0.3em;'></div>");this.hidden===true&&a(j).css("display","none");
this.search=this.search===false?false:true;if(typeof this.stype=="undefined")this.stype="text";d=a.extend({},this.searchoptions||{});if(this.search)switch(this.stype){case "select":if(g=this.surl||d.dataUrl)a.ajax(a.extend({url:g,dataType:"html",complete:function(l){if(d.buildSelect!==undefined)(l=d.buildSelect(l))&&a(f).append(l);else a(f).append(l.responseText);d.defaultValue&&a("select",f).val(d.defaultValue);a("select",f).attr({name:e.index||e.name,id:"gs_"+e.name});d.attr&&a("select",f).attr(d.attr);
a("select",f).css({width:"100%"});d.dataInit!==undefined&&d.dataInit(a("select",f)[0]);d.dataEvents!==undefined&&h(a("select",f)[0],d.dataEvents);c.autosearch===true&&a("select",f).change(function(){i();return false});l=null}},a.jgrid.ajaxOptions,b.p.ajaxSelectOptions||{}));else{var k;if(e.searchoptions&&e.searchoptions.value)k=e.searchoptions.value;else if(e.editoptions&&e.editoptions.value)k=e.editoptions.value;if(k){g=document.createElement("select");g.style.width="100%";a(g).attr({name:e.index||
e.name,id:"gs_"+e.name});var n,m;if(typeof k==="string"){k=k.split(";");for(var q=0;q<k.length;q++){n=k[q].split(":");m=document.createElement("option");m.value=n[0];m.innerHTML=n[1];g.appendChild(m)}}else if(typeof k==="object")for(n in k)if(k.hasOwnProperty(n)){m=document.createElement("option");m.value=n;m.innerHTML=k[n];g.appendChild(m)}d.defaultValue&&a(g).val(d.defaultValue);d.attr&&a(g).attr(d.attr);d.dataInit!==undefined&&d.dataInit(g);d.dataEvents!==undefined&&h(g,d.dataEvents);a(f).append(g);
c.autosearch===true&&a(g).change(function(){i();return false})}}break;case "text":g=d.defaultValue?d.defaultValue:"";a(f).append("<input type='text' style='width:95%;padding:0px;' name='"+(e.index||e.name)+"' id='gs_"+e.name+"' value='"+g+"'/>");d.attr&&a("input",f).attr(d.attr);d.dataInit!==undefined&&d.dataInit(a("input",f)[0]);d.dataEvents!==undefined&&h(a("input",f)[0],d.dataEvents);if(c.autosearch===true)c.searchOnEnter?a("input",f).keypress(function(l){if((l.charCode?l.charCode:l.keyCode?l.keyCode:
0)==13){i();return false}return this}):a("input",f).keydown(function(l){switch(l.which){case 13:return false;case 9:case 16:case 37:case 38:case 39:case 40:case 27:break;default:t&&clearTimeout(t);t=setTimeout(function(){i()},500)}})}a(j).append(f);a(o).append(j)});a("table thead",b.grid.hDiv).append(o);this.ftoolbar=true;this.triggerToolbar=i;this.clearToolbar=function(e){var f={},j,d=0,g;e=typeof e!="boolean"?true:e;a.each(b.p.colModel,function(){j=this.searchoptions&&this.searchoptions.defaultValue?
this.searchoptions.defaultValue:"";g=this.index||this.name;switch(this.stype){case "select":var r;a("#gs_"+a.jgrid.jqID(g)+" option",b.grid.hDiv).each(function(u){if(u===0)this.selected=true;if(a(this).text()==j){this.selected=true;r=a(this).val();return false}});if(r){f[g]=r;d++}else try{delete b.p.postData[g]}catch(p){}break;case "text":a("#gs_"+a.jgrid.jqID(g),b.grid.hDiv).val(j);if(j){f[g]=j;d++}else try{delete b.p.postData[g]}catch(s){}}});var k=d>0?true:false;if(c.stringResult===true||b.p.datatype==
"local"){var n='{"groupOp":"'+c.groupOp+'","rules":[',m=0;a.each(f,function(r,p){if(m>0)n+=",";n+='{"field":"'+r+'",';n+='"op":"eq",';p+="";n+='"data":"'+p.replace(/\\/g,"\\\\").replace(/\"/g,'\\"')+'"}';m++});n+="]}";a.extend(b.p.postData,{filters:n});a.each(["searchField","searchString","searchOper"],function(r,p){b.p.postData.hasOwnProperty(p)&&delete b.p.postData[p]})}else a.extend(b.p.postData,f);var q;if(b.p.searchurl){q=b.p.url;a(b).jqGrid("setGridParam",{url:b.p.searchurl})}var l=false;if(a.isFunction(c.beforeClear))l=
c.beforeClear.call(b);l||e&&a(b).jqGrid("setGridParam",{search:k}).trigger("reloadGrid",[{page:1}]);q&&a(b).jqGrid("setGridParam",{url:q});a.isFunction(c.afterClear)&&c.afterClear()};this.toggleToolbar=function(){var e=a("tr.ui-search-toolbar",b.grid.hDiv);e.css("display")=="none"?e.show():e.hide()}}})}})})(jQuery);
(function(a){a.extend(a.jgrid,{showModal:function(b){b.w.show()},closeModal:function(b){b.w.hide().attr("aria-hidden","true");b.o&&b.o.remove()},hideModal:function(b,c){c=a.extend({jqm:true,gb:""},c||{});if(c.onClose){var d=c.onClose(b);if(typeof d=="boolean"&&!d)return}if(a.fn.jqm&&c.jqm===true)a(b).attr("aria-hidden","true").jqmHide();else{if(c.gb!=="")try{a(".jqgrid-overlay:first",c.gb).hide()}catch(f){}a(b).hide().attr("aria-hidden","true")}},findPos:function(b){var c=0,d=0;if(b.offsetParent){do{c+=
b.offsetLeft;d+=b.offsetTop}while(b=b.offsetParent)}return[c,d]},createModal:function(b,c,d,f,g,h,j){var e=document.createElement("div"),k,m=this;j=a.extend({},j||{});k=a(d.gbox).attr("dir")=="rtl"?true:false;e.className="ui-widget ui-widget-content ui-corner-all ui-jqdialog";e.id=b.themodal;var i=document.createElement("div");i.className="ui-jqdialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix";i.id=b.modalhead;a(i).append("<span class='ui-jqdialog-title'>"+d.caption+"</span>");var q=
a("<a href='javascript:void(0)' class='ui-jqdialog-titlebar-close ui-corner-all'></a>").hover(function(){q.addClass("ui-state-hover")},function(){q.removeClass("ui-state-hover")}).append("<span class='ui-icon ui-icon-closethick'></span>");a(i).append(q);if(k){e.dir="rtl";a(".ui-jqdialog-title",i).css("float","right");a(".ui-jqdialog-titlebar-close",i).css("left","0.3em")}else{e.dir="ltr";a(".ui-jqdialog-title",i).css("float","left");a(".ui-jqdialog-titlebar-close",i).css("right","0.3em")}var l=document.createElement("div");
a(l).addClass("ui-jqdialog-content ui-widget-content").attr("id",b.modalcontent);a(l).append(c);e.appendChild(l);a(e).prepend(i);if(h===true)a("body").append(e);else typeof h=="string"?a(h).append(e):a(e).insertBefore(f);a(e).css(j);if(typeof d.jqModal==="undefined")d.jqModal=true;c={};if(a.fn.jqm&&d.jqModal===true){if(d.left===0&&d.top===0&&d.overlay){j=[];j=this.findPos(g);d.left=j[0]+4;d.top=j[1]+4}c.top=d.top+"px";c.left=d.left}else if(d.left!==0||d.top!==0){c.left=d.left;c.top=d.top+"px"}a("a.ui-jqdialog-titlebar-close",
i).click(function(){var p=a("#"+b.themodal).data("onClose")||d.onClose,o=a("#"+b.themodal).data("gbox")||d.gbox;m.hideModal("#"+b.themodal,{gb:o,jqm:d.jqModal,onClose:p});return false});if(d.width===0||!d.width)d.width=300;if(d.height===0||!d.height)d.height=200;if(!d.zIndex){f=a(f).parents("*[role=dialog]").filter(":first").css("z-index");d.zIndex=f?parseInt(f,10)+1:950}f=0;if(k&&c.left&&!h){f=a(d.gbox).width()-(!isNaN(d.width)?parseInt(d.width,10):0)-8;c.left=parseInt(c.left,10)+parseInt(f,10)}if(c.left)c.left+=
"px";a(e).css(a.extend({width:isNaN(d.width)?"auto":d.width+"px",height:isNaN(d.height)?"auto":d.height+"px",zIndex:d.zIndex,overflow:"hidden"},c)).attr({tabIndex:"-1",role:"dialog","aria-labelledby":b.modalhead,"aria-hidden":"true"});if(typeof d.drag=="undefined")d.drag=true;if(typeof d.resize=="undefined")d.resize=true;if(d.drag){a(i).css("cursor","move");if(a.fn.jqDrag)a(e).jqDrag(i);else try{a(e).draggable({handle:a("#"+i.id)})}catch(n){}}if(d.resize)if(a.fn.jqResize){a(e).append("<div class='jqResize ui-resizable-handle ui-resizable-se ui-icon ui-icon-gripsmall-diagonal-se ui-icon-grip-diagonal-se'></div>");
a("#"+b.themodal).jqResize(".jqResize",b.scrollelm?"#"+b.scrollelm:false)}else try{a(e).resizable({handles:"se, sw",alsoResize:b.scrollelm?"#"+b.scrollelm:false})}catch(r){}d.closeOnEscape===true&&a(e).keydown(function(p){if(p.which==27){p=a("#"+b.themodal).data("onClose")||d.onClose;m.hideModal(this,{gb:d.gbox,jqm:d.jqModal,onClose:p})}})},viewModal:function(b,c){c=a.extend({toTop:true,overlay:10,modal:false,onShow:this.showModal,onHide:this.closeModal,gbox:"",jqm:true,jqM:true},c||{});if(a.fn.jqm&&
c.jqm===true)c.jqM?a(b).attr("aria-hidden","false").jqm(c).jqmShow():a(b).attr("aria-hidden","false").jqmShow();else{if(c.gbox!==""){a(".jqgrid-overlay:first",c.gbox).show();a(b).data("gbox",c.gbox)}a(b).show().attr("aria-hidden","false");try{a(":input:visible",b)[0].focus()}catch(d){}}},info_dialog:function(b,c,d,f){var g={width:290,height:"auto",dataheight:"auto",drag:true,resize:false,caption:"<b>"+b+"</b>",left:250,top:170,zIndex:1E3,jqModal:true,modal:false,closeOnEscape:true,align:"center",
buttonalign:"center",buttons:[]};a.extend(g,f||{});var h=g.jqModal,j=this;if(a.fn.jqm&&!h)h=false;b="";if(g.buttons.length>0)for(f=0;f<g.buttons.length;f++){if(typeof g.buttons[f].id=="undefined")g.buttons[f].id="info_button_"+f;b+="<a href='javascript:void(0)' id='"+g.buttons[f].id+"' class='fm-button ui-state-default ui-corner-all'>"+g.buttons[f].text+"</a>"}f=isNaN(g.dataheight)?g.dataheight:g.dataheight+"px";var e="<div id='info_id'>";e+="<div id='infocnt' style='margin:0px;padding-bottom:1em;width:100%;overflow:auto;position:relative;height:"+
f+";"+("text-align:"+g.align+";")+"'>"+c+"</div>";e+=d?"<div class='ui-widget-content ui-helper-clearfix' style='text-align:"+g.buttonalign+";padding-bottom:0.8em;padding-top:0.5em;background-image: none;border-width: 1px 0 0 0;'><a href='javascript:void(0)' id='closedialog' class='fm-button ui-state-default ui-corner-all'>"+d+"</a>"+b+"</div>":b!==""?"<div class='ui-widget-content ui-helper-clearfix' style='text-align:"+g.buttonalign+";padding-bottom:0.8em;padding-top:0.5em;background-image: none;border-width: 1px 0 0 0;'>"+
b+"</div>":"";e+="</div>";try{a("#info_dialog").attr("aria-hidden")=="false"&&this.hideModal("#info_dialog",{jqm:h});a("#info_dialog").remove()}catch(k){}this.createModal({themodal:"info_dialog",modalhead:"info_head",modalcontent:"info_content",scrollelm:"infocnt"},e,g,"","",true);b&&a.each(g.buttons,function(i){a("#"+this.id,"#info_id").bind("click",function(){g.buttons[i].onClick.call(a("#info_dialog"));return false})});a("#closedialog","#info_id").click(function(){j.hideModal("#info_dialog",{jqm:h});
return false});a(".fm-button","#info_dialog").hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});a.isFunction(g.beforeOpen)&&g.beforeOpen();this.viewModal("#info_dialog",{onHide:function(i){i.w.hide().remove();i.o&&i.o.remove()},modal:g.modal,jqm:h});a.isFunction(g.afterOpen)&&g.afterOpen();try{a("#info_dialog").focus()}catch(m){}},createEl:function(b,c,d,f,g){function h(l,n){a.isFunction(n.dataInit)&&n.dataInit(l);n.dataEvents&&a.each(n.dataEvents,
function(){this.data!==undefined?a(l).bind(this.type,this.data,this.fn):a(l).bind(this.type,this.fn)});return n}function j(l,n){var r=["dataInit","dataEvents","dataUrl","buildSelect","sopt","searchhidden","defaultValue","attr"];a.each(n,function(p,o){a.inArray(p,r)===-1&&a(l).attr(p,o)});n.hasOwnProperty("id")||a(l).attr("id",a.jgrid.randId())}var e="";switch(b){case "textarea":e=document.createElement("textarea");if(f)c.cols||a(e).css({width:"98%"});else if(!c.cols)c.cols=20;if(!c.rows)c.rows=2;
if(d=="&nbsp;"||d=="&#160;"||d.length==1&&d.charCodeAt(0)==160)d="";e.value=d;j(e,c);c=h(e,c);a(e).attr({role:"textbox",multiline:"true"});break;case "checkbox":e=document.createElement("input");e.type="checkbox";if(c.value){b=c.value.split(":");if(d===b[0]){e.checked=true;e.defaultChecked=true}e.value=b[0];a(e).attr("offval",b[1])}else{b=d.toLowerCase();if(b.search(/(false|0|no|off|undefined)/i)<0&&b!==""){e.checked=true;e.defaultChecked=true;e.value=d}else e.value="on";a(e).attr("offval","off")}j(e,
c);c=h(e,c);a(e).attr("role","checkbox");break;case "select":e=document.createElement("select");e.setAttribute("role","select");f=[];if(c.multiple===true){b=true;e.multiple="multiple";a(e).attr("aria-multiselectable","true")}else b=false;if(typeof c.dataUrl!="undefined")a.ajax(a.extend({url:c.dataUrl,type:"GET",dataType:"html",context:{elem:e,options:c,vl:d},success:function(l){var n=[],r=this.elem,p=this.vl,o=a.extend({},this.options),s=o.multiple===true;if(typeof o.buildSelect!="undefined")l=o.buildSelect(l);
if(l=a(l).html()){a(r).append(l);j(r,o);o=h(r,o);if(typeof o.size==="undefined")o.size=s?3:1;if(s){n=p.split(",");n=a.map(n,function(t){return a.trim(t)})}else n[0]=a.trim(p);setTimeout(function(){a("option",r).each(function(){a(this).attr("role","option");if(a.inArray(a.trim(a(this).text()),n)>-1||a.inArray(a.trim(a(this).val()),n)>-1)this.selected="selected"})},0)}}},g||{}));else if(c.value){var k;if(b){f=d.split(",");f=a.map(f,function(l){return a.trim(l)});if(typeof c.size==="undefined")c.size=
3}else c.size=1;if(typeof c.value==="function")c.value=c.value();var m,i;if(typeof c.value==="string"){m=c.value.split(";");for(k=0;k<m.length;k++){i=m[k].split(":");if(i.length>2)i[1]=a.map(i,function(l,n){if(n>0)return l}).join(":");g=document.createElement("option");g.setAttribute("role","option");g.value=i[0];g.innerHTML=i[1];if(!b&&(a.trim(i[0])==a.trim(d)||a.trim(i[1])==a.trim(d)))g.selected="selected";if(b&&(a.inArray(a.trim(i[1]),f)>-1||a.inArray(a.trim(i[0]),f)>-1))g.selected="selected";
e.appendChild(g)}}else if(typeof c.value==="object"){m=c.value;for(k in m)if(m.hasOwnProperty(k)){g=document.createElement("option");g.setAttribute("role","option");g.value=k;g.innerHTML=m[k];if(!b&&(a.trim(k)==a.trim(d)||a.trim(m[k])==a.trim(d)))g.selected="selected";if(b&&(a.inArray(a.trim(m[k]),f)>-1||a.inArray(a.trim(k),f)>-1))g.selected="selected";e.appendChild(g)}}j(e,c);c=h(e,c)}break;case "text":case "password":case "button":k=b=="button"?"button":"textbox";e=document.createElement("input");
e.type=b;e.value=d;j(e,c);c=h(e,c);if(b!="button")if(f)c.size||a(e).css({width:"98%"});else if(!c.size)c.size=20;a(e).attr("role",k);break;case "image":case "file":e=document.createElement("input");e.type=b;j(e,c);c=h(e,c);break;case "custom":e=document.createElement("span");try{if(a.isFunction(c.custom_element))if(m=c.custom_element.call(this,d,c)){m=a(m).addClass("customelement").attr({id:c.id,name:c.name});a(e).empty().append(m)}else throw"e2";else throw"e1";}catch(q){q=="e1"&&this.info_dialog(a.jgrid.errors.errcap,
"function 'custom_element' "+a.jgrid.edit.msg.nodefined,a.jgrid.edit.bClose);q=="e2"?this.info_dialog(a.jgrid.errors.errcap,"function 'custom_element' "+a.jgrid.edit.msg.novalue,a.jgrid.edit.bClose):this.info_dialog(a.jgrid.errors.errcap,typeof q==="string"?q:q.message,a.jgrid.edit.bClose)}}return e},checkDate:function(b,c){var d={},f;b=b.toLowerCase();f=b.indexOf("/")!=-1?"/":b.indexOf("-")!=-1?"-":b.indexOf(".")!=-1?".":"/";b=b.split(f);c=c.split(f);if(c.length!=3)return false;f=-1;for(var g,h=
-1,j=-1,e=0;e<b.length;e++){g=isNaN(c[e])?0:parseInt(c[e],10);d[b[e]]=g;g=b[e];if(g.indexOf("y")!=-1)f=e;if(g.indexOf("m")!=-1)j=e;if(g.indexOf("d")!=-1)h=e}g=b[f]=="y"||b[f]=="yyyy"?4:b[f]=="yy"?2:-1;e=function(m){for(var i=1;i<=m;i++){this[i]=31;if(i==4||i==6||i==9||i==11)this[i]=30;if(i==2)this[i]=29}return this}(12);var k;if(f===-1)return false;else{k=d[b[f]].toString();if(g==2&&k.length==1)g=1;if(k.length!=g||d[b[f]]===0&&c[f]!="00")return false}if(j===-1)return false;else{k=d[b[j]].toString();
if(k.length<1||d[b[j]]<1||d[b[j]]>12)return false}if(h===-1)return false;else{k=d[b[h]].toString();if(k.length<1||d[b[h]]<1||d[b[h]]>31||d[b[j]]==2&&d[b[h]]>(d[b[f]]%4===0&&(d[b[f]]%100!==0||d[b[f]]%400===0)?29:28)||d[b[h]]>e[d[b[j]]])return false}return true},isEmpty:function(b){return b.match(/^\s+$/)||b===""?true:false},checkTime:function(b){var c=/^(\d{1,2}):(\d{2})([ap]m)?$/;if(!this.isEmpty(b))if(b=b.match(c)){if(b[3]){if(b[1]<1||b[1]>12)return false}else if(b[1]>23)return false;if(b[2]>59)return false}else return false;
return true},checkValues:function(b,c,d,f,g){var h,j;if(typeof f==="undefined")if(typeof c=="string"){f=0;for(g=d.p.colModel.length;f<g;f++)if(d.p.colModel[f].name==c){h=d.p.colModel[f].editrules;c=f;try{j=d.p.colModel[f].formoptions.label}catch(e){}break}}else{if(c>=0)h=d.p.colModel[c].editrules}else{h=f;j=g===undefined?"_":g}if(h){j||(j=d.p.colNames[c]);if(h.required===true)if(this.isEmpty(b))return[false,j+": "+a.jgrid.edit.msg.required,""];f=h.required===false?false:true;if(h.number===true)if(!(f===
false&&this.isEmpty(b)))if(isNaN(b))return[false,j+": "+a.jgrid.edit.msg.number,""];if(typeof h.minValue!="undefined"&&!isNaN(h.minValue))if(parseFloat(b)<parseFloat(h.minValue))return[false,j+": "+a.jgrid.edit.msg.minValue+" "+h.minValue,""];if(typeof h.maxValue!="undefined"&&!isNaN(h.maxValue))if(parseFloat(b)>parseFloat(h.maxValue))return[false,j+": "+a.jgrid.edit.msg.maxValue+" "+h.maxValue,""];if(h.email===true)if(!(f===false&&this.isEmpty(b))){g=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i;
if(!g.test(b))return[false,j+": "+a.jgrid.edit.msg.email,""]}if(h.integer===true)if(!(f===false&&this.isEmpty(b))){if(isNaN(b))return[false,j+": "+a.jgrid.edit.msg.integer,""];if(b%1!==0||b.indexOf(".")!=-1)return[false,j+": "+a.jgrid.edit.msg.integer,""]}if(h.date===true)if(!(f===false&&this.isEmpty(b))){c=d.p.colModel[c].formatoptions&&d.p.colModel[c].formatoptions.newformat?d.p.colModel[c].formatoptions.newformat:d.p.colModel[c].datefmt||"Y-m-d";if(!this.checkDate(c,b))return[false,j+": "+a.jgrid.edit.msg.date+
" - "+c,""]}if(h.time===true)if(!(f===false&&this.isEmpty(b)))if(!this.checkTime(b))return[false,j+": "+a.jgrid.edit.msg.date+" - hh:mm (am/pm)",""];if(h.url===true)if(!(f===false&&this.isEmpty(b))){g=/^(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;if(!g.test(b))return[false,j+": "+a.jgrid.edit.msg.url,""]}if(h.custom===true)if(!(f===false&&this.isEmpty(b)))if(a.isFunction(h.custom_func)){b=h.custom_func.call(d,b,j);return a.isArray(b)?
b:[false,a.jgrid.edit.msg.customarray,""]}else return[false,a.jgrid.edit.msg.customfcheck,""]}return[true,"",""]}})})(jQuery);
(function(a){var c=null;a.jgrid.extend({searchGrid:function(d){d=a.extend({recreateFilter:false,drag:true,sField:"searchField",sValue:"searchString",sOper:"searchOper",sFilter:"filters",loadDefaults:true,beforeShowSearch:null,afterShowSearch:null,onInitializeSearch:null,closeAfterSearch:false,closeAfterReset:false,closeOnEscape:false,multipleSearch:false,multipleGroup:false,top:0,left:0,jqModal:true,modal:false,resize:false,width:450,height:"auto",dataheight:"auto",showQuery:false,errorcheck:true,
sopt:null,stringResult:undefined,onClose:null,onSearch:null,onReset:null,toTop:true,overlay:10,columns:[],tmplNames:null,tmplFilters:null,tmplLabel:" Template: ",showOnLoad:false,layer:null},a.jgrid.search,d||{});return this.each(function(){function b(){if(a.isFunction(d.beforeShowSearch)){C=d.beforeShowSearch(a("#"+q));if(typeof C==="undefined")C=true}if(C){a.jgrid.viewModal("#"+z.themodal,{gbox:"#gbox_"+q,jqm:d.jqModal,modal:d.modal,overlay:d.overlay,toTop:d.toTop});a.isFunction(d.afterShowSearch)&&
d.afterShowSearch(a("#"+q))}}var l=this;if(l.grid){var q="fbox_"+l.p.id,C=true,z={themodal:"searchmod"+q,modalhead:"searchhd"+q,modalcontent:"searchcnt"+q,scrollelm:q},D=l.p.postData[d.sFilter];if(typeof D==="string")D=a.jgrid.parse(D);d.recreateFilter===true&&a("#"+z.themodal).remove();if(a("#"+z.themodal).html()!==null)b();else{var w=a("<span><div id='"+q+"' class='searchFilter' style='overflow:auto'></div></span>").insertBefore("#gview_"+l.p.id);if(a.isFunction(d.onInitializeSearch))d.onInitializeSearch(a("#"+
q));var n=a.extend([],l.p.colModel),s="<a href='javascript:void(0)' id='"+q+"_search' class='fm-button ui-state-default ui-corner-all fm-button-icon-right ui-reset'><span class='ui-icon ui-icon-search'></span>"+d.Find+"</a>",t="<a href='javascript:void(0)' id='"+q+"_reset' class='fm-button ui-state-default ui-corner-all fm-button-icon-left ui-search'><span class='ui-icon ui-icon-arrowreturnthick-1-w'></span>"+d.Reset+"</a>",p="",e="",u,o=false;if(d.showQuery)p="<a href='javascript:void(0)' id='"+
q+"_query' class='fm-button ui-state-default ui-corner-all fm-button-icon-left'><span class='ui-icon ui-icon-comment'></span>Query</a>";if(d.columns.length)n=d.columns;else a.each(n,function(f,j){if(!j.label)j.label=l.p.colNames[f];if(!o){var G=typeof j.search==="undefined"?true:j.search,L=j.hidden===true;if(j.searchoptions&&j.searchoptions.searchhidden===true&&G||G&&!L){o=true;u=j.index||j.name}}});if(!D&&u||d.multipleSearch===false)D={groupOp:"AND",rules:[{field:u,op:"eq",data:""}]};o=false;if(d.tmplNames&&
d.tmplNames.length){o=true;e=d.tmplLabel;e+="<select class='ui-template'>";e+="<option value='default'>Default</option>";a.each(d.tmplNames,function(f,j){e+="<option value='"+f+"'>"+j+"</option>"});e+="</select>"}s="<table class='EditTable' style='border:0px none;margin-top:5px' id='"+q+"_2'><tbody><tr><td colspan='2'><hr class='ui-widget-content' style='margin:1px'/></td></tr><tr><td class='EditButton' style='text-align:left'>"+t+e+"</td><td class='EditButton'>"+p+s+"</td></tr></tbody></table>";
a("#"+q).jqFilter({columns:n,filter:d.loadDefaults?D:null,showQuery:d.showQuery,errorcheck:d.errorcheck,sopt:d.sopt,groupButton:d.multipleGroup,_gridsopt:a.jgrid.search.odata,onChange:function(){this.p.showQuery&&a(".query",this).html(this.toUserFriendlyString())}});w.append(s);o&&d.tmplFilters&&d.tmplFilters.length&&a(".ui-template",w).bind("change",function(){var f=a(this).val();f=="default"?a("#"+q).jqFilter("addFilter",D):a("#"+q).jqFilter("addFilter",d.tmplFilters[parseInt(f,10)]);return false});
if(d.multipleSearch===false){a(".add-rule","#"+q).hide();a(".delete-rule","#"+q).hide()}if(a.isFunction(d.onInitializeSearch))d.onInitializeSearch(a("#"+q));d.layer?a.jgrid.createModal(z,w,d,"#gview_"+l.p.id,a("#gbox_"+l.p.id)[0],"#"+d.layer,{position:"relative"}):a.jgrid.createModal(z,w,d,"#gview_"+l.p.id,a("#gbox_"+l.p.id)[0]);p&&a("#"+q+"_query").bind("click",function(){a(".queryresult",w).toggle();return false});if(d.stringResult===undefined)d.stringResult=d.multipleSearch;a("#"+q+"_search").bind("click",
function(){var f=a("#"+q),j={},G,L=f.jqFilter("filterData");if(d.errorcheck){f[0].hideError();d.showQuery||f.jqFilter("toSQLString");if(f[0].p.error){f[0].showError();return false}}if(d.stringResult){try{G=xmlJsonClass.toJson(L,"","",false)}catch(I){try{G=JSON.stringify(L)}catch(T){}}if(typeof G==="string"){j[d.sFilter]=G;a.each([d.sField,d.sValue,d.sOper],function(){j[this]=""})}}else if(d.multipleSearch){j[d.sFilter]=L;a.each([d.sField,d.sValue,d.sOper],function(){j[this]=""})}else{j[d.sField]=
L.rules[0].field;j[d.sValue]=L.rules[0].data;j[d.sOper]=L.rules[0].op;j[d.sFilter]=""}l.p.search=true;a.extend(l.p.postData,j);if(a.isFunction(d.onSearch))d.onSearch();a(l).trigger("reloadGrid",[{page:1}]);d.closeAfterSearch&&a.jgrid.hideModal("#"+z.themodal,{gb:"#gbox_"+l.p.id,jqm:d.jqModal,onClose:d.onClose});return false});a("#"+q+"_reset").bind("click",function(){var f={},j=a("#"+q);l.p.search=false;if(d.multipleSearch===false)f[d.sField]=f[d.sValue]=f[d.sOper]="";else f[d.sFilter]="";j[0].resetFilter();
o&&a(".ui-template",w).val("default");a.extend(l.p.postData,f);if(a.isFunction(d.onReset))d.onReset();a(l).trigger("reloadGrid",[{page:1}]);return false});b();a(".fm-button:not(.ui-state-disabled)",w).hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")})}}})},editGridRow:function(d,b){c=b=a.extend({top:0,left:0,width:300,height:"auto",dataheight:"auto",modal:false,overlay:10,drag:true,resize:true,url:null,mtype:"POST",clearAfterAdd:true,closeAfterEdit:false,
reloadAfterSubmit:true,onInitializeForm:null,beforeInitData:null,beforeShowForm:null,afterShowForm:null,beforeSubmit:null,afterSubmit:null,onclickSubmit:null,afterComplete:null,onclickPgButtons:null,afterclickPgButtons:null,editData:{},recreateForm:false,jqModal:true,closeOnEscape:false,addedrow:"first",topinfo:"",bottominfo:"",saveicon:[],closeicon:[],savekey:[false,13],navkeys:[false,38,40],checkOnSubmit:false,checkOnUpdate:false,_savedData:{},processing:false,onClose:null,ajaxEditOptions:{},serializeEditData:null,
viewPagerButtons:true},a.jgrid.edit,b||{});return this.each(function(){function l(){a(".FormElement","#"+f).each(function(){var g=a(".customelement",this);if(g.length){var k=a(g[0]).attr("name");a.each(e.p.colModel,function(){if(this.name===k&&this.editoptions&&a.isFunction(this.editoptions.custom_value)){try{h[k]=this.editoptions.custom_value(a("#"+a.jgrid.jqID(k),"#"+f),"get");if(h[k]===undefined)throw"e1";}catch(r){r==="e1"?a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,"function 'custom_value' "+
a.jgrid.edit.msg.novalue,jQuery.jgrid.edit.bClose):a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,r.message,jQuery.jgrid.edit.bClose)}return true}})}else{switch(a(this).get(0).type){case "checkbox":if(a(this).attr("checked"))h[this.name]=a(this).val();else{g=a(this).attr("offval");h[this.name]=g}break;case "select-one":h[this.name]=a("option:selected",this).val();P[this.name]=a("option:selected",this).text();break;case "select-multiple":h[this.name]=a(this).val();h[this.name]=h[this.name]?h[this.name].join(","):
"";var v=[];a("option:selected",this).each(function(r,E){v[r]=a(E).text()});P[this.name]=v.join(",");break;case "password":case "text":case "textarea":case "button":h[this.name]=a(this).val()}if(e.p.autoencode)h[this.name]=a.jgrid.htmlEncode(h[this.name])}});return true}function q(g,k,v,r){var E,A,y,K=0,B,N,F,S=[],M=false,aa="",R;for(R=1;R<=r;R++)aa+="<td class='CaptionTD'>&#160;</td><td class='DataTD'>&#160;</td>";if(g!="_empty")M=a(k).jqGrid("getInd",g);a(k.p.colModel).each(function(U){E=this.name;
N=(A=this.editrules&&this.editrules.edithidden===true?false:this.hidden===true?true:false)?"style='display:none'":"";if(E!=="cb"&&E!=="subgrid"&&this.editable===true&&E!=="rn"){if(M===false)B="";else if(E==k.p.ExpandColumn&&k.p.treeGrid===true)B=a("td:eq("+U+")",k.rows[M]).text();else try{B=a.unformat(a("td:eq("+U+")",k.rows[M]),{rowId:g,colModel:this},U)}catch(fa){B=a("td:eq("+U+")",k.rows[M]).text()}var Y=a.extend({},this.editoptions||{},{id:E,name:E}),Z=a.extend({},{elmprefix:"",elmsuffix:"",rowabove:false,
rowcontent:""},this.formoptions||{}),ea=parseInt(Z.rowpos,10)||K+1,ga=parseInt((parseInt(Z.colpos,10)||1)*2,10);if(g=="_empty"&&Y.defaultValue)B=a.isFunction(Y.defaultValue)?Y.defaultValue():Y.defaultValue;if(!this.edittype)this.edittype="text";if(e.p.autoencode)B=a.jgrid.htmlDecode(B);F=a.jgrid.createEl(this.edittype,Y,B,false,a.extend({},a.jgrid.ajaxOptions,k.p.ajaxSelectOptions||{}));if(B===""&&this.edittype=="checkbox")B=a(F).attr("offval");if(B===""&&this.edittype=="select")B=a("option:eq(0)",
F).text();if(c.checkOnSubmit||c.checkOnUpdate)c._savedData[E]=B;a(F).addClass("FormElement");if(this.edittype=="text"||this.edittype=="textarea")a(F).addClass("ui-widget-content ui-corner-all");y=a(v).find("tr[rowpos="+ea+"]");if(Z.rowabove){Y=a("<tr><td class='contentinfo' colspan='"+r*2+"'>"+Z.rowcontent+"</td></tr>");a(v).append(Y);Y[0].rp=ea}if(y.length===0){y=a("<tr "+N+" rowpos='"+ea+"'></tr>").addClass("FormData").attr("id","tr_"+E);a(y).append(aa);a(v).append(y);y[0].rp=ea}a("td:eq("+(ga-
2)+")",y[0]).html(typeof Z.label==="undefined"?k.p.colNames[U]:Z.label);a("td:eq("+(ga-1)+")",y[0]).append(Z.elmprefix).append(F).append(Z.elmsuffix);S[K]=U;K++}});if(K>0){R=a("<tr class='FormData' style='display:none'><td class='CaptionTD'></td><td colspan='"+(r*2-1)+"' class='DataTD'><input class='FormElement' id='id_g' type='text' name='"+k.p.id+"_id' value='"+g+"'/></td></tr>");R[0].rp=K+999;a(v).append(R);if(c.checkOnSubmit||c.checkOnUpdate)c._savedData[k.p.id+"_id"]=g}return S}function C(g,
k,v){var r,E=0,A,y,K,B,N;if(c.checkOnSubmit||c.checkOnUpdate){c._savedData={};c._savedData[k.p.id+"_id"]=g}var F=k.p.colModel;if(g=="_empty"){a(F).each(function(){r=this.name;K=a.extend({},this.editoptions||{});if((y=a("#"+a.jgrid.jqID(r),"#"+v))&&y[0]!==null){B="";if(K.defaultValue){B=a.isFunction(K.defaultValue)?K.defaultValue():K.defaultValue;if(y[0].type=="checkbox"){N=B.toLowerCase();if(N.search(/(false|0|no|off|undefined)/i)<0&&N!==""){y[0].checked=true;y[0].defaultChecked=true;y[0].value=B}else y.attr({checked:"",
defaultChecked:""})}else y.val(B)}else if(y[0].type=="checkbox"){y[0].checked=false;y[0].defaultChecked=false;B=a(y).attr("offval")}else if(y[0].type&&y[0].type.substr(0,6)=="select")y[0].selectedIndex=0;else y.val(B);if(c.checkOnSubmit===true||c.checkOnUpdate)c._savedData[r]=B}});a("#id_g","#"+v).val(g)}else{var S=a(k).jqGrid("getInd",g,true);if(S){a("td",S).each(function(M){r=F[M].name;if(r!=="cb"&&r!=="subgrid"&&r!=="rn"&&F[M].editable===true){if(r==k.p.ExpandColumn&&k.p.treeGrid===true)A=a(this).text();
else try{A=a.unformat(a(this),{rowId:g,colModel:F[M]},M)}catch(aa){A=a(this).text()}if(e.p.autoencode)A=a.jgrid.htmlDecode(A);if(c.checkOnSubmit===true||c.checkOnUpdate)c._savedData[r]=A;r=a.jgrid.jqID(r);switch(F[M].edittype){case "password":case "text":case "button":case "image":a("#"+r,"#"+v).val(A);break;case "textarea":if(A=="&nbsp;"||A=="&#160;"||A.length==1&&A.charCodeAt(0)==160)A="";a("#"+r,"#"+v).val(A);break;case "select":var R=A.split(",");R=a.map(R,function(fa){return a.trim(fa)});a("#"+
r+" option","#"+v).each(function(){this.selected=!F[M].editoptions.multiple&&(R[0]==a.trim(a(this).text())||R[0]==a.trim(a(this).val()))?true:F[M].editoptions.multiple?a.inArray(a.trim(a(this).text()),R)>-1||a.inArray(a.trim(a(this).val()),R)>-1?true:false:false});break;case "checkbox":A+="";if(F[M].editoptions&&F[M].editoptions.value)if(F[M].editoptions.value.split(":")[0]==A){a("#"+r,"#"+v).attr("checked",true);a("#"+r,"#"+v).attr("defaultChecked",true)}else{a("#"+r,"#"+v).attr("checked",false);
a("#"+r,"#"+v).attr("defaultChecked","")}else{A=A.toLowerCase();if(A.search(/(false|0|no|off|undefined)/i)<0&&A!==""){a("#"+r,"#"+v).attr("checked",true);a("#"+r,"#"+v).attr("defaultChecked",true)}else{a("#"+r,"#"+v).attr("checked",false);a("#"+r,"#"+v).attr("defaultChecked","")}}break;case "custom":try{if(F[M].editoptions&&a.isFunction(F[M].editoptions.custom_value))F[M].editoptions.custom_value(a("#"+r,"#"+v),"set",A);else throw"e1";}catch(U){U=="e1"?a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,
"function 'custom_value' "+a.jgrid.edit.msg.nodefined,jQuery.jgrid.edit.bClose):a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,U.message,jQuery.jgrid.edit.bClose)}}E++}});E>0&&a("#id_g","#"+f).val(g)}}}function z(){var g,k=[true,"",""],v={},r=e.p.prmNames,E,A,y,K;if(a.isFunction(c.beforeCheckValues)){var B=c.beforeCheckValues(h,a("#"+o),h[e.p.id+"_id"]=="_empty"?r.addoper:r.editoper);if(B&&typeof B==="object")h=B}for(y in h)if(h.hasOwnProperty(y)){k=a.jgrid.checkValues(h[y],y,e);if(k[0]===false)break}if(k[0]){if(a.isFunction(c.onclickSubmit))v=
c.onclickSubmit(c,h)||{};if(a.isFunction(c.beforeSubmit))k=c.beforeSubmit(h,a("#"+o))}if(k[0]&&!c.processing){c.processing=true;a("#sData","#"+f+"_2").addClass("ui-state-active");A=r.oper;E=r.id;h[A]=a.trim(h[e.p.id+"_id"])=="_empty"?r.addoper:r.editoper;if(h[A]!=r.addoper)h[E]=h[e.p.id+"_id"];else if(h[E]===undefined)h[E]=h[e.p.id+"_id"];delete h[e.p.id+"_id"];h=a.extend(h,c.editData,v);if(e.p.treeGrid===true){if(h[A]==r.addoper){K=a(e).jqGrid("getGridParam","selrow");h[e.p.treeGridModel=="adjacency"?
e.p.treeReader.parent_id_field:"parent_id"]=K}for(i in e.p.treeReader){v=e.p.treeReader[i];if(h.hasOwnProperty(v))h[A]==r.addoper&&i==="parent_id_field"||delete h[v]}}v=a.extend({url:c.url?c.url:a(e).jqGrid("getGridParam","editurl"),type:c.mtype,data:a.isFunction(c.serializeEditData)?c.serializeEditData(h):h,complete:function(N,F){if(F!="success"){k[0]=false;k[1]=a.isFunction(c.errorTextFormat)?c.errorTextFormat(N):F+" Status: '"+N.statusText+"'. Error code: "+N.status}else if(a.isFunction(c.afterSubmit))k=
c.afterSubmit(N,h);if(k[0]===false){a("#FormError>td","#"+f).html(k[1]);a("#FormError","#"+f).show()}else{a.each(e.p.colModel,function(){if(P[this.name]&&this.formatter&&this.formatter=="select")try{delete P[this.name]}catch(aa){}});h=a.extend(h,P);e.p.autoencode&&a.each(h,function(aa,R){h[aa]=a.jgrid.htmlDecode(R)});c.reloadAfterSubmit=c.reloadAfterSubmit&&e.p.datatype!="local";if(h[A]==r.addoper){k[2]||(k[2]=a.jgrid.randId());h[E]=k[2];if(c.closeAfterAdd){if(c.reloadAfterSubmit)a(e).trigger("reloadGrid");
else if(e.p.treeGrid===true)a(e).jqGrid("addChildNode",k[2],K,h);else{a(e).jqGrid("addRowData",k[2],h,b.addedrow);a(e).jqGrid("setSelection",k[2])}a.jgrid.hideModal("#"+j.themodal,{gb:"#gbox_"+u,jqm:b.jqModal,onClose:c.onClose})}else if(c.clearAfterAdd){if(c.reloadAfterSubmit)a(e).trigger("reloadGrid");else e.p.treeGrid===true?a(e).jqGrid("addChildNode",k[2],K,h):a(e).jqGrid("addRowData",k[2],h,b.addedrow);C("_empty",e,o)}else if(c.reloadAfterSubmit)a(e).trigger("reloadGrid");else e.p.treeGrid===
true?a(e).jqGrid("addChildNode",k[2],K,h):a(e).jqGrid("addRowData",k[2],h,b.addedrow)}else{if(c.reloadAfterSubmit){a(e).trigger("reloadGrid");c.closeAfterEdit||setTimeout(function(){a(e).jqGrid("setSelection",h[E])},1E3)}else e.p.treeGrid===true?a(e).jqGrid("setTreeRow",h[E],h):a(e).jqGrid("setRowData",h[E],h);c.closeAfterEdit&&a.jgrid.hideModal("#"+j.themodal,{gb:"#gbox_"+u,jqm:b.jqModal,onClose:c.onClose})}if(a.isFunction(c.afterComplete)){g=N;setTimeout(function(){c.afterComplete(g,h,a("#"+o));
g=null},500)}if(c.checkOnSubmit||c.checkOnUpdate){a("#"+o).data("disabled",false);if(c._savedData[e.p.id+"_id"]!="_empty")for(var S in c._savedData)if(h[S])c._savedData[S]=h[S]}}c.processing=false;a("#sData","#"+f+"_2").removeClass("ui-state-active");try{a(":input:visible","#"+o)[0].focus()}catch(M){}}},a.jgrid.ajaxOptions,c.ajaxEditOptions);if(!v.url&&!c.useDataProxy)if(a.isFunction(e.p.dataProxy))c.useDataProxy=true;else{k[0]=false;k[1]+=" "+a.jgrid.errors.nourl}if(k[0])c.useDataProxy?e.p.dataProxy.call(e,
v,"set_"+e.p.id):a.ajax(v)}if(k[0]===false){a("#FormError>td","#"+f).html(k[1]);a("#FormError","#"+f).show()}}function D(g,k){var v=false,r;for(r in g)if(g[r]!=k[r]){v=true;break}return v}function w(){a.each(e.p.colModel,function(g,k){if(k.editoptions&&k.editoptions.NullIfEmpty===true)if(h.hasOwnProperty(k.name)&&h[k.name]=="")h[k.name]="null"})}function n(){var g=true;a("#FormError","#"+f).hide();if(c.checkOnUpdate){h={};P={};l();O=a.extend({},h,P);if(V=D(O,c._savedData)){a("#"+o).data("disabled",
true);a(".confirm","#"+j.themodal).show();g=false}else w()}return g}function s(){if(d!=="_empty"&&typeof e.p.savedRow!=="undefined"&&e.p.savedRow.length>0&&a.isFunction(a.fn.jqGrid.restoreRow))for(var g=0;g<e.p.savedRow.length;g++)if(e.p.savedRow[g].id==d){a(e).jqGrid("restoreRow",d);break}}function t(g,k){g===0?a("#pData","#"+f+"_2").addClass("ui-state-disabled"):a("#pData","#"+f+"_2").removeClass("ui-state-disabled");g==k?a("#nData","#"+f+"_2").addClass("ui-state-disabled"):a("#nData","#"+f+"_2").removeClass("ui-state-disabled")}
function p(){var g=a(e).jqGrid("getDataIDs"),k=a("#id_g","#"+f).val();return[a.inArray(k,g),g]}var e=this;if(e.grid&&d){var u=e.p.id,o="FrmGrid_"+u,f="TblGrid_"+u,j={themodal:"editmod"+u,modalhead:"edithd"+u,modalcontent:"editcnt"+u,scrollelm:o},G=a.isFunction(c.beforeShowForm)?c.beforeShowForm:false,L=a.isFunction(c.afterShowForm)?c.afterShowForm:false,I=a.isFunction(c.beforeInitData)?c.beforeInitData:false,T=a.isFunction(c.onInitializeForm)?c.onInitializeForm:false,H=true,m=1,x=0,h,P,O,V;if(d===
"new"){d="_empty";b.caption=c.addCaption}else b.caption=c.editCaption;b.recreateForm===true&&a("#"+j.themodal).html()!==null&&a("#"+j.themodal).remove();var Q=true;if(b.checkOnUpdate&&b.jqModal&&!b.modal)Q=false;if(a("#"+j.themodal).html()!==null){if(I){H=I(a("#"+o));if(typeof H=="undefined")H=true}if(H===false)return;s();a(".ui-jqdialog-title","#"+j.modalhead).html(b.caption);a("#FormError","#"+f).hide();if(c.topinfo){a(".topinfo","#"+f+"_2").html(c.topinfo);a(".tinfo","#"+f+"_2").show()}else a(".tinfo",
"#"+f+"_2").hide();if(c.bottominfo){a(".bottominfo","#"+f+"_2").html(c.bottominfo);a(".binfo","#"+f+"_2").show()}else a(".binfo","#"+f+"_2").hide();C(d,e,o);d=="_empty"||!c.viewPagerButtons?a("#pData, #nData","#"+f+"_2").hide():a("#pData, #nData","#"+f+"_2").show();if(c.processing===true){c.processing=false;a("#sData","#"+f+"_2").removeClass("ui-state-active")}if(a("#"+o).data("disabled")===true){a(".confirm","#"+j.themodal).hide();a("#"+o).data("disabled",false)}G&&G(a("#"+o));a("#"+j.themodal).data("onClose",
c.onClose);a.jgrid.viewModal("#"+j.themodal,{gbox:"#gbox_"+u,jqm:b.jqModal,jqM:false,overlay:b.overlay,modal:b.modal});Q||a(".jqmOverlay").click(function(){if(!n())return false;a.jgrid.hideModal("#"+j.themodal,{gb:"#gbox_"+u,jqm:b.jqModal,onClose:c.onClose});return false});L&&L(a("#"+o))}else{var J=isNaN(b.dataheight)?b.dataheight:b.dataheight+"px";J=a("<form name='FormPost' id='"+o+"' class='FormGrid' onSubmit='return false;' style='width:100%;overflow:auto;position:relative;height:"+J+";'></form>").data("disabled",
false);var W=a("<table id='"+f+"' class='EditTable' cellspacing='0' cellpadding='0' border='0'><tbody></tbody></table>");if(I){H=I(a("#"+o));if(typeof H=="undefined")H=true}if(H===false)return;s();a(e.p.colModel).each(function(){var g=this.formoptions;m=Math.max(m,g?g.colpos||0:0);x=Math.max(x,g?g.rowpos||0:0)});a(J).append(W);I=a("<tr id='FormError' style='display:none'><td class='ui-state-error' colspan='"+m*2+"'></td></tr>");I[0].rp=0;a(W).append(I);I=a("<tr style='display:none' class='tinfo'><td class='topinfo' colspan='"+
m*2+"'>"+c.topinfo+"</td></tr>");I[0].rp=0;a(W).append(I);H=(I=e.p.direction=="rtl"?true:false)?"nData":"pData";var X=I?"pData":"nData";q(d,e,W,m);H="<a href='javascript:void(0)' id='"+H+"' class='fm-button ui-state-default ui-corner-left'><span class='ui-icon ui-icon-triangle-1-w'></span></div>";X="<a href='javascript:void(0)' id='"+X+"' class='fm-button ui-state-default ui-corner-right'><span class='ui-icon ui-icon-triangle-1-e'></span></div>";var ba="<a href='javascript:void(0)' id='sData' class='fm-button ui-state-default ui-corner-all'>"+
b.bSubmit+"</a>",$="<a href='javascript:void(0)' id='cData' class='fm-button ui-state-default ui-corner-all'>"+b.bCancel+"</a>";H="<table border='0' cellspacing='0' cellpadding='0' class='EditTable' id='"+f+"_2'><tbody><tr><td colspan='2'><hr class='ui-widget-content' style='margin:1px'/></td></tr><tr id='Act_Buttons'><td class='navButton'>"+(I?X+H:H+X)+"</td><td class='EditButton'>"+ba+$+"</td></tr>";H+="<tr style='display:none' class='binfo'><td class='bottominfo' colspan='2'>"+c.bottominfo+"</td></tr>";
H+="</tbody></table>";if(x>0){var ca=[];a.each(a(W)[0].rows,function(g,k){ca[g]=k});ca.sort(function(g,k){if(g.rp>k.rp)return 1;if(g.rp<k.rp)return-1;return 0});a.each(ca,function(g,k){a("tbody",W).append(k)})}b.gbox="#gbox_"+u;var da=false;if(b.closeOnEscape===true){b.closeOnEscape=false;da=true}J=a("<span></span>").append(J).append(H);a.jgrid.createModal(j,J,b,"#gview_"+e.p.id,a("#gbox_"+e.p.id)[0]);if(I){a("#pData, #nData","#"+f+"_2").css("float","right");a(".EditButton","#"+f+"_2").css("text-align",
"left")}c.topinfo&&a(".tinfo","#"+f+"_2").show();c.bottominfo&&a(".binfo","#"+f+"_2").show();H=J=null;a("#"+j.themodal).keydown(function(g){var k=g.target;if(a("#"+o).data("disabled")===true)return false;if(c.savekey[0]===true&&g.which==c.savekey[1])if(k.tagName!="TEXTAREA"){a("#sData","#"+f+"_2").trigger("click");return false}if(g.which===27){if(!n())return false;da&&a.jgrid.hideModal(this,{gb:b.gbox,jqm:b.jqModal,onClose:c.onClose});return false}if(c.navkeys[0]===true){if(a("#id_g","#"+f).val()==
"_empty")return true;if(g.which==c.navkeys[1]){a("#pData","#"+f+"_2").trigger("click");return false}if(g.which==c.navkeys[2]){a("#nData","#"+f+"_2").trigger("click");return false}}});if(b.checkOnUpdate){a("a.ui-jqdialog-titlebar-close span","#"+j.themodal).removeClass("jqmClose");a("a.ui-jqdialog-titlebar-close","#"+j.themodal).unbind("click").click(function(){if(!n())return false;a.jgrid.hideModal("#"+j.themodal,{gb:"#gbox_"+u,jqm:b.jqModal,onClose:c.onClose});return false})}b.saveicon=a.extend([true,
"left","ui-icon-disk"],b.saveicon);b.closeicon=a.extend([true,"left","ui-icon-close"],b.closeicon);if(b.saveicon[0]===true)a("#sData","#"+f+"_2").addClass(b.saveicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("<span class='ui-icon "+b.saveicon[2]+"'></span>");if(b.closeicon[0]===true)a("#cData","#"+f+"_2").addClass(b.closeicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("<span class='ui-icon "+b.closeicon[2]+"'></span>");if(c.checkOnSubmit||c.checkOnUpdate){ba=
"<a href='javascript:void(0)' id='sNew' class='fm-button ui-state-default ui-corner-all' style='z-index:1002'>"+b.bYes+"</a>";X="<a href='javascript:void(0)' id='nNew' class='fm-button ui-state-default ui-corner-all' style='z-index:1002'>"+b.bNo+"</a>";$="<a href='javascript:void(0)' id='cNew' class='fm-button ui-state-default ui-corner-all' style='z-index:1002'>"+b.bExit+"</a>";J=b.zIndex||999;J++;a("<div class='ui-widget-overlay jqgrid-overlay confirm' style='z-index:"+J+";display:none;'>&#160;"+
(a.browser.msie&&a.browser.version==6?'<iframe style="display:block;position:absolute;z-index:-1;filter:Alpha(Opacity=\'0\');" src="javascript:false;"></iframe>':"")+"</div><div class='confirm ui-widget-content ui-jqconfirm' style='z-index:"+(J+1)+"'>"+b.saveData+"<br/><br/>"+ba+X+$+"</div>").insertAfter("#"+o);a("#sNew","#"+j.themodal).click(function(){z();a("#"+o).data("disabled",false);a(".confirm","#"+j.themodal).hide();return false});a("#nNew","#"+j.themodal).click(function(){a(".confirm","#"+
j.themodal).hide();a("#"+o).data("disabled",false);setTimeout(function(){a(":input","#"+o)[0].focus()},0);return false});a("#cNew","#"+j.themodal).click(function(){a(".confirm","#"+j.themodal).hide();a("#"+o).data("disabled",false);a.jgrid.hideModal("#"+j.themodal,{gb:"#gbox_"+u,jqm:b.jqModal,onClose:c.onClose});return false})}T&&T(a("#"+o));d=="_empty"||!c.viewPagerButtons?a("#pData,#nData","#"+f+"_2").hide():a("#pData,#nData","#"+f+"_2").show();G&&G(a("#"+o));a("#"+j.themodal).data("onClose",c.onClose);
a.jgrid.viewModal("#"+j.themodal,{gbox:"#gbox_"+u,jqm:b.jqModal,overlay:b.overlay,modal:b.modal});Q||a(".jqmOverlay").click(function(){if(!n())return false;a.jgrid.hideModal("#"+j.themodal,{gb:"#gbox_"+u,jqm:b.jqModal,onClose:c.onClose});return false});L&&L(a("#"+o));a(".fm-button","#"+j.themodal).hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});a("#sData","#"+f+"_2").click(function(){h={};P={};a("#FormError","#"+f).hide();l();w();if(h[e.p.id+
"_id"]=="_empty")z();else if(b.checkOnSubmit===true){O=a.extend({},h,P);if(V=D(O,c._savedData)){a("#"+o).data("disabled",true);a(".confirm","#"+j.themodal).show()}else z()}else z();return false});a("#cData","#"+f+"_2").click(function(){if(!n())return false;a.jgrid.hideModal("#"+j.themodal,{gb:"#gbox_"+u,jqm:b.jqModal,onClose:c.onClose});return false});a("#nData","#"+f+"_2").click(function(){if(!n())return false;a("#FormError","#"+f).hide();var g=p();g[0]=parseInt(g[0],10);if(g[0]!=-1&&g[1][g[0]+1]){if(a.isFunction(b.onclickPgButtons))b.onclickPgButtons("next",
a("#"+o),g[1][g[0]]);C(g[1][g[0]+1],e,o);a(e).jqGrid("setSelection",g[1][g[0]+1]);a.isFunction(b.afterclickPgButtons)&&b.afterclickPgButtons("next",a("#"+o),g[1][g[0]+1]);t(g[0]+1,g[1].length-1)}return false});a("#pData","#"+f+"_2").click(function(){if(!n())return false;a("#FormError","#"+f).hide();var g=p();if(g[0]!=-1&&g[1][g[0]-1]){if(a.isFunction(b.onclickPgButtons))b.onclickPgButtons("prev",a("#"+o),g[1][g[0]]);C(g[1][g[0]-1],e,o);a(e).jqGrid("setSelection",g[1][g[0]-1]);a.isFunction(b.afterclickPgButtons)&&
b.afterclickPgButtons("prev",a("#"+o),g[1][g[0]-1]);t(g[0]-1,g[1].length-1)}return false})}G=p();t(G[0],G[1].length-1)}})},viewGridRow:function(d,b){b=a.extend({top:0,left:0,width:0,height:"auto",dataheight:"auto",modal:false,overlay:10,drag:true,resize:true,jqModal:true,closeOnEscape:false,labelswidth:"30%",closeicon:[],navkeys:[false,38,40],onClose:null,beforeShowForm:null,beforeInitData:null,viewPagerButtons:true},a.jgrid.view,b||{});return this.each(function(){function l(){if(b.closeOnEscape===
true||b.navkeys[0]===true)setTimeout(function(){a(".ui-jqdialog-titlebar-close","#"+p.modalhead).focus()},0)}function q(m,x,h,P){for(var O,V,Q,J=0,W,X,ba=[],$=false,ca="<td class='CaptionTD form-view-label ui-widget-content' width='"+b.labelswidth+"'>&#160;</td><td class='DataTD form-view-data ui-helper-reset ui-widget-content'>&#160;</td>",da="",g=["integer","number","currency"],k=0,v=0,r,E,A,y=1;y<=P;y++)da+=y==1?ca:"<td class='CaptionTD form-view-label ui-widget-content'>&#160;</td><td class='DataTD form-view-data ui-widget-content'>&#160;</td>";
a(x.p.colModel).each(function(){V=this.editrules&&this.editrules.edithidden===true?false:this.hidden===true?true:false;if(!V&&this.align==="right")if(this.formatter&&a.inArray(this.formatter,g)!==-1)k=Math.max(k,parseInt(this.width,10));else v=Math.max(v,parseInt(this.width,10))});r=k!==0?k:v!==0?v:0;$=a(x).jqGrid("getInd",m);a(x.p.colModel).each(function(K){O=this.name;E=false;X=(V=this.editrules&&this.editrules.edithidden===true?false:this.hidden===true?true:false)?"style='display:none'":"";A=typeof this.viewable!=
"boolean"?true:this.viewable;if(O!=="cb"&&O!=="subgrid"&&O!=="rn"&&A){W=$===false?"":O==x.p.ExpandColumn&&x.p.treeGrid===true?a("td:eq("+K+")",x.rows[$]).text():a("td:eq("+K+")",x.rows[$]).html();E=this.align==="right"&&r!==0?true:false;a.extend({},this.editoptions||{},{id:O,name:O});var B=a.extend({},{rowabove:false,rowcontent:""},this.formoptions||{}),N=parseInt(B.rowpos,10)||J+1,F=parseInt((parseInt(B.colpos,10)||1)*2,10);if(B.rowabove){var S=a("<tr><td class='contentinfo' colspan='"+P*2+"'>"+
B.rowcontent+"</td></tr>");a(h).append(S);S[0].rp=N}Q=a(h).find("tr[rowpos="+N+"]");if(Q.length===0){Q=a("<tr "+X+" rowpos='"+N+"'></tr>").addClass("FormData").attr("id","trv_"+O);a(Q).append(da);a(h).append(Q);Q[0].rp=N}a("td:eq("+(F-2)+")",Q[0]).html("<b>"+(typeof B.label==="undefined"?x.p.colNames[K]:B.label)+"</b>");a("td:eq("+(F-1)+")",Q[0]).append("<span>"+W+"</span>").attr("id","v_"+O);E&&a("td:eq("+(F-1)+") span",Q[0]).css({"text-align":"right",width:r+"px"});ba[J]=K;J++}});if(J>0){m=a("<tr class='FormData' style='display:none'><td class='CaptionTD'></td><td colspan='"+
(P*2-1)+"' class='DataTD'><input class='FormElement' id='id_g' type='text' name='id' value='"+m+"'/></td></tr>");m[0].rp=J+99;a(h).append(m)}return ba}function C(m,x){var h,P,O=0,V,Q;if(Q=a(x).jqGrid("getInd",m,true)){a("td",Q).each(function(J){h=x.p.colModel[J].name;P=x.p.colModel[J].editrules&&x.p.colModel[J].editrules.edithidden===true?false:x.p.colModel[J].hidden===true?true:false;if(h!=="cb"&&h!=="subgrid"&&h!=="rn"){V=h==x.p.ExpandColumn&&x.p.treeGrid===true?a(this).text():a(this).html();a.extend({},
x.p.colModel[J].editoptions||{});h=a.jgrid.jqID("v_"+h);a("#"+h+" span","#"+t).html(V);P&&a("#"+h,"#"+t).parents("tr:first").hide();O++}});O>0&&a("#id_g","#"+t).val(m)}}function z(m,x){m===0?a("#pData","#"+t+"_2").addClass("ui-state-disabled"):a("#pData","#"+t+"_2").removeClass("ui-state-disabled");m==x?a("#nData","#"+t+"_2").addClass("ui-state-disabled"):a("#nData","#"+t+"_2").removeClass("ui-state-disabled")}function D(){var m=a(w).jqGrid("getDataIDs"),x=a("#id_g","#"+t).val();return[a.inArray(x,
m),m]}var w=this;if(w.grid&&d){if(!b.imgpath)b.imgpath=w.p.imgpath;var n=w.p.id,s="ViewGrid_"+n,t="ViewTbl_"+n,p={themodal:"viewmod"+n,modalhead:"viewhd"+n,modalcontent:"viewcnt"+n,scrollelm:s},e=a.isFunction(b.beforeInitData)?b.beforeInitData:false,u=true,o=1,f=0;if(a("#"+p.themodal).html()!==null){if(e){u=e(a("#"+s));if(typeof u=="undefined")u=true}if(u===false)return;a(".ui-jqdialog-title","#"+p.modalhead).html(b.caption);a("#FormError","#"+t).hide();C(d,w);a.isFunction(b.beforeShowForm)&&b.beforeShowForm(a("#"+
s));a.jgrid.viewModal("#"+p.themodal,{gbox:"#gbox_"+n,jqm:b.jqModal,jqM:false,overlay:b.overlay,modal:b.modal});l()}else{var j=isNaN(b.dataheight)?b.dataheight:b.dataheight+"px";j=a("<form name='FormPost' id='"+s+"' class='FormGrid' style='width:100%;overflow:auto;position:relative;height:"+j+";'></form>");var G=a("<table id='"+t+"' class='EditTable' cellspacing='1' cellpadding='2' border='0' style='table-layout:fixed'><tbody></tbody></table>");if(e){u=e(a("#"+s));if(typeof u=="undefined")u=true}if(u===
false)return;a(w.p.colModel).each(function(){var m=this.formoptions;o=Math.max(o,m?m.colpos||0:0);f=Math.max(f,m?m.rowpos||0:0)});a(j).append(G);q(d,w,G,o);e=w.p.direction=="rtl"?true:false;u="<a href='javascript:void(0)' id='"+(e?"nData":"pData")+"' class='fm-button ui-state-default ui-corner-left'><span class='ui-icon ui-icon-triangle-1-w'></span></a>";var L="<a href='javascript:void(0)' id='"+(e?"pData":"nData")+"' class='fm-button ui-state-default ui-corner-right'><span class='ui-icon ui-icon-triangle-1-e'></span></a>",
I="<a href='javascript:void(0)' id='cData' class='fm-button ui-state-default ui-corner-all'>"+b.bClose+"</a>";if(f>0){var T=[];a.each(a(G)[0].rows,function(m,x){T[m]=x});T.sort(function(m,x){if(m.rp>x.rp)return 1;if(m.rp<x.rp)return-1;return 0});a.each(T,function(m,x){a("tbody",G).append(x)})}b.gbox="#gbox_"+n;var H=false;if(b.closeOnEscape===true){b.closeOnEscape=false;H=true}j=a("<span></span>").append(j).append("<table border='0' class='EditTable' id='"+t+"_2'><tbody><tr id='Act_Buttons'><td class='navButton' width='"+
b.labelswidth+"'>"+(e?L+u:u+L)+"</td><td class='EditButton'>"+I+"</td></tr></tbody></table>");a.jgrid.createModal(p,j,b,"#gview_"+w.p.id,a("#gview_"+w.p.id)[0]);if(e){a("#pData, #nData","#"+t+"_2").css("float","right");a(".EditButton","#"+t+"_2").css("text-align","left")}b.viewPagerButtons||a("#pData, #nData","#"+t+"_2").hide();j=null;a("#"+p.themodal).keydown(function(m){if(m.which===27){H&&a.jgrid.hideModal(this,{gb:b.gbox,jqm:b.jqModal,onClose:b.onClose});return false}if(b.navkeys[0]===true){if(m.which===
b.navkeys[1]){a("#pData","#"+t+"_2").trigger("click");return false}if(m.which===b.navkeys[2]){a("#nData","#"+t+"_2").trigger("click");return false}}});b.closeicon=a.extend([true,"left","ui-icon-close"],b.closeicon);if(b.closeicon[0]===true)a("#cData","#"+t+"_2").addClass(b.closeicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("<span class='ui-icon "+b.closeicon[2]+"'></span>");a.isFunction(b.beforeShowForm)&&b.beforeShowForm(a("#"+s));a.jgrid.viewModal("#"+p.themodal,{gbox:"#gbox_"+
n,jqm:b.jqModal,modal:b.modal});a(".fm-button:not(.ui-state-disabled)","#"+t+"_2").hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});l();a("#cData","#"+t+"_2").click(function(){a.jgrid.hideModal("#"+p.themodal,{gb:"#gbox_"+n,jqm:b.jqModal,onClose:b.onClose});return false});a("#nData","#"+t+"_2").click(function(){a("#FormError","#"+t).hide();var m=D();m[0]=parseInt(m[0],10);if(m[0]!=-1&&m[1][m[0]+1]){if(a.isFunction(b.onclickPgButtons))b.onclickPgButtons("next",
a("#"+s),m[1][m[0]]);C(m[1][m[0]+1],w);a(w).jqGrid("setSelection",m[1][m[0]+1]);a.isFunction(b.afterclickPgButtons)&&b.afterclickPgButtons("next",a("#"+s),m[1][m[0]+1]);z(m[0]+1,m[1].length-1)}l();return false});a("#pData","#"+t+"_2").click(function(){a("#FormError","#"+t).hide();var m=D();if(m[0]!=-1&&m[1][m[0]-1]){if(a.isFunction(b.onclickPgButtons))b.onclickPgButtons("prev",a("#"+s),m[1][m[0]]);C(m[1][m[0]-1],w);a(w).jqGrid("setSelection",m[1][m[0]-1]);a.isFunction(b.afterclickPgButtons)&&b.afterclickPgButtons("prev",
a("#"+s),m[1][m[0]-1]);z(m[0]-1,m[1].length-1)}l();return false})}j=D();z(j[0],j[1].length-1)}})},delGridRow:function(d,b){c=b=a.extend({top:0,left:0,width:240,height:"auto",dataheight:"auto",modal:false,overlay:10,drag:true,resize:true,url:"",mtype:"POST",reloadAfterSubmit:true,beforeShowForm:null,beforeInitData:null,afterShowForm:null,beforeSubmit:null,onclickSubmit:null,afterSubmit:null,jqModal:true,closeOnEscape:false,delData:{},delicon:[],cancelicon:[],onClose:null,ajaxDelOptions:{},processing:false,
serializeDelData:null,useDataProxy:false},a.jgrid.del,b||{});return this.each(function(){var l=this;if(l.grid)if(d){var q=typeof b.beforeShowForm==="function"?true:false,C=typeof b.afterShowForm==="function"?true:false,z=a.isFunction(b.beforeInitData)?b.beforeInitData:false,D=l.p.id,w={},n=true,s="DelTbl_"+D,t,p,e,u,o={themodal:"delmod"+D,modalhead:"delhd"+D,modalcontent:"delcnt"+D,scrollelm:s};if(jQuery.isArray(d))d=d.join();if(a("#"+o.themodal).html()!==null){if(z){n=z(a("#"+s));if(typeof n=="undefined")n=
true}if(n===false)return;a("#DelData>td","#"+s).text(d);a("#DelError","#"+s).hide();if(c.processing===true){c.processing=false;a("#dData","#"+s).removeClass("ui-state-active")}q&&b.beforeShowForm(a("#"+s));a.jgrid.viewModal("#"+o.themodal,{gbox:"#gbox_"+D,jqm:b.jqModal,jqM:false,overlay:b.overlay,modal:b.modal})}else{var f=isNaN(b.dataheight)?b.dataheight:b.dataheight+"px";f="<div id='"+s+"' class='formdata' style='width:100%;overflow:auto;position:relative;height:"+f+";'>";f+="<table class='DelTable'><tbody>";
f+="<tr id='DelError' style='display:none'><td class='ui-state-error'></td></tr>";f+="<tr id='DelData' style='display:none'><td >"+d+"</td></tr>";f+='<tr><td class="delmsg" style="white-space:pre;">'+b.msg+"</td></tr><tr><td >&#160;</td></tr>";f+="</tbody></table></div>";f+="<table cellspacing='0' cellpadding='0' border='0' class='EditTable' id='"+s+"_2'><tbody><tr><td><hr class='ui-widget-content' style='margin:1px'/></td></tr></tr><tr><td class='DelButton EditButton'>"+("<a href='javascript:void(0)' id='dData' class='fm-button ui-state-default ui-corner-all'>"+
b.bSubmit+"</a>")+"&#160;"+("<a href='javascript:void(0)' id='eData' class='fm-button ui-state-default ui-corner-all'>"+b.bCancel+"</a>")+"</td></tr></tbody></table>";b.gbox="#gbox_"+D;a.jgrid.createModal(o,f,b,"#gview_"+l.p.id,a("#gview_"+l.p.id)[0]);if(z){n=z(a("#"+s));if(typeof n=="undefined")n=true}if(n===false)return;a(".fm-button","#"+s+"_2").hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});b.delicon=a.extend([true,"left","ui-icon-scissors"],
b.delicon);b.cancelicon=a.extend([true,"left","ui-icon-cancel"],b.cancelicon);if(b.delicon[0]===true)a("#dData","#"+s+"_2").addClass(b.delicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("<span class='ui-icon "+b.delicon[2]+"'></span>");if(b.cancelicon[0]===true)a("#eData","#"+s+"_2").addClass(b.cancelicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("<span class='ui-icon "+b.cancelicon[2]+"'></span>");a("#dData","#"+s+"_2").click(function(){var j=[true,""];
w={};var G=a("#DelData>td","#"+s).text();if(typeof b.onclickSubmit==="function")w=b.onclickSubmit(c,G)||{};if(typeof b.beforeSubmit==="function")j=b.beforeSubmit(G);if(j[0]&&!c.processing){c.processing=true;a(this).addClass("ui-state-active");e=l.p.prmNames;t=a.extend({},c.delData,w);u=e.oper;t[u]=e.deloper;p=e.id;t[p]=G;var L=a.extend({url:c.url?c.url:a(l).jqGrid("getGridParam","editurl"),type:b.mtype,data:a.isFunction(b.serializeDelData)?b.serializeDelData(t):t,complete:function(I,T){if(T!="success"){j[0]=
false;j[1]=a.isFunction(c.errorTextFormat)?c.errorTextFormat(I):T+" Status: '"+I.statusText+"'. Error code: "+I.status}else if(typeof c.afterSubmit==="function")j=c.afterSubmit(I,t);if(j[0]===false){a("#DelError>td","#"+s).html(j[1]);a("#DelError","#"+s).show()}else{if(c.reloadAfterSubmit&&l.p.datatype!="local")a(l).trigger("reloadGrid");else{var H=[];H=G.split(",");if(l.p.treeGrid===true)try{a(l).jqGrid("delTreeNode",H[0])}catch(m){}else for(var x=0;x<H.length;x++)a(l).jqGrid("delRowData",H[x]);
l.p.selrow=null;l.p.selarrrow=[]}a.isFunction(c.afterComplete)&&setTimeout(function(){c.afterComplete(I,G)},500)}c.processing=false;a("#dData","#"+s+"_2").removeClass("ui-state-active");j[0]&&a.jgrid.hideModal("#"+o.themodal,{gb:"#gbox_"+D,jqm:b.jqModal,onClose:c.onClose})}},a.jgrid.ajaxOptions,b.ajaxDelOptions);if(!L.url&&!c.useDataProxy)if(a.isFunction(l.p.dataProxy))c.useDataProxy=true;else{j[0]=false;j[1]+=" "+a.jgrid.errors.nourl}if(j[0])c.useDataProxy?l.p.dataProxy.call(l,L,"del_"+l.p.id):a.ajax(L)}if(j[0]===
false){a("#DelError>td","#"+s).html(j[1]);a("#DelError","#"+s).show()}return false});a("#eData","#"+s+"_2").click(function(){a.jgrid.hideModal("#"+o.themodal,{gb:"#gbox_"+D,jqm:b.jqModal,onClose:c.onClose});return false});q&&b.beforeShowForm(a("#"+s));a.jgrid.viewModal("#"+o.themodal,{gbox:"#gbox_"+D,jqm:b.jqModal,overlay:b.overlay,modal:b.modal})}C&&b.afterShowForm(a("#"+s));b.closeOnEscape===true&&setTimeout(function(){a(".ui-jqdialog-titlebar-close","#"+o.modalhead).focus()},0)}})},navGrid:function(d,
b,l,q,C,z,D){b=a.extend({edit:true,editicon:"ui-icon-pencil",add:true,addicon:"ui-icon-plus",del:true,delicon:"ui-icon-trash",search:true,searchicon:"ui-icon-search",refresh:true,refreshicon:"ui-icon-refresh",refreshstate:"firstpage",view:false,viewicon:"ui-icon-document",position:"left",closeOnEscape:true,beforeRefresh:null,afterRefresh:null,cloneToTop:false},a.jgrid.nav,b||{});return this.each(function(){if(!this.nav){var w={themodal:"alertmod",modalhead:"alerthd",modalcontent:"alertcnt"},n=this,
s,t,p;if(!(!n.grid||typeof d!="string")){if(a("#"+w.themodal).html()===null){if(typeof window.innerWidth!="undefined"){s=window.innerWidth;t=window.innerHeight}else if(typeof document.documentElement!="undefined"&&typeof document.documentElement.clientWidth!="undefined"&&document.documentElement.clientWidth!==0){s=document.documentElement.clientWidth;t=document.documentElement.clientHeight}else{s=1024;t=768}a.jgrid.createModal(w,"<div>"+b.alerttext+"</div><span tabindex='0'><span tabindex='-1' id='jqg_alrt'></span></span>",
{gbox:"#gbox_"+n.p.id,jqModal:true,drag:true,resize:true,caption:b.alertcap,top:t/2-25,left:s/2-100,width:200,height:"auto",closeOnEscape:b.closeOnEscape},"","",true)}s=1;if(b.cloneToTop&&n.p.toppager)s=2;for(t=0;t<s;t++){var e=a("<table cellspacing='0' cellpadding='0' border='0' class='ui-pg-table navtable' style='float:left;table-layout:auto;'><tbody><tr></tr></tbody></table>"),u,o;if(t===0){u=d;o=n.p.id;if(u==n.p.toppager){o+="_top";s=1}}else{u=n.p.toppager;o=n.p.id+"_top"}n.p.direction=="rtl"&&
a(e).attr("dir","rtl").css("float","right");if(b.add){q=q||{};p=a("<td class='ui-pg-button ui-corner-all'></td>");a(p).append("<div class='ui-pg-div'><span class='ui-icon "+b.addicon+"'></span>"+b.addtext+"</div>");a("tr",e).append(p);a(p,e).attr({title:b.addtitle||"",id:q.id||"add_"+o}).click(function(){a(this).hasClass("ui-state-disabled")||(typeof b.addfunc=="function"?b.addfunc():a(n).jqGrid("editGridRow","new",q));return false}).hover(function(){a(this).hasClass("ui-state-disabled")||a(this).addClass("ui-state-hover")},
function(){a(this).removeClass("ui-state-hover")});p=null}if(b.edit){p=a("<td class='ui-pg-button ui-corner-all'></td>");l=l||{};a(p).append("<div class='ui-pg-div'><span class='ui-icon "+b.editicon+"'></span>"+b.edittext+"</div>");a("tr",e).append(p);a(p,e).attr({title:b.edittitle||"",id:l.id||"edit_"+o}).click(function(){if(!a(this).hasClass("ui-state-disabled")){var f=n.p.selrow;if(f)typeof b.editfunc=="function"?b.editfunc(f):a(n).jqGrid("editGridRow",f,l);else{a.jgrid.viewModal("#"+w.themodal,
{gbox:"#gbox_"+n.p.id,jqm:true});a("#jqg_alrt").focus()}}return false}).hover(function(){a(this).hasClass("ui-state-disabled")||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});p=null}if(b.view){p=a("<td class='ui-pg-button ui-corner-all'></td>");D=D||{};a(p).append("<div class='ui-pg-div'><span class='ui-icon "+b.viewicon+"'></span>"+b.viewtext+"</div>");a("tr",e).append(p);a(p,e).attr({title:b.viewtitle||"",id:D.id||"view_"+o}).click(function(){if(!a(this).hasClass("ui-state-disabled")){var f=
n.p.selrow;if(f)a(n).jqGrid("viewGridRow",f,D);else{a.jgrid.viewModal("#"+w.themodal,{gbox:"#gbox_"+n.p.id,jqm:true});a("#jqg_alrt").focus()}}return false}).hover(function(){a(this).hasClass("ui-state-disabled")||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});p=null}if(b.del){p=a("<td class='ui-pg-button ui-corner-all'></td>");C=C||{};a(p).append("<div class='ui-pg-div'><span class='ui-icon "+b.delicon+"'></span>"+b.deltext+"</div>");a("tr",e).append(p);a(p,
e).attr({title:b.deltitle||"",id:C.id||"del_"+o}).click(function(){if(!a(this).hasClass("ui-state-disabled")){var f;if(n.p.multiselect){f=n.p.selarrrow;if(f.length===0)f=null}else f=n.p.selrow;if(f)"function"==typeof b.delfunc?b.delfunc(f):a(n).jqGrid("delGridRow",f,C);else{a.jgrid.viewModal("#"+w.themodal,{gbox:"#gbox_"+n.p.id,jqm:true});a("#jqg_alrt").focus()}}return false}).hover(function(){a(this).hasClass("ui-state-disabled")||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});
p=null}if(b.add||b.edit||b.del||b.view)a("tr",e).append("<td class='ui-pg-button ui-state-disabled' style='width:4px;'><span class='ui-separator'></span></td>");if(b.search){p=a("<td class='ui-pg-button ui-corner-all'></td>");z=z||{};a(p).append("<div class='ui-pg-div'><span class='ui-icon "+b.searchicon+"'></span>"+b.searchtext+"</div>");a("tr",e).append(p);a(p,e).attr({title:b.searchtitle||"",id:z.id||"search_"+o}).click(function(){a(this).hasClass("ui-state-disabled")||a(n).jqGrid("searchGrid",
z);return false}).hover(function(){a(this).hasClass("ui-state-disabled")||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});z.showOnLoad&&z.showOnLoad===true&&a(p,e).click();p=null}if(b.refresh){p=a("<td class='ui-pg-button ui-corner-all'></td>");a(p).append("<div class='ui-pg-div'><span class='ui-icon "+b.refreshicon+"'></span>"+b.refreshtext+"</div>");a("tr",e).append(p);a(p,e).attr({title:b.refreshtitle||"",id:"refresh_"+o}).click(function(){if(!a(this).hasClass("ui-state-disabled")){a.isFunction(b.beforeRefresh)&&
b.beforeRefresh();n.p.search=false;try{var f=n.p.id;n.p.postData.filters="";a("#fbox_"+f).jqFilter("resetFilter");a.isFunction(n.clearToolbar)&&n.clearToolbar(false)}catch(j){}switch(b.refreshstate){case "firstpage":a(n).trigger("reloadGrid",[{page:1}]);break;case "current":a(n).trigger("reloadGrid",[{current:true}])}a.isFunction(b.afterRefresh)&&b.afterRefresh()}return false}).hover(function(){a(this).hasClass("ui-state-disabled")||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});
p=null}p=a(".ui-jqgrid").css("font-size")||"11px";a("body").append("<div id='testpg2' class='ui-jqgrid ui-widget ui-widget-content' style='font-size:"+p+";visibility:hidden;' ></div>");p=a(e).clone().appendTo("#testpg2").width();a("#testpg2").remove();a(u+"_"+b.position,u).append(e);if(n.p._nvtd){if(p>n.p._nvtd[0]){a(u+"_"+b.position,u).width(p);n.p._nvtd[0]=p}n.p._nvtd[1]=p}e=p=p=null;this.nav=true}}}})},navButtonAdd:function(d,b){b=a.extend({caption:"newButton",title:"",buttonicon:"ui-icon-newwin",
onClickButton:null,position:"last",cursor:"pointer"},b||{});return this.each(function(){if(this.grid){if(d.indexOf("#")!==0)d="#"+d;var l=a(".navtable",d)[0],q=this;if(l)if(!(b.id&&a("#"+b.id,l).html()!==null)){var C=a("<td></td>");b.buttonicon.toString().toUpperCase()=="NONE"?a(C).addClass("ui-pg-button ui-corner-all").append("<div class='ui-pg-div'>"+b.caption+"</div>"):a(C).addClass("ui-pg-button ui-corner-all").append("<div class='ui-pg-div'><span class='ui-icon "+b.buttonicon+"'></span>"+b.caption+
"</div>");b.id&&a(C).attr("id",b.id);if(b.position=="first")l.rows[0].cells.length===0?a("tr",l).append(C):a("tr td:eq(0)",l).before(C);else a("tr",l).append(C);a(C,l).attr("title",b.title||"").click(function(z){a(this).hasClass("ui-state-disabled")||a.isFunction(b.onClickButton)&&b.onClickButton.call(q,z);return false}).hover(function(){a(this).hasClass("ui-state-disabled")||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")})}}})},navSeparatorAdd:function(d,b){b=
a.extend({sepclass:"ui-separator",sepcontent:""},b||{});return this.each(function(){if(this.grid){if(d.indexOf("#")!==0)d="#"+d;var l=a(".navtable",d)[0];if(l){var q="<td class='ui-pg-button ui-state-disabled' style='width:4px;'><span class='"+b.sepclass+"'></span>"+b.sepcontent+"</td>";a("tr",l).append(q)}}})},GridToForm:function(d,b){return this.each(function(){if(this.grid){var l=a(this).jqGrid("getRowData",d);if(l)for(var q in l)a("[name="+a.jgrid.jqID(q)+"]",b).is("input:radio")||a("[name="+
a.jgrid.jqID(q)+"]",b).is("input:checkbox")?a("[name="+a.jgrid.jqID(q)+"]",b).each(function(){a(this).val()==l[q]?a(this).attr("checked","checked"):a(this).attr("checked","")}):a("[name="+a.jgrid.jqID(q)+"]",b).val(l[q])}})},FormToGrid:function(d,b,l,q){return this.each(function(){if(this.grid){l||(l="set");q||(q="first");var C=a(b).serializeArray(),z={};a.each(C,function(D,w){z[w.name]=w.value});if(l=="add")a(this).jqGrid("addRowData",d,z,q);else l=="set"&&a(this).jqGrid("setRowData",d,z)}})}})})(jQuery);
(function(d){d.fn.jqFilter=function(k){if(typeof k==="string"){var t=d.fn.jqFilter[k];if(!t)throw"jqFilter - No such method: "+k;var z=d.makeArray(arguments).slice(1);return t.apply(this,z)}var o=d.extend(true,{filter:null,columns:[],onChange:null,checkValues:null,error:false,errmsg:"",errorcheck:true,showQuery:true,sopt:null,ops:[{name:"eq",description:"equal",operator:"="},{name:"ne",description:"not equal",operator:"<>"},{name:"lt",description:"less",operator:"<"},{name:"le",description:"less or equal",
operator:"<="},{name:"gt",description:"greater",operator:">"},{name:"ge",description:"greater or equal",operator:">="},{name:"bw",description:"begins with",operator:"LIKE"},{name:"bn",description:"does not begin with",operator:"NOT LIKE"},{name:"in",description:"in",operator:"IN"},{name:"ni",description:"not in",operator:"NOT IN"},{name:"ew",description:"ends with",operator:"LIKE"},{name:"en",description:"does not end with",operator:"NOT LIKE"},{name:"cn",description:"contains",operator:"LIKE"},{name:"nc",
description:"does not contain",operator:"NOT LIKE"},{name:"nu",description:"is null",operator:"IS NULL"},{name:"nn",description:"is not null",operator:"IS NOT NULL"}],numopts:["eq","ne","lt","le","gt","ge","nu","nn","in","ni"],stropts:["eq","ne","bw","bn","ew","en","cn","nc","nu","nn","in","ni"],_gridsopt:[],groupOps:["AND","OR"],groupButton:true},k||{});return this.each(function(){if(!this.filter){this.p=o;if(this.p.filter===null||this.p.filter===undefined)this.p.filter={groupOp:this.p.groupOps[0],
rules:[],groups:[]};var p,u=this.p.columns.length,i,v=/msie/i.test(navigator.userAgent)&&!window.opera;if(this.p._gridsopt.length)for(p=0;p<this.p._gridsopt.length;p++)this.p.ops[p].description=this.p._gridsopt[p];this.p.initFilter=d.extend(true,{},this.p.filter);if(u){for(p=0;p<u;p++){i=this.p.columns[p];if(i.stype)i.inputtype=i.stype;else if(!i.inputtype)i.inputtype="text";if(i.sorttype)i.searchtype=i.sorttype;else if(!i.searchtype)i.searchtype="string";if(i.hidden===undefined)i.hidden=false;if(!i.label)i.label=
i.name;if(i.index)i.name=i.index;if(!i.hasOwnProperty("searchoptions"))i.searchoptions={};if(!i.hasOwnProperty("searchrules"))i.searchrules={}}this.p.showQuery&&d(this).append("<table class='queryresult ui-widget ui-widget-content' style='display:block;max-width:440px;border:0px none;'><tbody><tr><td class='query'></td></tr></tbody></table>");var w=function(c,f){var a=[true,""];if(d.isFunction(f.searchrules))a=f.searchrules(c,f);else if(d.jgrid&&d.jgrid.checkValues)try{a=d.jgrid.checkValues(c,-1,
null,f.searchrules,f.label)}catch(b){}if(a&&a.length&&a[0]===false){o.error=!a[0];o.errmsg=a[1]}};this.onchange=function(){this.p.error=false;this.p.errmsg="";return d.isFunction(this.p.onChange)?this.p.onChange.call(this,this.p):false};this.reDraw=function(){d("table.group:first",this).remove();var c=this.createTableForGroup(o.filter,null);d(this).append(c)};this.createTableForGroup=function(c,f){var a=this,b,e=d("<table class='group ui-widget ui-widget-content' style='border:0px none;'><tbody></tbody></table>");
f===null&&d(e).append("<tr class='error' style='display:none;'><th colspan='5' class='ui-state-error' align='left'></th></tr>");var g=d("<tr></tr>");d(e).append(g);var j=d("<th colspan='5' align='left'></th>");g.append(j);var h=d("<select class='opsel'></select>");j.append(h);g="";var l;for(b=0;b<o.groupOps.length;b++){l=c.groupOp===a.p.groupOps[b]?" selected='selected'":"";g+="<option value='"+a.p.groupOps[b]+"'"+l+">"+a.p.groupOps[b]+"</option>"}h.append(g).bind("change",function(){c.groupOp=d(h).val();
a.onchange()});g="<span></span>";if(this.p.groupButton){g=d("<input type='button' value='+ {}' title='Add subgroup' class='add-group'/>");g.bind("click",function(){if(c.groups===undefined)c.groups=[];c.groups.push({groupOp:o.groupOps[0],rules:[],groups:[]});a.reDraw();a.onchange();return false})}j.append(g);g=d("<input type='button' value='+' title='Add rule' class='add-rule ui-add'/>");var m;g.bind("click",function(){if(c.rules===undefined)c.rules=[];for(b=0;b<a.p.columns.length;b++){var n=typeof a.p.columns[b].search===
"undefined"?true:a.p.columns[b].search,r=a.p.columns[b].hidden===true;if(a.p.columns[b].searchoptions.searchhidden===true&&n||n&&!r){m=a.p.columns[b];break}}c.rules.push({field:m.name,op:(m.searchoptions.sopt?m.searchoptions.sopt:a.p.sopt?a.p.sopt:m.searchtype==="string"?a.p.stropts:a.p.numopts)[0],data:""});a.reDraw();return false});j.append(g);if(f!==null){g=d("<input type='button' value='-' title='Delete group' class='delete-group'/>");j.append(g);g.bind("click",function(){for(b=0;b<f.groups.length;b++)if(f.groups[b]===
c){f.groups.splice(b,1);break}a.reDraw();a.onchange();return false})}if(c.groups!==undefined)for(b=0;b<c.groups.length;b++){j=d("<tr></tr>");e.append(j);g=d("<td class='first'></td>");j.append(g);g=d("<td colspan='4'></td>");g.append(this.createTableForGroup(c.groups[b],c));j.append(g)}if(c.groupOp===undefined)c.groupOp=a.p.groupOps[0];if(c.rules!==undefined)for(b=0;b<c.rules.length;b++)e.append(this.createTableRowForRule(c.rules[b],c));return e};this.createTableRowForRule=function(c,f){var a=this,
b=d("<tr></tr>"),e,g,j,h,l="",m;b.append("<td class='first'></td>");var n=d("<td class='columns'></td>");b.append(n);var r=d("<select></select>");n.append(r);r.bind("change",function(){c.field=d(r).val();j=d(this).parents("tr:first");for(e=0;e<a.p.columns.length;e++)if(a.p.columns[e].name===c.field){h=a.p.columns[e];break}if(h){h.searchoptions.id=d.jgrid.randId();if(v&&h.inputtype==="text")if(!h.searchoptions.size)h.searchoptions.size=10;var q=d.jgrid.createEl(h.inputtype,h.searchoptions,"",true,
a.p.ajaxSelectOptions,true);d(q).addClass("input-elm");g=h.searchoptions.sopt?h.searchoptions.sopt:a.p.sopt?a.p.sopt:h.searchtype==="string"?a.p.stropts:a.p.numopts;var x="",y="";for(e=0;e<a.p.ops.length;e++)if(d.inArray(a.p.ops[e].name,g)!==-1){y=c.op===a.p.ops[e].name?" selected=selected":"";x+="<option value='"+a.p.ops[e].name+"'"+y+">"+a.p.ops[e].description+"</option>"}d(".selectopts",j).empty().append(x);d(".data",j).empty().append(q);d(".input-elm",j).bind("change",function(){c.data=d(this).val();
if(d.isArray(c.data))c.data=c.data.join(",");a.onchange()});setTimeout(function(){c.data=d(q).val();a.onchange()},0)}});for(e=n=0;e<a.p.columns.length;e++){m=typeof a.p.columns[e].search==="undefined"?true:a.p.columns[e].search;var A=a.p.columns[e].hidden===true;if(a.p.columns[e].searchoptions.searchhidden===true&&m||m&&!A){m="";if(c.field===a.p.columns[e].name){m=" selected='selected'";n=e}l+="<option value='"+a.p.columns[e].name+"'"+m+">"+a.p.columns[e].label+"</option>"}}r.append(l);l=d("<td class='operators'></td>");
b.append(l);h=o.columns[n];h.searchoptions.id=d.jgrid.randId();if(v&&h.inputtype==="text")if(!h.searchoptions.size)h.searchoptions.size=10;n=d.jgrid.createEl(h.inputtype,h.searchoptions,c.data,true,a.p.ajaxSelectOptions,true);var s=d("<select class='selectopts'></select>");l.append(s);s.bind("change",function(){c.op=d(s).val();j=d(this).parents("tr:first");var q=d(".input-elm",j)[0];if(c.op==="nu"||c.op==="nn"){c.data="";q.value="";q.setAttribute("readonly","true");q.setAttribute("disabled","true")}else{q.removeAttribute("readonly");
q.removeAttribute("disabled")}a.onchange()});g=h.searchoptions.sopt?h.searchoptions.sopt:a.p.sopt?a.p.sopt:h.searchtype==="string"?o.stropts:a.p.numopts;l="";for(e=0;e<a.p.ops.length;e++)if(d.inArray(a.p.ops[e].name,g)!==-1){m=c.op===a.p.ops[e].name?" selected='selected'":"";l+="<option value='"+a.p.ops[e].name+"'"+m+">"+a.p.ops[e].description+"</option>"}s.append(l);l=d("<td class='data'></td>");b.append(l);l.append(n);d(n).addClass("input-elm").bind("change",function(){c.data=d(this).val();if(d.isArray(c.data))c.data=
c.data.join(",");a.onchange()});l=d("<td></td>");b.append(l);n=d("<input type='button' value='-' title='Delete rule' class='delete-rule ui-del'/>");l.append(n);n.bind("click",function(){for(e=0;e<f.rules.length;e++)if(f.rules[e]===c){f.rules.splice(e,1);break}a.reDraw();a.onchange();return false});return b};this.getStringForGroup=function(c){var f="(",a;if(c.groups!==undefined)for(a=0;a<c.groups.length;a++){if(f.length>1)f+=" "+c.groupOp+" ";try{f+=this.getStringForGroup(c.groups[a])}catch(b){alert(b)}}if(c.rules!==
undefined)try{for(a=0;a<c.rules.length;a++){if(f.length>1)f+=" "+c.groupOp+" ";f+=this.getStringForRule(c.rules[a])}}catch(e){alert(e)}f+=")";return f==="()"?"":f};this.getStringForRule=function(c){var f="",a="",b,e;for(b=0;b<this.p.ops.length;b++)if(this.p.ops[b].name===c.op){f=this.p.ops[b].operator;a=this.p.ops[b].name;break}for(b=0;b<this.p.columns.length;b++)if(this.p.columns[b].name===c.field){e=this.p.columns[b];break}b=c.data;if(a==="bw"||a==="bn")b+="%";if(a==="ew"||a==="en")b="%"+b;if(a===
"cn"||a==="nc")b="%"+b+"%";if(a==="in"||a==="ni")b=" ("+b+")";o.errorcheck&&w(c.data,e);return d.inArray(e.searchtype,["int","integer","float","number","currency"])!==-1||a==="nn"||a==="nu"?c.field+" "+f+" "+b:c.field+" "+f+' "'+b+'"'};this.resetFilter=function(){this.p.filter=d.extend(true,{},this.p.initFilter);this.reDraw();this.onchange()};this.hideError=function(){d("th.ui-state-error",this).html("");d("tr.error",this).hide()};this.showError=function(){d("th.ui-state-error",this).html(this.p.errmsg);
d("tr.error",this).show()};this.toUserFriendlyString=function(){return this.getStringForGroup(o.filter)};this.toString=function(){function c(a){var b="(",e;if(a.groups!==undefined)for(e=0;e<a.groups.length;e++){if(b.length>1)b+=a.groupOp==="OR"?" || ":" && ";b+=c(a.groups[e])}if(a.rules!==undefined)for(e=0;e<a.rules.length;e++){if(b.length>1)b+=a.groupOp==="OR"?" || ":" && ";var g=a.rules[e];if(f.p.errorcheck){var j=void 0,h=void 0;for(j=0;j<f.p.columns.length;j++)if(f.p.columns[j].name===g.field){h=
f.p.columns[j];break}h&&w(g.data,h)}b+=g.op+"(item."+g.field+",'"+g.data+"')"}b+=")";return b==="()"?"":b}var f=this;return c(this.p.filter)};this.reDraw();if(this.p.showQuery)this.onchange();this.filter=true}}})};d.extend(d.fn.jqFilter,{toSQLString:function(){var k="";this.each(function(){k=this.toUserFriendlyString()});return k},filterData:function(){var k;this.each(function(){k=this.p.filter});return k},getParameter:function(k){if(k!==undefined)if(this.p.hasOwnProperty(k))return this.p[k];return this.p},
resetFilter:function(){return this.each(function(){this.resetFilter()})},addFilter:function(k){if(typeof k==="string")k=jQuery.jgrid.parse(k);this.each(function(){this.p.filter=k;this.reDraw();this.onchange()})}})})(jQuery);
(function(a){a.jgrid.extend({editRow:function(e,x,l,r,t,y,u,j,s){var f={keys:x||false,oneditfunc:l||null,successfunc:r||null,url:t||null,extraparam:y||{},aftersavefunc:u||null,errorfunc:j||null,afterrestorefunc:s||null,restoreAfterErorr:true},o=a.makeArray(arguments).slice(1),b;b=o[0]&&typeof o[0]=="object"&&!a.isFunction(o[0])?a.extend(f,o[0]):f;return this.each(function(){var d=this,c,m,v=0,q=null,p={},k,g;if(d.grid){k=a(d).jqGrid("getInd",e,true);if(k!==false)if((a(k).attr("editable")||"0")=="0"&&
!a(k).hasClass("not-editable-row")){g=d.p.colModel;a("td",k).each(function(h){c=g[h].name;var A=d.p.treeGrid===true&&c==d.p.ExpandColumn;if(A)m=a("span:first",this).html();else try{m=a.unformat(this,{rowId:e,colModel:g[h]},h)}catch(n){m=a(this).html()}if(c!="cb"&&c!="subgrid"&&c!="rn"){if(d.p.autoencode)m=a.jgrid.htmlDecode(m);p[c]=m;if(g[h].editable===true){if(q===null)q=h;A?a("span:first",this).html(""):a(this).html("");var i=a.extend({},g[h].editoptions||{},{id:e+"_"+c,name:c});if(!g[h].edittype)g[h].edittype=
"text";i=a.jgrid.createEl(g[h].edittype,i,m,true,a.extend({},a.jgrid.ajaxOptions,d.p.ajaxSelectOptions||{}));a(i).addClass("editable");A?a("span:first",this).append(i):a(this).append(i);g[h].edittype=="select"&&g[h].editoptions.multiple===true&&a.browser.msie&&a(i).width(a(i).width());v++}}});if(v>0){p.id=e;d.p.savedRow.push(p);a(k).attr("editable","1");a("td:eq("+q+") input",k).focus();b.keys===true&&a(k).bind("keydown",function(h){h.keyCode===27&&a(d).jqGrid("restoreRow",e,s);if(h.keyCode===13){if(h.target.tagName==
"TEXTAREA")return true;a(d).jqGrid("saveRow",e,b);return false}h.stopPropagation()});a.isFunction(b.oneditfunc)&&b.oneditfunc.call(d,e)}}}})},saveRow:function(e,x,l,r,t,y,u){var j={successfunc:x||null,url:l||null,extraparam:r||{},aftersavefunc:t||null,errorfunc:y||null,afterrestorefunc:u||null,restoreAfterErorr:true},s=a.makeArray(arguments).slice(1),f;f=s[0]&&typeof s[0]=="object"&&!a.isFunction(s[0])?a.extend(j,s[0]):j;var o=false,b=this[0],d,c={},m={},v={},q,p,k;if(!b.grid)return o;k=a(b).jqGrid("getInd",
e,true);if(k===false)return o;j=a(k).attr("editable");f.url=f.url?f.url:b.p.editurl;if(j==="1"){var g;a("td",k).each(function(n){g=b.p.colModel[n];d=g.name;if(d!="cb"&&d!="subgrid"&&g.editable===true&&d!="rn"&&!a(this).hasClass("not-editable-cell")){switch(g.edittype){case "checkbox":var i=["Yes","No"];if(g.editoptions)i=g.editoptions.value.split(":");c[d]=a("input",this).attr("checked")?i[0]:i[1];break;case "text":case "password":case "textarea":case "button":c[d]=a("input, textarea",this).val();
break;case "select":if(g.editoptions.multiple){i=a("select",this);var w=[];c[d]=a(i).val();c[d]=c[d]?c[d].join(","):"";a("select > option:selected",this).each(function(B,C){w[B]=a(C).text()});m[d]=w.join(",")}else{c[d]=a("select>option:selected",this).val();m[d]=a("select>option:selected",this).text()}if(g.formatter&&g.formatter=="select")m={};break;case "custom":try{if(g.editoptions&&a.isFunction(g.editoptions.custom_value)){c[d]=g.editoptions.custom_value.call(b,a(".customelement",this),"get");
if(c[d]===undefined)throw"e2";}else throw"e1";}catch(z){z=="e1"&&a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,"function 'custom_value' "+a.jgrid.edit.msg.nodefined,jQuery.jgrid.edit.bClose);z=="e2"?a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,"function 'custom_value' "+a.jgrid.edit.msg.novalue,jQuery.jgrid.edit.bClose):a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,z.message,jQuery.jgrid.edit.bClose)}}p=a.jgrid.checkValues(c[d],n,b);if(p[0]===false){p[1]=c[d]+" "+p[1];return false}if(b.p.autoencode)c[d]=
a.jgrid.htmlEncode(c[d]);if(f.url!=="clientArray"&&g.editoptions&&g.editoptions.NullIfEmpty===true)if(c[d]=="")v[d]="null"}});if(p[0]===false){try{var h=a.jgrid.findPos(a("#"+a.jgrid.jqID(e),b.grid.bDiv)[0]);a.jgrid.info_dialog(a.jgrid.errors.errcap,p[1],a.jgrid.edit.bClose,{left:h[0],top:h[1]})}catch(A){alert(p[1])}return o}if(c){j=b.p.prmNames;s=j.oper;h=j.id;c[s]=j.editoper;c[h]=e;if(typeof b.p.inlineData=="undefined")b.p.inlineData={};c=a.extend({},c,b.p.inlineData,f.extraparam)}if(f.url=="clientArray"){c=
a.extend({},c,m);b.p.autoencode&&a.each(c,function(n,i){c[n]=a.jgrid.htmlDecode(i)});h=a(b).jqGrid("setRowData",e,c);a(k).attr("editable","0");for(j=0;j<b.p.savedRow.length;j++)if(b.p.savedRow[j].id==e){q=j;break}q>=0&&b.p.savedRow.splice(q,1);a.isFunction(f.aftersavefunc)&&f.aftersavefunc.call(b,e,h);o=true}else{a("#lui_"+b.p.id).show();v=a.extend({},c,v);a.ajax(a.extend({url:f.url,data:a.isFunction(b.p.serializeRowData)?b.p.serializeRowData.call(b,v):v,type:"POST",async:false,complete:function(n,
i){a("#lui_"+b.p.id).hide();if(i==="success")if((a.isFunction(f.succesfunc)?f.succesfunc.call(b,n):true)===true){b.p.autoencode&&a.each(c,function(z,B){c[z]=a.jgrid.htmlDecode(B)});c=a.extend({},c,m);a(b).jqGrid("setRowData",e,c);a(k).attr("editable","0");for(var w=0;w<b.p.savedRow.length;w++)if(b.p.savedRow[w].id==e){q=w;break}q>=0&&b.p.savedRow.splice(q,1);a.isFunction(f.aftersavefunc)&&f.aftersavefunc.call(b,e,n);o=true}else{a.isFunction(f.errorfunc)&&f.errorfunc.call(b,e,n,i);f.restoreAfterError===
true&&a(b).jqGrid("restoreRow",e,f.afterrestorefunc)}},error:function(n,i){a("#lui_"+b.p.id).hide();if(a.isFunction(f.errorfunc))f.errorfunc.call(b,e,n,i);else try{jQuery.jgrid.info_dialog(jQuery.jgrid.errors.errcap,'<div class="ui-state-error">'+n.responseText+"</div>",jQuery.jgrid.edit.bClose,{buttonalign:"right"})}catch(w){alert(n.responseText)}f.restoreAfterError===true&&a(b).jqGrid("restoreRow",e,f.afterrestorefunc)}},a.jgrid.ajaxOptions,b.p.ajaxRowOptions||{}))}a(k).unbind("keydown")}return o},
restoreRow:function(e,x){return this.each(function(){var l=this,r,t,y={};if(l.grid){t=a(l).jqGrid("getInd",e,true);if(t!==false){for(var u=0;u<l.p.savedRow.length;u++)if(l.p.savedRow[u].id==e){r=u;break}if(r>=0){if(a.isFunction(a.fn.datepicker))try{a("input.hasDatepicker","#"+a.jgrid.jqID(t.id)).datepicker("hide")}catch(j){}a.each(l.p.colModel,function(){if(this.editable===true&&this.name in l.p.savedRow[r]&&!a(this).hasClass("not-editable-cell"))y[this.name]=l.p.savedRow[r][this.name]});a(l).jqGrid("setRowData",
e,y);a(t).attr("editable","0").unbind("keydown");l.p.savedRow.splice(r,1)}a.isFunction(x)&&x.call(l,e)}}})}})})(jQuery);
(function(b){b.jgrid.extend({editCell:function(d,e,a){return this.each(function(){var c=this,h,f,g;if(!(!c.grid||c.p.cellEdit!==true)){e=parseInt(e,10);c.p.selrow=c.rows[d].id;c.p.knv||b(c).jqGrid("GridNav");if(c.p.savedRow.length>0){if(a===true)if(d==c.p.iRow&&e==c.p.iCol)return;b(c).jqGrid("saveCell",c.p.savedRow[0].id,c.p.savedRow[0].ic)}else window.setTimeout(function(){b("#"+c.p.knv).attr("tabindex","-1").focus()},0);h=c.p.colModel[e].name;if(!(h=="subgrid"||h=="cb"||h=="rn")){g=b("td:eq("+e+
")",c.rows[d]);if(c.p.colModel[e].editable===true&&a===true&&!g.hasClass("not-editable-cell")){if(parseInt(c.p.iCol,10)>=0&&parseInt(c.p.iRow,10)>=0){b("td:eq("+c.p.iCol+")",c.rows[c.p.iRow]).removeClass("edit-cell ui-state-highlight");b(c.rows[c.p.iRow]).removeClass("selected-row ui-state-hover")}b(g).addClass("edit-cell ui-state-highlight");b(c.rows[d]).addClass("selected-row ui-state-hover");try{f=b.unformat(g,{rowId:c.rows[d].id,colModel:c.p.colModel[e]},e)}catch(k){f=b(g).html()}if(c.p.autoencode)f=
b.jgrid.htmlDecode(f);if(!c.p.colModel[e].edittype)c.p.colModel[e].edittype="text";c.p.savedRow.push({id:d,ic:e,name:h,v:f});if(b.isFunction(c.p.formatCell)){var j=c.p.formatCell.call(c,c.rows[d].id,h,f,d,e);if(j!==undefined)f=j}j=b.extend({},c.p.colModel[e].editoptions||{},{id:d+"_"+h,name:h});var i=b.jgrid.createEl(c.p.colModel[e].edittype,j,f,true,b.extend({},b.jgrid.ajaxOptions,c.p.ajaxSelectOptions||{}));b.isFunction(c.p.beforeEditCell)&&c.p.beforeEditCell.call(c,c.rows[d].id,h,f,d,e);b(g).html("").append(i).attr("tabindex",
"0");window.setTimeout(function(){b(i).focus()},0);b("input, select, textarea",g).bind("keydown",function(l){if(l.keyCode===27)if(b("input.hasDatepicker",g).length>0)b(".ui-datepicker").is(":hidden")?b(c).jqGrid("restoreCell",d,e):b("input.hasDatepicker",g).datepicker("hide");else b(c).jqGrid("restoreCell",d,e);l.keyCode===13&&b(c).jqGrid("saveCell",d,e);if(l.keyCode==9)if(c.grid.hDiv.loading)return false;else l.shiftKey?b(c).jqGrid("prevCell",d,e):b(c).jqGrid("nextCell",d,e);l.stopPropagation()});
b.isFunction(c.p.afterEditCell)&&c.p.afterEditCell.call(c,c.rows[d].id,h,f,d,e)}else{if(parseInt(c.p.iCol,10)>=0&&parseInt(c.p.iRow,10)>=0){b("td:eq("+c.p.iCol+")",c.rows[c.p.iRow]).removeClass("edit-cell ui-state-highlight");b(c.rows[c.p.iRow]).removeClass("selected-row ui-state-hover")}g.addClass("edit-cell ui-state-highlight");b(c.rows[d]).addClass("selected-row ui-state-hover");if(b.isFunction(c.p.onSelectCell)){f=g.html().replace(/\&#160\;/ig,"");c.p.onSelectCell.call(c,c.rows[d].id,h,f,d,e)}}c.p.iCol=
e;c.p.iRow=d}}})},saveCell:function(d,e){return this.each(function(){var a=this,c;if(!(!a.grid||a.p.cellEdit!==true)){c=a.p.savedRow.length>=1?0:null;if(c!==null){var h=b("td:eq("+e+")",a.rows[d]),f,g,k=a.p.colModel[e],j=k.name,i=b.jgrid.jqID(j);switch(k.edittype){case "select":if(k.editoptions.multiple){i=b("#"+d+"_"+i,a.rows[d]);var l=[];if(f=b(i).val())f.join(",");else f="";b("option:selected",i).each(function(o,p){l[o]=b(p).text()});g=l.join(",")}else{f=b("#"+d+"_"+i+">option:selected",a.rows[d]).val();
g=b("#"+d+"_"+i+">option:selected",a.rows[d]).text()}if(k.formatter)g=f;break;case "checkbox":var m=["Yes","No"];if(k.editoptions)m=k.editoptions.value.split(":");g=f=b("#"+d+"_"+i,a.rows[d]).attr("checked")?m[0]:m[1];break;case "password":case "text":case "textarea":case "button":g=f=b("#"+d+"_"+i,a.rows[d]).val();break;case "custom":try{if(k.editoptions&&b.isFunction(k.editoptions.custom_value)){f=k.editoptions.custom_value.call(a,b(".customelement",h),"get");if(f===undefined)throw"e2";else g=f}else throw"e1";
}catch(q){q=="e1"&&b.jgrid.info_dialog(jQuery.jgrid.errors.errcap,"function 'custom_value' "+b.jgrid.edit.msg.nodefined,jQuery.jgrid.edit.bClose);q=="e2"?b.jgrid.info_dialog(jQuery.jgrid.errors.errcap,"function 'custom_value' "+b.jgrid.edit.msg.novalue,jQuery.jgrid.edit.bClose):b.jgrid.info_dialog(jQuery.jgrid.errors.errcap,q.message,jQuery.jgrid.edit.bClose)}}if(g!=a.p.savedRow[c].v){if(b.isFunction(a.p.beforeSaveCell))if(c=a.p.beforeSaveCell.call(a,a.rows[d].id,j,f,d,e))g=f=c;var r=b.jgrid.checkValues(f,
e,a);if(r[0]===true){c={};if(b.isFunction(a.p.beforeSubmitCell))(c=a.p.beforeSubmitCell.call(a,a.rows[d].id,j,f,d,e))||(c={});b("input.hasDatepicker",h).length>0&&b("input.hasDatepicker",h).datepicker("hide");if(a.p.cellsubmit=="remote")if(a.p.cellurl){var n={};if(a.p.autoencode)f=b.jgrid.htmlEncode(f);n[j]=f;m=a.p.prmNames;k=m.id;i=m.oper;n[k]=a.rows[d].id;n[i]=m.editoper;n=b.extend(c,n);b("#lui_"+a.p.id).show();a.grid.hDiv.loading=true;b.ajax(b.extend({url:a.p.cellurl,data:b.isFunction(a.p.serializeCellData)?
a.p.serializeCellData.call(a,n):n,type:"POST",complete:function(o,p){b("#lui_"+a.p.id).hide();a.grid.hDiv.loading=false;if(p=="success")if(b.isFunction(a.p.afterSubmitCell)){var s=a.p.afterSubmitCell.call(a,o,n.id,j,f,d,e);if(s[0]===true){b(h).empty();b(a).jqGrid("setCell",a.rows[d].id,e,g,false,false,true);b(h).addClass("dirty-cell");b(a.rows[d]).addClass("edited");b.isFunction(a.p.afterSaveCell)&&a.p.afterSaveCell.call(a,a.rows[d].id,j,f,d,e);a.p.savedRow.splice(0,1)}else{b.jgrid.info_dialog(b.jgrid.errors.errcap,
s[1],b.jgrid.edit.bClose);b(a).jqGrid("restoreCell",d,e)}}else{b(h).empty();b(a).jqGrid("setCell",a.rows[d].id,e,g,false,false,true);b(h).addClass("dirty-cell");b(a.rows[d]).addClass("edited");b.isFunction(a.p.afterSaveCell)&&a.p.afterSaveCell.call(a,a.rows[d].id,j,f,d,e);a.p.savedRow.splice(0,1)}},error:function(o,p){b("#lui_"+a.p.id).hide();a.grid.hDiv.loading=false;b.isFunction(a.p.errorCell)?a.p.errorCell.call(a,o,p):b.jgrid.info_dialog(b.jgrid.errors.errcap,o.status+" : "+o.statusText+"<br/>"+
p,b.jgrid.edit.bClose);b(a).jqGrid("restoreCell",d,e)}},b.jgrid.ajaxOptions,a.p.ajaxCellOptions||{}))}else try{b.jgrid.info_dialog(b.jgrid.errors.errcap,b.jgrid.errors.nourl,b.jgrid.edit.bClose);b(a).jqGrid("restoreCell",d,e)}catch(t){}if(a.p.cellsubmit=="clientArray"){b(h).empty();b(a).jqGrid("setCell",a.rows[d].id,e,g,false,false,true);b(h).addClass("dirty-cell");b(a.rows[d]).addClass("edited");b.isFunction(a.p.afterSaveCell)&&a.p.afterSaveCell.call(a,a.rows[d].id,j,f,d,e);a.p.savedRow.splice(0,
1)}}else try{window.setTimeout(function(){b.jgrid.info_dialog(b.jgrid.errors.errcap,f+" "+r[1],b.jgrid.edit.bClose)},100);b(a).jqGrid("restoreCell",d,e)}catch(u){}}else b(a).jqGrid("restoreCell",d,e)}b.browser.opera?b("#"+a.p.knv).attr("tabindex","-1").focus():window.setTimeout(function(){b("#"+a.p.knv).attr("tabindex","-1").focus()},0)}})},restoreCell:function(d,e){return this.each(function(){var a=this,c;if(!(!a.grid||a.p.cellEdit!==true)){c=a.p.savedRow.length>=1?0:null;if(c!==null){var h=b("td:eq("+
e+")",a.rows[d]);if(b.isFunction(b.fn.datepicker))try{b("input.hasDatepicker",h).datepicker("hide")}catch(f){}b(h).empty().attr("tabindex","-1");b(a).jqGrid("setCell",a.rows[d].id,e,a.p.savedRow[c].v,false,false,true);b.isFunction(a.p.afterRestoreCell)&&a.p.afterRestoreCell.call(a,a.rows[d].id,a.p.savedRow[c].v,d,e);a.p.savedRow.splice(0,1)}window.setTimeout(function(){b("#"+a.p.knv).attr("tabindex","-1").focus()},0)}})},nextCell:function(d,e){return this.each(function(){var a=false;if(!(!this.grid||
this.p.cellEdit!==true)){for(var c=e+1;c<this.p.colModel.length;c++)if(this.p.colModel[c].editable===true){a=c;break}if(a!==false)b(this).jqGrid("editCell",d,a,true);else this.p.savedRow.length>0&&b(this).jqGrid("saveCell",d,e)}})},prevCell:function(d,e){return this.each(function(){var a=false;if(!(!this.grid||this.p.cellEdit!==true)){for(var c=e-1;c>=0;c--)if(this.p.colModel[c].editable===true){a=c;break}if(a!==false)b(this).jqGrid("editCell",d,a,true);else this.p.savedRow.length>0&&b(this).jqGrid("saveCell",
d,e)}})},GridNav:function(){return this.each(function(){function d(g,k,j){if(j.substr(0,1)=="v"){var i=b(a.grid.bDiv)[0].clientHeight,l=b(a.grid.bDiv)[0].scrollTop,m=a.rows[g].offsetTop+a.rows[g].clientHeight,q=a.rows[g].offsetTop;if(j=="vd")if(m>=i)b(a.grid.bDiv)[0].scrollTop=b(a.grid.bDiv)[0].scrollTop+a.rows[g].clientHeight;if(j=="vu")if(q<l)b(a.grid.bDiv)[0].scrollTop=b(a.grid.bDiv)[0].scrollTop-a.rows[g].clientHeight}if(j=="h"){j=b(a.grid.bDiv)[0].clientWidth;i=b(a.grid.bDiv)[0].scrollLeft;l=
a.rows[g].cells[k].offsetLeft;if(a.rows[g].cells[k].offsetLeft+a.rows[g].cells[k].clientWidth>=j+parseInt(i,10))b(a.grid.bDiv)[0].scrollLeft=b(a.grid.bDiv)[0].scrollLeft+a.rows[g].cells[k].clientWidth;else if(l<i)b(a.grid.bDiv)[0].scrollLeft=b(a.grid.bDiv)[0].scrollLeft-a.rows[g].cells[k].clientWidth}}function e(g,k){var j,i;if(k=="lft"){j=g+1;for(i=g;i>=0;i--)if(a.p.colModel[i].hidden!==true){j=i;break}}if(k=="rgt"){j=g-1;for(i=g;i<a.p.colModel.length;i++)if(a.p.colModel[i].hidden!==true){j=i;break}}return j}
var a=this;if(!(!a.grid||a.p.cellEdit!==true)){a.p.knv=a.p.id+"_kn";var c=b("<span style='width:0px;height:0px;background-color:black;' tabindex='0'><span tabindex='-1' style='width:0px;height:0px;background-color:grey' id='"+a.p.knv+"'></span></span>"),h,f;b(c).insertBefore(a.grid.cDiv);b("#"+a.p.knv).focus().keydown(function(g){f=g.keyCode;if(a.p.direction=="rtl")if(f==37)f=39;else if(f==39)f=37;switch(f){case 38:if(a.p.iRow-1>0){d(a.p.iRow-1,a.p.iCol,"vu");b(a).jqGrid("editCell",a.p.iRow-1,a.p.iCol,
false)}break;case 40:if(a.p.iRow+1<=a.rows.length-1){d(a.p.iRow+1,a.p.iCol,"vd");b(a).jqGrid("editCell",a.p.iRow+1,a.p.iCol,false)}break;case 37:if(a.p.iCol-1>=0){h=e(a.p.iCol-1,"lft");d(a.p.iRow,h,"h");b(a).jqGrid("editCell",a.p.iRow,h,false)}break;case 39:if(a.p.iCol+1<=a.p.colModel.length-1){h=e(a.p.iCol+1,"rgt");d(a.p.iRow,h,"h");b(a).jqGrid("editCell",a.p.iRow,h,false)}break;case 13:parseInt(a.p.iCol,10)>=0&&parseInt(a.p.iRow,10)>=0&&b(a).jqGrid("editCell",a.p.iRow,a.p.iCol,true)}return false})}})},
getChangedCells:function(d){var e=[];d||(d="all");this.each(function(){var a=this,c;!a.grid||a.p.cellEdit!==true||b(a.rows).each(function(h){var f={};if(b(this).hasClass("edited")){b("td",this).each(function(g){c=a.p.colModel[g].name;if(c!=="cb"&&c!=="subgrid")if(d=="dirty"){if(b(this).hasClass("dirty-cell"))try{f[c]=b.unformat(this,{rowId:a.rows[h].id,colModel:a.p.colModel[g]},g)}catch(k){f[c]=b.jgrid.htmlDecode(b(this).html())}}else try{f[c]=b.unformat(this,{rowId:a.rows[h].id,colModel:a.p.colModel[g]},
g)}catch(j){f[c]=b.jgrid.htmlDecode(b(this).html())}});f.id=this.id;e.push(f)}})});return e}})})(jQuery);
(function(b){b.fn.jqm=function(a){var f={overlay:50,closeoverlay:true,overlayClass:"jqmOverlay",closeClass:"jqmClose",trigger:".jqModal",ajax:e,ajaxText:"",target:e,modal:e,toTop:e,onShow:e,onHide:e,onLoad:e};return this.each(function(){if(this._jqm)return j[this._jqm].c=b.extend({},j[this._jqm].c,a);l++;this._jqm=l;j[l]={c:b.extend(f,b.jqm.params,a),a:e,w:b(this).addClass("jqmID"+l),s:l};f.trigger&&b(this).jqmAddTrigger(f.trigger)})};b.fn.jqmAddClose=function(a){return o(this,a,"jqmHide")};b.fn.jqmAddTrigger=
function(a){return o(this,a,"jqmShow")};b.fn.jqmShow=function(a){return this.each(function(){b.jqm.open(this._jqm,a)})};b.fn.jqmHide=function(a){return this.each(function(){b.jqm.close(this._jqm,a)})};b.jqm={hash:{},open:function(a,f){var c=j[a],d=c.c,i="."+d.closeClass,g=parseInt(c.w.css("z-index"));g=g>0?g:3E3;var h=b("<div></div>").css({height:"100%",width:"100%",position:"fixed",left:0,top:0,"z-index":g-1,opacity:d.overlay/100});if(c.a)return e;c.t=f;c.a=true;c.w.css("z-index",g);if(d.modal){k[0]||
setTimeout(function(){p("bind")},1);k.push(a)}else if(d.overlay>0)d.closeoverlay&&c.w.jqmAddClose(h);else h=e;c.o=h?h.addClass(d.overlayClass).prependTo("body"):e;if(q){b("html,body").css({height:"100%",width:"100%"});if(h){h=h.css({position:"absolute"})[0];for(var m in{Top:1,Left:1})h.style.setExpression(m.toLowerCase(),"(_=(document.documentElement.scroll"+m+" || document.body.scroll"+m+"))+'px'")}}if(d.ajax){g=d.target||c.w;h=d.ajax;g=typeof g=="string"?b(g,c.w):b(g);h=h.substr(0,1)=="@"?b(f).attr(h.substring(1)):
h;g.html(d.ajaxText).load(h,function(){d.onLoad&&d.onLoad.call(this,c);i&&c.w.jqmAddClose(b(i,c.w));r(c)})}else i&&c.w.jqmAddClose(b(i,c.w));d.toTop&&c.o&&c.w.before('<span id="jqmP'+c.w[0]._jqm+'"></span>').insertAfter(c.o);d.onShow?d.onShow(c):c.w.show();r(c);return e},close:function(a){a=j[a];if(!a.a)return e;a.a=e;if(k[0]){k.pop();k[0]||p("unbind")}a.c.toTop&&a.o&&b("#jqmP"+a.w[0]._jqm).after(a.w).remove();if(a.c.onHide)a.c.onHide(a);else{a.w.hide();a.o&&a.o.remove()}return e},params:{}};var l=
0,j=b.jqm.hash,k=[],q=b.browser.msie&&b.browser.version=="6.0",e=false,r=function(a){var f=b('<iframe src="javascript:false;document.write(\'\');" class="jqm"></iframe>').css({opacity:0});if(q)if(a.o)a.o.html('<p style="width:100%;height:100%"/>').prepend(f);else b("iframe.jqm",a.w)[0]||a.w.prepend(f);s(a)},s=function(a){try{b(":input:visible",a.w)[0].focus()}catch(f){}},p=function(a){b(document)[a]("keypress",n)[a]("keydown",n)[a]("mousedown",n)},n=function(a){var f=j[k[k.length-1]];(a=!b(a.target).parents(".jqmID"+
f.s)[0])&&s(f);return!a},o=function(a,f,c){return a.each(function(){var d=this._jqm;b(f).each(function(){if(!this[c]){this[c]=[];b(this).click(function(){for(var i in{jqmShow:1,jqmHide:1})for(var g in this[i])if(j[this[i][g]])j[this[i][g]].w[i](this);return e})}this[c].push(d)})})}})(jQuery);
(function(b){b.fn.jqDrag=function(a){return l(this,a,"d")};b.fn.jqResize=function(a,e){return l(this,a,"r",e)};b.jqDnR={dnr:{},e:0,drag:function(a){if(c.k=="d")d.css({left:c.X+a.pageX-c.pX,top:c.Y+a.pageY-c.pY});else{d.css({width:Math.max(a.pageX-c.pX+c.W,0),height:Math.max(a.pageY-c.pY+c.H,0)});M1&&f.css({width:Math.max(a.pageX-M1.pX+M1.W,0),height:Math.max(a.pageY-M1.pY+M1.H,0)})}return false},stop:function(){b(document).unbind("mousemove",i.drag).unbind("mouseup",i.stop)}};var i=b.jqDnR,c=i.dnr,
d=i.e,f,l=function(a,e,n,m){return a.each(function(){e=e?b(e,a):a;e.bind("mousedown",{e:a,k:n},function(g){var j=g.data,h={};d=j.e;f=m?b(m):false;if(d.css("position")!="relative")try{d.position(h)}catch(o){}c={X:h.left||k("left")||0,Y:h.top||k("top")||0,W:k("width")||d[0].scrollWidth||0,H:k("height")||d[0].scrollHeight||0,pX:g.pageX,pY:g.pageY,k:j.k};M1=f&&j.k!="d"?{X:h.left||f1("left")||0,Y:h.top||f1("top")||0,W:f[0].offsetWidth||f1("width")||0,H:f[0].offsetHeight||f1("height")||0,pX:g.pageX,pY:g.pageY,
k:j.k}:false;if(b("input.hasDatepicker",d[0])[0])try{b("input.hasDatepicker",d[0]).datepicker("hide")}catch(p){}b(document).mousemove(b.jqDnR.drag).mouseup(b.jqDnR.stop);return false})})},k=function(a){return parseInt(d.css(a))||false};f1=function(a){return parseInt(f.css(a))||false}})(jQuery);
(function(b){b.jgrid.extend({setSubGrid:function(){return this.each(function(){var f;this.p.subGridOptions=b.extend({plusicon:"ui-icon-plus",minusicon:"ui-icon-minus",openicon:"ui-icon-carat-1-sw",expandOnLoad:false,delayOnLoad:50,selectOnExpand:false,reloadOnExpand:true},this.p.subGridOptions||{});this.p.colNames.unshift("");this.p.colModel.unshift({name:"subgrid",width:b.browser.safari?this.p.subGridWidth+this.p.cellLayout:this.p.subGridWidth,sortable:false,resizable:false,hidedlg:true,search:false,
fixed:true});f=this.p.subGridModel;if(f[0]){f[0].align=b.extend([],f[0].align||[]);for(var a=0;a<f[0].name.length;a++)f[0].align[a]=f[0].align[a]||"left"}})},addSubGridCell:function(f,a){var j="",q,r;this.each(function(){j=this.formatCol(f,a);r=this.p.id;q=this.p.subGridOptions.plusicon});return'<td role="grid" aria-describedby="'+r+'_subgrid" class="ui-sgcollapsed sgcollapsed" '+j+"><a href='javascript:void(0);'><span class='ui-icon "+q+"'></span></a></td>"},addSubGrid:function(f){return this.each(function(){var a=
this;if(a.grid){var j=function(i,d,e){d=b("<td align='"+a.p.subGridModel[0].align[e]+"'></td>").html(d);b(i).append(d)},q=function(i,d){var e,c,g,k=b("<table cellspacing='0' cellpadding='0' border='0'><tbody></tbody></table>"),h=b("<tr></tr>");for(c=0;c<a.p.subGridModel[0].name.length;c++){e=b("<th class='ui-state-default ui-th-subgrid ui-th-column ui-th-"+a.p.direction+"'></th>");b(e).html(a.p.subGridModel[0].name[c]);b(e).width(a.p.subGridModel[0].width[c]);b(h).append(e)}b(k).append(h);if(i){g=
a.p.xmlReader.subgrid;b(g.root+" "+g.row,i).each(function(){h=b("<tr class='ui-widget-content ui-subtblcell'></tr>");if(g.repeatitems===true)b(g.cell,this).each(function(m){j(h,b(this).text()||"&#160;",m)});else{var n=a.p.subGridModel[0].mapping||a.p.subGridModel[0].name;if(n)for(c=0;c<n.length;c++)j(h,b(n[c],this).text()||"&#160;",c)}b(k).append(h)})}e=b("table:first",a.grid.bDiv).attr("id")+"_";b("#"+e+d).append(k);a.grid.hDiv.loading=false;b("#load_"+a.p.id).hide();return false},r=function(i,d){var e,
c,g,k,h,n=b("<table cellspacing='0' cellpadding='0' border='0'><tbody></tbody></table>"),m=b("<tr></tr>");for(c=0;c<a.p.subGridModel[0].name.length;c++){e=b("<th class='ui-state-default ui-th-subgrid ui-th-column ui-th-"+a.p.direction+"'></th>");b(e).html(a.p.subGridModel[0].name[c]);b(e).width(a.p.subGridModel[0].width[c]);b(m).append(e)}b(n).append(m);if(i){k=a.p.jsonReader.subgrid;e=i[k.root];if(typeof e!=="undefined")for(c=0;c<e.length;c++){g=e[c];m=b("<tr class='ui-widget-content ui-subtblcell'></tr>");
if(k.repeatitems===true){if(k.cell)g=g[k.cell];for(h=0;h<g.length;h++)j(m,g[h]||"&#160;",h)}else{var t=a.p.subGridModel[0].mapping||a.p.subGridModel[0].name;if(t.length)for(h=0;h<t.length;h++)j(m,g[t[h]]||"&#160;",h)}b(n).append(m)}}c=b("table:first",a.grid.bDiv).attr("id")+"_";b("#"+c+d).append(n);a.grid.hDiv.loading=false;b("#load_"+a.p.id).hide();return false},w=function(i){var d,e,c,g;d=b(i).attr("id");e={nd_:(new Date).getTime()};e[a.p.prmNames.subgridid]=d;if(!a.p.subGridModel[0])return false;
if(a.p.subGridModel[0].params)for(g=0;g<a.p.subGridModel[0].params.length;g++)for(c=0;c<a.p.colModel.length;c++)if(a.p.colModel[c].name==a.p.subGridModel[0].params[g])e[a.p.colModel[c].name]=b("td:eq("+c+")",i).text().replace(/\&#160\;/ig,"");if(!a.grid.hDiv.loading){a.grid.hDiv.loading=true;b("#load_"+a.p.id).show();if(!a.p.subgridtype)a.p.subgridtype=a.p.datatype;if(b.isFunction(a.p.subgridtype))a.p.subgridtype.call(a,e);else a.p.subgridtype=a.p.subgridtype.toLowerCase();switch(a.p.subgridtype){case "xml":case "json":b.ajax(b.extend({type:a.p.mtype,
url:a.p.subGridUrl,dataType:a.p.subgridtype,data:b.isFunction(a.p.serializeSubGridData)?a.p.serializeSubGridData.call(a,e):e,complete:function(k){a.p.subgridtype=="xml"?q(k.responseXML,d):r(b.jgrid.parse(k.responseText),d)}},b.jgrid.ajaxOptions,a.p.ajaxSubgridOptions||{}))}}return false},l,s,u,v=0,o,p;b.each(a.p.colModel,function(){if(this.hidden===true||this.name=="rn"||this.name=="cb")v++});b(a.rows).each(function(i){var d=this;if(b(d).hasClass("jqgrow")){b(this.cells[f]).bind("click",function(){p=
d.nextSibling;if(b(this).hasClass("sgcollapsed")){s=a.p.id;l=d.id;if(a.p.subGridOptions.reloadOnExpand===true||a.p.subGridOptions.reloadOnExpand===false&&!b(p).hasClass("ui-subgrid")){u=f>=1?"<td colspan='"+f+"'>&#160;</td>":"";o=true;if(b.isFunction(a.p.subGridBeforeExpand))o=a.p.subGridBeforeExpand.call(a,s+"_"+l,l);if(o===false)return false;b(d).after("<tr role='row' class='ui-subgrid'>"+u+"<td class='ui-widget-content subgrid-cell'><span class='ui-icon "+a.p.subGridOptions.openicon+"'></span></td><td colspan='"+
parseInt(a.p.colNames.length-1-v,10)+"' class='ui-widget-content subgrid-data'><div id="+s+"_"+l+" class='tablediv'></div></td></tr>");b.isFunction(a.p.subGridRowExpanded)?a.p.subGridRowExpanded.call(a,s+"_"+l,l):w(d)}else b(p).show();b(this).html("<a href='javascript:void(0);'><span class='ui-icon "+a.p.subGridOptions.minusicon+"'></span></a>").removeClass("sgcollapsed").addClass("sgexpanded");a.p.subGridOptions.selectOnExpand&&b(a).jqGrid("setSelection",l)}else if(b(this).hasClass("sgexpanded")){o=
true;if(b.isFunction(a.p.subGridRowColapsed)){l=d.id;o=a.p.subGridRowColapsed.call(a,s+"_"+l,l)}if(o===false)return false;if(a.p.subGridOptions.reloadOnExpand===true)b(p).remove(".ui-subgrid");else b(p).hasClass("ui-subgrid")&&b(p).hide();b(this).html("<a href='javascript:void(0);'><span class='ui-icon "+a.p.subGridOptions.plusicon+"'></span></a>").removeClass("sgexpanded").addClass("sgcollapsed")}return false});a.p.subGridOptions.expandOnLoad===true&&setTimeout(function(){b(d.cells[f]).trigger("click")},
i*a.p.subGridOptions.delayOnLoad)}});a.subGridXml=function(i,d){q(i,d)};a.subGridJson=function(i,d){r(i,d)}}})},expandSubGridRow:function(f){return this.each(function(){if(this.grid||f)if(this.p.subGrid===true){var a=b(this).jqGrid("getInd",f,true);if(a)(a=b("td.sgcollapsed",a)[0])&&b(a).trigger("click")}})},collapseSubGridRow:function(f){return this.each(function(){if(this.grid||f)if(this.p.subGrid===true){var a=b(this).jqGrid("getInd",f,true);if(a)(a=b("td.sgexpanded",a)[0])&&b(a).trigger("click")}})},
toggleSubGridRow:function(f){return this.each(function(){if(this.grid||f)if(this.p.subGrid===true){var a=b(this).jqGrid("getInd",f,true);if(a){var j=b("td.sgcollapsed",a)[0];if(j)b(j).trigger("click");else(j=b("td.sgexpanded",a)[0])&&b(j).trigger("click")}}})}})})(jQuery);
(function(e){e.jgrid.extend({groupingSetup:function(){return this.each(function(){var a=this.p.groupingView;if(a!==null&&(typeof a==="object"||e.isFunction(a)))if(a.groupField.length){if(typeof a.visibiltyOnNextGrouping=="undefined")a.visibiltyOnNextGrouping=[];for(var b=0;b<a.groupField.length;b++){a.groupOrder[b]||(a.groupOrder[b]="asc");a.groupText[b]||(a.groupText[b]="{0}");if(typeof a.groupColumnShow[b]!="boolean")a.groupColumnShow[b]=true;if(typeof a.groupSummary[b]!="boolean")a.groupSummary[b]=
false;if(a.groupColumnShow[b]===true){a.visibiltyOnNextGrouping[b]=true;e(this).jqGrid("showCol",a.groupField[b])}else{a.visibiltyOnNextGrouping[b]=e("#"+this.p.id+"_"+a.groupField[b]).is(":visible");e(this).jqGrid("hideCol",a.groupField[b])}a.sortitems[b]=[];a.sortnames[b]=[];a.summaryval[b]=[];if(a.groupSummary[b]){a.summary[b]=[];for(var c=this.p.colModel,d=0,g=c.length;d<g;d++)c[d].summaryType&&a.summary[b].push({nm:c[d].name,st:c[d].summaryType,v:""})}}this.p.scroll=false;this.p.rownumbers=false;
this.p.subGrid=false;this.p.treeGrid=false;this.p.gridview=true}else this.p.grouping=false;else this.p.grouping=false})},groupingPrepare:function(a,b,c,d){this.each(function(){b[0]+="";var g=b[0].toString().split(" ").join(""),h=this.p.groupingView,f=this;if(c.hasOwnProperty(g))c[g].push(a);else{c[g]=[];c[g].push(a);h.sortitems[0].push(g);h.sortnames[0].push(e.trim(b[0].toString()));h.summaryval[0][g]=e.extend(true,[],h.summary[0])}h.groupSummary[0]&&e.each(h.summaryval[0][g],function(){this.v=e.isFunction(this.st)?
this.st.call(f,this.v,this.nm,d):e(f).jqGrid("groupingCalculations."+this.st,this.v,this.nm,d)})});return c},groupingToggle:function(a){this.each(function(){var b=this.p.groupingView,c=a.lastIndexOf("_"),d=a.substring(0,c+1);c=parseInt(a.substring(c+1),10)+1;var g=b.minusicon,h=b.plusicon,f=e("#"+a)[0].nextSibling,k=e("#"+a+" span.tree-wrap-"+this.p.direction),l=false;if(k.hasClass(g)){if(b.showSummaryOnHide&&b.groupSummary[0]){if(f)for(;f;){if(e(f).hasClass("jqfoot"))break;e(f).hide();f=f.nextSibling}}else if(f)for(;f;){if(e(f).attr("id")==
d+String(c))break;e(f).hide();f=f.nextSibling}k.removeClass(g).addClass(h);l=true}else{if(f)for(;f;){if(e(f).attr("id")==d+String(c))break;e(f).show();f=f.nextSibling}k.removeClass(h).addClass(g);l=false}e.isFunction(this.p.onClickGroup)&&this.p.onClickGroup.call(this,a,l)});return false},groupingRender:function(a,b){return this.each(function(){var c=this,d=c.p.groupingView,g="",h="",f,k="",l,r,m;if(!d.groupDataSorted){d.sortitems[0].sort();d.sortnames[0].sort();if(d.groupOrder[0].toLowerCase()==
"desc"){d.sortitems[0].reverse();d.sortnames[0].reverse()}}k=d.groupCollapse?d.plusicon:d.minusicon;k+=" tree-wrap-"+c.p.direction;for(m=0;m<b;){if(c.p.colModel[m].name==d.groupField[0]){r=m;break}m++}e.each(d.sortitems[0],function(o,n){f=c.p.id+"ghead_"+o;h="<span style='cursor:pointer;' class='ui-icon "+k+"' onclick=\"jQuery('#"+c.p.id+"').jqGrid('groupingToggle','"+f+"');return false;\"></span>";try{l=c.formatter(f,d.sortnames[0][o],r,d.sortitems[0])}catch(v){l=d.sortnames[0][o]}g+='<tr id="'+
f+'" role="row" class= "ui-widget-content jqgroup ui-row-'+c.p.direction+'"><td colspan="'+b+'">'+h+e.jgrid.format(d.groupText[0],l,a[n].length)+"</td></tr>";for(var i=0;i<a[n].length;i++)g+=a[n][i].join("");if(d.groupSummary[0]){i="";if(d.groupCollapse&&!d.showSummaryOnHide)i=' style="display:none;"';g+="<tr"+i+' role="row" class="ui-widget-content jqfoot ui-row-'+c.p.direction+'">';i=d.summaryval[0][n];for(var p=c.p.colModel,q,s=a[n].length,j=0;j<b;j++){var t="<td "+c.formatCol(j,1,"")+">&#160;</td>",
u="{0}";e.each(i,function(){if(this.nm==p[j].name){if(p[j].summaryTpl)u=p[j].summaryTpl;if(this.st=="avg")if(this.v&&s>0)this.v/=s;try{q=c.formatter("",this.v,j,this)}catch(w){q=this.v}t="<td "+c.formatCol(j,1,"")+">"+e.jgrid.format(u,q)+"</td>";return false}});g+=t}g+="</tr>"}});e("#"+c.p.id+" tbody:first").append(g);g=null})},groupingGroupBy:function(a,b){return this.each(function(){if(typeof a=="string")a=[a];var c=this.p.groupingView;this.p.grouping=true;for(var d=0;d<c.groupField.length;d++)!c.groupColumnShow[d]&&
c.visibiltyOnNextGrouping[d]&&e(this).jqGrid("showCol",c.groupField[d]);for(d=0;d<a.length;d++)c.visibiltyOnNextGrouping[d]=e("#"+this.p.id+"_"+a[d]).is(":visible");this.p.groupingView=e.extend(this.p.groupingView,b||{});c.groupField=a;e(this).trigger("reloadGrid")})},groupingRemove:function(a){return this.each(function(){if(typeof a=="undefined")a=true;this.p.grouping=false;if(a===true){for(var b=this.p.groupingView,c=0;c<b.groupField.length;c++)!b.groupColumnShow[c]&&b.visibiltyOnNextGrouping[c]&&
e(this).jqGrid("showCol",b.groupField);e("tr.jqgroup, tr.jqfoot","#"+this.p.id+" tbody:first").remove();e("tr.jqgrow:hidden","#"+this.p.id+" tbody:first").show()}else e(this).trigger("reloadGrid")})},groupingCalculations:{sum:function(a,b,c){return parseFloat(a||0)+parseFloat(c[b]||0)},min:function(a,b,c){if(a==="")return parseFloat(c[b]||0);return Math.min(parseFloat(a),parseFloat(c[b]||0))},max:function(a,b,c){if(a==="")return parseFloat(c[b]||0);return Math.max(parseFloat(a),parseFloat(c[b]||0))},
count:function(a,b,c){if(a==="")a=0;return c.hasOwnProperty(b)?a+1:0},avg:function(a,b,c){return parseFloat(a||0)+parseFloat(c[b]||0)}}})})(jQuery);
(function(d){d.jgrid.extend({setTreeNode:function(b,c){return this.each(function(){var a=this;if(a.grid&&a.p.treeGrid)for(var e=a.p.expColInd,g=a.p.treeReader.expanded_field,h=a.p.treeReader.leaf_field,f=a.p.treeReader.level_field,k=a.p.treeReader.icon_field,i=a.p.treeReader.loaded,j,o,n,l;b<c;){l=a.p.data[a.p._index[a.rows[b].id]];if(a.p.treeGridModel=="nested")if(!l[h]){j=parseInt(l[a.p.treeReader.left_field],10);o=parseInt(l[a.p.treeReader.right_field],10);l[h]=o===j+1?"true":"false";a.rows[b].cells[a.p._treeleafpos].innerHTML=
l[h]}j=parseInt(l[f],10);if(a.p.tree_root_level===0){n=j+1;o=j}else{n=j;o=j-1}n="<div class='tree-wrap tree-wrap-"+a.p.direction+"' style='width:"+n*18+"px;'>";n+="<div style='"+(a.p.direction=="rtl"?"right:":"left:")+o*18+"px;' class='ui-icon ";if(l[i]!==undefined)l[i]=l[i]=="true"||l[i]===true?true:false;if(l[h]=="true"||l[h]===true){n+=(l[k]!==undefined&&l[k]!==""?l[k]:a.p.treeIcons.leaf)+" tree-leaf treeclick'";l[h]=true;o="leaf"}else{l[h]=false;o=""}l[g]=(l[g]=="true"||l[g]===true?true:false)&&
l[i];n+=l[g]===true?a.p.treeIcons.minus+" tree-minus treeclick'":a.p.treeIcons.plus+" tree-plus treeclick'";n+="</div></div>";d(a.rows[b].cells[e]).wrapInner("<span class='cell-wrapper"+o+"'></span>").prepend(n);if(j!==parseInt(a.p.tree_root_level,10))(l=(l=d(a).jqGrid("getNodeParent",l))&&l.hasOwnProperty(g)?l[g]:true)||d(a.rows[b]).css("display","none");d(a.rows[b].cells[e]).find("div.treeclick").bind("click",function(m){m=d(m.target||m.srcElement,a.rows).closest("tr.jqgrow")[0].id;m=a.p._index[m];
if(!a.p.data[m][h])if(a.p.data[m][g]){d(a).jqGrid("collapseRow",a.p.data[m]);d(a).jqGrid("collapseNode",a.p.data[m])}else{d(a).jqGrid("expandRow",a.p.data[m]);d(a).jqGrid("expandNode",a.p.data[m])}return false});a.p.ExpandColClick===true&&d(a.rows[b].cells[e]).find("span.cell-wrapper").css("cursor","pointer").bind("click",function(m){m=d(m.target||m.srcElement,a.rows).closest("tr.jqgrow")[0].id;var r=a.p._index[m];if(!a.p.data[r][h])if(a.p.data[r][g]){d(a).jqGrid("collapseRow",a.p.data[r]);d(a).jqGrid("collapseNode",
a.p.data[r])}else{d(a).jqGrid("expandRow",a.p.data[r]);d(a).jqGrid("expandNode",a.p.data[r])}d(a).jqGrid("setSelection",m);return false});b++}})},setTreeGrid:function(){return this.each(function(){var b=this,c=0,a=false,e,g,h=[];if(b.p.treeGrid){b.p.treedatatype||d.extend(b.p,{treedatatype:b.p.datatype});b.p.subGrid=false;b.p.altRows=false;b.p.pgbuttons=false;b.p.pginput=false;b.p.gridview=true;b.p.multiselect=false;b.p.rowList=[];b.p.expColInd=0;b.p.treeIcons=d.extend({plus:"ui-icon-triangle-1-"+
(b.p.direction=="rtl"?"w":"e"),minus:"ui-icon-triangle-1-s",leaf:"ui-icon-radio-off"},b.p.treeIcons||{});if(b.p.treeGridModel=="nested")b.p.treeReader=d.extend({level_field:"level",left_field:"lft",right_field:"rgt",leaf_field:"isLeaf",expanded_field:"expanded",loaded:"loaded",icon_field:"icon"},b.p.treeReader);else if(b.p.treeGridModel=="adjacency")b.p.treeReader=d.extend({level_field:"level",parent_id_field:"parent",leaf_field:"isLeaf",expanded_field:"expanded",loaded:"loaded",icon_field:"icon"},
b.p.treeReader);for(g in b.p.colModel)if(b.p.colModel.hasOwnProperty(g)){e=b.p.colModel[g].name;if(e==b.p.ExpandColumn&&!a){a=true;b.p.expColInd=c}c++;for(var f in b.p.treeReader)b.p.treeReader[f]==e&&h.push(e)}d.each(b.p.treeReader,function(k,i){if(i&&d.inArray(i,h)===-1){if(k==="leaf_field")b.p._treeleafpos=c;c++;b.p.colNames.push(i);b.p.colModel.push({name:i,width:1,hidden:true,sortable:false,resizable:false,hidedlg:true,editable:true,search:false})}})}})},expandRow:function(b){this.each(function(){var c=
this;if(c.grid&&c.p.treeGrid){var a=d(c).jqGrid("getNodeChildren",b),e=c.p.treeReader.expanded_field;d(a).each(function(){var g=d.jgrid.getAccessor(this,c.p.localReader.id);d("#"+g,c.grid.bDiv).css("display","");this[e]&&d(c).jqGrid("expandRow",this)})}})},collapseRow:function(b){this.each(function(){var c=this;if(c.grid&&c.p.treeGrid){var a=d(c).jqGrid("getNodeChildren",b),e=c.p.treeReader.expanded_field;d(a).each(function(){var g=d.jgrid.getAccessor(this,c.p.localReader.id);d("#"+g,c.grid.bDiv).css("display",
"none");this[e]&&d(c).jqGrid("collapseRow",this)})}})},getRootNodes:function(){var b=[];this.each(function(){var c=this;if(c.grid&&c.p.treeGrid)switch(c.p.treeGridModel){case "nested":var a=c.p.treeReader.level_field;d(c.p.data).each(function(){parseInt(this[a],10)===parseInt(c.p.tree_root_level,10)&&b.push(this)});break;case "adjacency":var e=c.p.treeReader.parent_id_field;d(c.p.data).each(function(){if(this[e]===null||String(this[e]).toLowerCase()=="null")b.push(this)})}});return b},getNodeDepth:function(b){var c=
null;this.each(function(){if(this.grid&&this.p.treeGrid)switch(this.p.treeGridModel){case "nested":c=parseInt(b[this.p.treeReader.level_field],10)-parseInt(this.p.tree_root_level,10);break;case "adjacency":c=d(this).jqGrid("getNodeAncestors",b).length}});return c},getNodeParent:function(b){var c=null;this.each(function(){if(this.grid&&this.p.treeGrid)switch(this.p.treeGridModel){case "nested":var a=this.p.treeReader.left_field,e=this.p.treeReader.right_field,g=this.p.treeReader.level_field,h=parseInt(b[a],
10),f=parseInt(b[e],10),k=parseInt(b[g],10);d(this.p.data).each(function(){if(parseInt(this[g],10)===k-1&&parseInt(this[a],10)<h&&parseInt(this[e],10)>f){c=this;return false}});break;case "adjacency":var i=this.p.treeReader.parent_id_field,j=this.p.localReader.id;d(this.p.data).each(function(){if(this[j]==b[i]){c=this;return false}})}});return c},getNodeChildren:function(b){var c=[];this.each(function(){if(this.grid&&this.p.treeGrid)switch(this.p.treeGridModel){case "nested":var a=this.p.treeReader.left_field,
e=this.p.treeReader.right_field,g=this.p.treeReader.level_field,h=parseInt(b[a],10),f=parseInt(b[e],10),k=parseInt(b[g],10);d(this.p.data).each(function(){parseInt(this[g],10)===k+1&&parseInt(this[a],10)>h&&parseInt(this[e],10)<f&&c.push(this)});break;case "adjacency":var i=this.p.treeReader.parent_id_field,j=this.p.localReader.id;d(this.p.data).each(function(){this[i]==b[j]&&c.push(this)})}});return c},getFullTreeNode:function(b){var c=[];this.each(function(){var a;if(this.grid&&this.p.treeGrid)switch(this.p.treeGridModel){case "nested":var e=
this.p.treeReader.left_field,g=this.p.treeReader.right_field,h=this.p.treeReader.level_field,f=parseInt(b[e],10),k=parseInt(b[g],10),i=parseInt(b[h],10);d(this.p.data).each(function(){parseInt(this[h],10)>=i&&parseInt(this[e],10)>=f&&parseInt(this[e],10)<=k&&c.push(this)});break;case "adjacency":if(b){c.push(b);var j=this.p.treeReader.parent_id_field,o=this.p.localReader.id;d(this.p.data).each(function(n){a=c.length;for(n=0;n<a;n++)if(c[n][o]==this[j]){c.push(this);break}})}}});return c},getNodeAncestors:function(b){var c=
[];this.each(function(){if(this.grid&&this.p.treeGrid)for(var a=d(this).jqGrid("getNodeParent",b);a;){c.push(a);a=d(this).jqGrid("getNodeParent",a)}});return c},isVisibleNode:function(b){var c=true;this.each(function(){if(this.grid&&this.p.treeGrid){var a=d(this).jqGrid("getNodeAncestors",b),e=this.p.treeReader.expanded_field;d(a).each(function(){c=c&&this[e];if(!c)return false})}});return c},isNodeLoaded:function(b){var c;this.each(function(){if(this.grid&&this.p.treeGrid){var a=this.p.treeReader.leaf_field;
c=b!==undefined?b.loaded!==undefined?b.loaded:b[a]||d(this).jqGrid("getNodeChildren",b).length>0?true:false:false}});return c},expandNode:function(b){return this.each(function(){if(this.grid&&this.p.treeGrid){var c=this.p.treeReader.expanded_field,a=this.p.treeReader.parent_id_field,e=this.p.treeReader.loaded,g=this.p.treeReader.level_field,h=this.p.treeReader.left_field,f=this.p.treeReader.right_field;if(!b[c]){var k=d.jgrid.getAccessor(b,this.p.localReader.id),i=d("#"+k,this.grid.bDiv)[0],j=this.p._index[k];
if(d(this).jqGrid("isNodeLoaded",this.p.data[j])){b[c]=true;d("div.treeclick",i).removeClass(this.p.treeIcons.plus+" tree-plus").addClass(this.p.treeIcons.minus+" tree-minus")}else{b[c]=true;d("div.treeclick",i).removeClass(this.p.treeIcons.plus+" tree-plus").addClass(this.p.treeIcons.minus+" tree-minus");this.p.treeANode=i.rowIndex;this.p.datatype=this.p.treedatatype;this.p.treeGridModel=="nested"?d(this).jqGrid("setGridParam",{postData:{nodeid:k,n_left:b[h],n_right:b[f],n_level:b[g]}}):d(this).jqGrid("setGridParam",
{postData:{nodeid:k,parentid:b[a],n_level:b[g]}});d(this).trigger("reloadGrid");b[e]=true;this.p.treeGridModel=="nested"?d(this).jqGrid("setGridParam",{postData:{nodeid:"",n_left:"",n_right:"",n_level:""}}):d(this).jqGrid("setGridParam",{postData:{nodeid:"",parentid:"",n_level:""}})}}}})},collapseNode:function(b){return this.each(function(){if(this.grid&&this.p.treeGrid)if(b.expanded){b.expanded=false;var c=d.jgrid.getAccessor(b,this.p.localReader.id);c=d("#"+c,this.grid.bDiv)[0];d("div.treeclick",
c).removeClass(this.p.treeIcons.minus+" tree-minus").addClass(this.p.treeIcons.plus+" tree-plus")}})},SortTree:function(b,c,a,e){return this.each(function(){if(this.grid&&this.p.treeGrid){var g,h,f,k=[],i=this,j;g=d(this).jqGrid("getRootNodes");g=d.jgrid.from(g);g.orderBy(b,c,a,e);j=g.select();g=0;for(h=j.length;g<h;g++){f=j[g];k.push(f);d(this).jqGrid("collectChildrenSortTree",k,f,b,c,a,e)}d.each(k,function(o){var n=d.jgrid.getAccessor(this,i.p.localReader.id);d("#"+i.p.id+" tbody tr:eq("+o+")").after(d("tr#"+
n,i.grid.bDiv))});k=j=g=null}})},collectChildrenSortTree:function(b,c,a,e,g,h){return this.each(function(){if(this.grid&&this.p.treeGrid){var f,k,i,j;f=d(this).jqGrid("getNodeChildren",c);f=d.jgrid.from(f);f.orderBy(a,e,g,h);j=f.select();f=0;for(k=j.length;f<k;f++){i=j[f];b.push(i);d(this).jqGrid("collectChildrenSortTree",b,i,a,e,g,h)}}})},setTreeRow:function(b,c){var a=false;this.each(function(){if(this.grid&&this.p.treeGrid)a=d(this).jqGrid("setRowData",b,c)});return a},delTreeNode:function(b){return this.each(function(){var c=
this.p.localReader.id,a=this.p.treeReader.left_field,e=this.p.treeReader.right_field,g,h,f;if(this.grid&&this.p.treeGrid){var k=this.p._index[b];if(k!==undefined){g=parseInt(this.p.data[k][e],10);h=g-parseInt(this.p.data[k][a],10)+1;k=d(this).jqGrid("getFullTreeNode",this.p.data[k]);if(k.length>0)for(var i=0;i<k.length;i++)d(this).jqGrid("delRowData",k[i][c]);if(this.p.treeGridModel==="nested"){c=d.jgrid.from(this.p.data).greater(a,g,{stype:"integer"}).select();if(c.length)for(f in c)c[f][a]=parseInt(c[f][a],
10)-h;c=d.jgrid.from(this.p.data).greater(e,g,{stype:"integer"}).select();if(c.length)for(f in c)c[f][e]=parseInt(c[f][e],10)-h}}}})},addChildNode:function(b,c,a){var e=this[0];if(a){var g=e.p.treeReader.expanded_field,h=e.p.treeReader.leaf_field,f=e.p.treeReader.level_field,k=e.p.treeReader.parent_id_field,i=e.p.treeReader.left_field,j=e.p.treeReader.right_field,o=e.p.treeReader.loaded,n,l,m,r,p;n=0;var s=c,t;if(!b){p=e.p.data.length-1;if(p>=0)for(;p>=0;){n=Math.max(n,parseInt(e.p.data[p][e.p.localReader.id],
10));p--}b=n+1}var u=d(e).jqGrid("getInd",c);t=false;if(c===undefined||c===null||c===""){s=c=null;n="last";r=e.p.tree_root_level;p=e.p.data.length+1}else{n="after";l=e.p._index[c];m=e.p.data[l];c=m[e.p.localReader.id];r=parseInt(m[f],10)+1;p=d(e).jqGrid("getFullTreeNode",m);if(p.length){s=p=p[p.length-1][e.p.localReader.id];p=d(e).jqGrid("getInd",s)+1}else p=d(e).jqGrid("getInd",c)+1;if(m[h]){t=true;m[g]=true;d(e.rows[u]).find("span.cell-wrapperleaf").removeClass("cell-wrapperleaf").addClass("cell-wrapper").end().find("div.tree-leaf").removeClass(e.p.treeIcons.leaf+
" tree-leaf").addClass(e.p.treeIcons.minus+" tree-minus");e.p.data[l][h]=false;m[o]=true}}l=p+1;a[g]=false;a[o]=true;a[f]=r;a[h]=true;if(e.p.treeGridModel==="adjacency")a[k]=c;if(e.p.treeGridModel==="nested"){var q;if(c!==null){h=parseInt(m[j],10);f=d.jgrid.from(e.p.data);f=f.greaterOrEquals(j,h,{stype:"integer"});f=f.select();if(f.length)for(q in f){f[q][i]=f[q][i]>h?parseInt(f[q][i],10)+2:f[q][i];f[q][j]=f[q][j]>=h?parseInt(f[q][j],10)+2:f[q][j]}a[i]=h;a[j]=h+1}else{h=parseInt(d(e).jqGrid("getCol",
j,false,"max"),10);f=d.jgrid.from(e.p.data).greater(i,h,{stype:"integer"}).select();if(f.length)for(q in f)f[q][i]=parseInt(f[q][i],10)+2;f=d.jgrid.from(e.p.data).greater(j,h,{stype:"integer"}).select();if(f.length)for(q in f)f[q][j]=parseInt(f[q][j],10)+2;a[i]=h+1;a[j]=h+2}}if(c===null||d(e).jqGrid("isNodeLoaded",m)||t){d(e).jqGrid("addRowData",b,a,n,s);d(e).jqGrid("setTreeNode",p,l)}m&&!m[g]&&d(e.rows[u]).find("div.treeclick").click()}}})})(jQuery);
(function(b){b.jgrid.extend({jqGridImport:function(a){a=b.extend({imptype:"xml",impstring:"",impurl:"",mtype:"GET",impData:{},xmlGrid:{config:"roots>grid",data:"roots>rows"},jsonGrid:{config:"grid",data:"data"},ajaxOptions:{}},a||{});return this.each(function(){var d=this,c=function(f,g){var e=b(g.xmlGrid.config,f)[0],h=b(g.xmlGrid.data,f)[0],i;if(xmlJsonClass.xml2json&&b.jgrid.parse){e=xmlJsonClass.xml2json(e," ");e=b.jgrid.parse(e);for(var l in e)if(e.hasOwnProperty(l))i=e[l];if(h){h=e.grid.datatype;
e.grid.datatype="xmlstring";e.grid.datastr=f;b(d).jqGrid(i).jqGrid("setGridParam",{datatype:h})}else b(d).jqGrid(i)}else alert("xml2json or parse are not present")},j=function(f,g){if(f&&typeof f=="string"){var e=b.jgrid.parse(f),h=e[g.jsonGrid.config];if(e=e[g.jsonGrid.data]){var i=h.datatype;h.datatype="jsonstring";h.datastr=e;b(d).jqGrid(h).jqGrid("setGridParam",{datatype:i})}else b(d).jqGrid(h)}};switch(a.imptype){case "xml":b.ajax(b.extend({url:a.impurl,type:a.mtype,data:a.impData,dataType:"xml",
complete:function(f,g){if(g=="success"){c(f.responseXML,a);b.isFunction(a.importComplete)&&a.importComplete(f)}}},a.ajaxOptions));break;case "xmlstring":if(a.impstring&&typeof a.impstring=="string"){var k=b.jgrid.stringToDoc(a.impstring);if(k){c(k,a);b.isFunction(a.importComplete)&&a.importComplete(k);a.impstring=null}k=null}break;case "json":b.ajax(b.extend({url:a.impurl,type:a.mtype,data:a.impData,dataType:"json",complete:function(f,g){if(g=="success"){j(f.responseText,a);b.isFunction(a.importComplete)&&
a.importComplete(f)}}},a.ajaxOptions));break;case "jsonstring":if(a.impstring&&typeof a.impstring=="string"){j(a.impstring,a);b.isFunction(a.importComplete)&&a.importComplete(a.impstring);a.impstring=null}}})},jqGridExport:function(a){a=b.extend({exptype:"xmlstring",root:"grid",ident:"\t"},a||{});var d=null;this.each(function(){if(this.grid){var c=b.extend({},b(this).jqGrid("getGridParam"));if(c.rownumbers){c.colNames.splice(0,1);c.colModel.splice(0,1)}if(c.multiselect){c.colNames.splice(0,1);c.colModel.splice(0,
1)}if(c.subGrid){c.colNames.splice(0,1);c.colModel.splice(0,1)}c.knv=null;if(c.treeGrid)for(var j in c.treeReader)if(c.treeReader.hasOwnProperty(j)){c.colNames.splice(c.colNames.length-1);c.colModel.splice(c.colModel.length-1)}switch(a.exptype){case "xmlstring":d="<"+a.root+">"+xmlJsonClass.json2xml(c,a.ident)+"</"+a.root+">";break;case "jsonstring":d="{"+xmlJsonClass.toJson(c,a.root,a.ident)+"}";if(c.postData.filters!==undefined){d=d.replace(/filters":"/,'filters":');d=d.replace(/}]}"/,"}]}")}}}});
return d},excelExport:function(a){a=b.extend({exptype:"remote",url:null,oper:"oper",tag:"excel",exportOptions:{}},a||{});return this.each(function(){if(this.grid){var d;if(a.exptype=="remote"){d=b.extend({},this.p.postData);d[a.oper]=a.tag;d=jQuery.param(d);d=a.url.indexOf("?")!=-1?a.url+"&"+d:a.url+"?"+d;window.location=d}}})}})})(jQuery);
var xmlJsonClass={xml2json:function(a,b){if(a.nodeType===9)a=a.documentElement;var g=this.toJson(this.toObj(this.removeWhite(a)),a.nodeName,"\t");return"{\n"+b+(b?g.replace(/\t/g,b):g.replace(/\t|\n/g,""))+"\n}"},json2xml:function(a,b){var g=function(d,c,i){var h="",k,j;if(d instanceof Array)if(d.length===0)h+=i+"<"+c+">__EMPTY_ARRAY_</"+c+">\n";else{k=0;for(j=d.length;k<j;k+=1){var l=i+g(d[k],c,i+"\t")+"\n";h+=l}}else if(typeof d==="object"){k=false;h+=i+"<"+c;for(j in d)if(d.hasOwnProperty(j))if(j.charAt(0)===
"@")h+=" "+j.substr(1)+'="'+d[j].toString()+'"';else k=true;h+=k?">":"/>";if(k){for(j in d)if(d.hasOwnProperty(j))if(j==="#text")h+=d[j];else if(j==="#cdata")h+="<![CDATA["+d[j]+"]]\>";else if(j.charAt(0)!=="@")h+=g(d[j],j,i+"\t");h+=(h.charAt(h.length-1)==="\n"?i:"")+"</"+c+">"}}else h+=typeof d==="function"?i+"<"+c+"><![CDATA["+d+"]]\></"+c+">":d.toString()==='""'||d.toString().length===0?i+"<"+c+">__EMPTY_STRING_</"+c+">":i+"<"+c+">"+d.toString()+"</"+c+">";return h},e="",f;for(f in a)if(a.hasOwnProperty(f))e+=
g(a[f],f,"");return b?e.replace(/\t/g,b):e.replace(/\t|\n/g,"")},toObj:function(a){var b={},g=/function/i;if(a.nodeType===1){if(a.attributes.length){var e;for(e=0;e<a.attributes.length;e+=1)b["@"+a.attributes[e].nodeName]=(a.attributes[e].nodeValue||"").toString()}if(a.firstChild){var f=e=0,d=false,c;for(c=a.firstChild;c;c=c.nextSibling)if(c.nodeType===1)d=true;else if(c.nodeType===3&&c.nodeValue.match(/[^ \f\n\r\t\v]/))e+=1;else if(c.nodeType===4)f+=1;if(d)if(e<2&&f<2){this.removeWhite(a);for(c=
a.firstChild;c;c=c.nextSibling)if(c.nodeType===3)b["#text"]=this.escape(c.nodeValue);else if(c.nodeType===4)if(g.test(c.nodeValue))b[c.nodeName]=[b[c.nodeName],c.nodeValue];else b["#cdata"]=this.escape(c.nodeValue);else if(b[c.nodeName])if(b[c.nodeName]instanceof Array)b[c.nodeName][b[c.nodeName].length]=this.toObj(c);else b[c.nodeName]=[b[c.nodeName],this.toObj(c)];else b[c.nodeName]=this.toObj(c)}else if(a.attributes.length)b["#text"]=this.escape(this.innerXml(a));else b=this.escape(this.innerXml(a));
else if(e)if(a.attributes.length)b["#text"]=this.escape(this.innerXml(a));else{b=this.escape(this.innerXml(a));if(b==="__EMPTY_ARRAY_")b="[]";else if(b==="__EMPTY_STRING_")b=""}else if(f)if(f>1)b=this.escape(this.innerXml(a));else for(c=a.firstChild;c;c=c.nextSibling)if(g.test(a.firstChild.nodeValue)){b=a.firstChild.nodeValue;break}else b["#cdata"]=this.escape(c.nodeValue)}if(!a.attributes.length&&!a.firstChild)b=null}else if(a.nodeType===9)b=this.toObj(a.documentElement);else alert("unhandled node type: "+
a.nodeType);return b},toJson:function(a,b,g,e){if(e===undefined)e=true;var f=b?'"'+b+'"':"",d="\t",c="\n";if(!e)c=d="";if(a==="[]")f+=b?":[]":"[]";else if(a instanceof Array){var i,h,k=[];h=0;for(i=a.length;h<i;h+=1)k[h]=this.toJson(a[h],"",g+d,e);f+=(b?":[":"[")+(k.length>1?c+g+d+k.join(","+c+g+d)+c+g:k.join(""))+"]"}else if(a===null)f+=(b&&":")+"null";else if(typeof a==="object"){i=[];for(h in a)if(a.hasOwnProperty(h))i[i.length]=this.toJson(a[h],h,g+d,e);f+=(b?":{":"{")+(i.length>1?c+g+d+i.join(","+
c+g+d)+c+g:i.join(""))+"}"}else f+=typeof a==="string"?(b&&":")+'"'+a.replace(/\\/g,"\\\\").replace(/\"/g,'\\"')+'"':(b&&":")+'"'+a.toString()+'"';return f},innerXml:function(a){var b="";if("innerHTML"in a)b=a.innerHTML;else{var g=function(e){var f="",d;if(e.nodeType===1){f+="<"+e.nodeName;for(d=0;d<e.attributes.length;d+=1)f+=" "+e.attributes[d].nodeName+'="'+(e.attributes[d].nodeValue||"").toString()+'"';if(e.firstChild){f+=">";for(d=e.firstChild;d;d=d.nextSibling)f+=g(d);f+="</"+e.nodeName+">"}else f+=
"/>"}else if(e.nodeType===3)f+=e.nodeValue;else if(e.nodeType===4)f+="<![CDATA["+e.nodeValue+"]]\>";return f};for(a=a.firstChild;a;a=a.nextSibling)b+=g(a)}return b},escape:function(a){return a.replace(/[\\]/g,"\\\\").replace(/[\"]/g,'\\"').replace(/[\n]/g,"\\n").replace(/[\r]/g,"\\r")},removeWhite:function(a){a.normalize();var b;for(b=a.firstChild;b;)if(b.nodeType===3)if(b.nodeValue.match(/[^ \f\n\r\t\v]/))b=b.nextSibling;else{var g=b.nextSibling;a.removeChild(b);b=g}else{b.nodeType===1&&this.removeWhite(b);
b=b.nextSibling}return a}};
function tableToGrid(n,o){jQuery(n).each(function(){if(!this.grid){jQuery(this).width("99%");var a=jQuery(this).width(),d=jQuery("input[type=checkbox]:first",jQuery(this)),b=jQuery("input[type=radio]:first",jQuery(this));d=d.length>0;b=!d&&b.length>0;var l=d||b,c=[],g=[];jQuery("th",jQuery(this)).each(function(){if(c.length===0&&l){c.push({name:"__selection__",index:"__selection__",width:0,hidden:true});g.push("__selection__")}else{c.push({name:jQuery(this).attr("id")||jQuery.trim(jQuery.jgrid.stripHtml(jQuery(this).html())).split(" ").join("_"),
index:jQuery(this).attr("id")||jQuery.trim(jQuery.jgrid.stripHtml(jQuery(this).html())).split(" ").join("_"),width:jQuery(this).width()||150});g.push(jQuery(this).html())}});var f=[],h=[],i=[];jQuery("tbody > tr",jQuery(this)).each(function(){var j={},e=0;jQuery("td",jQuery(this)).each(function(){if(e===0&&l){var k=jQuery("input",jQuery(this)),m=k.attr("value");h.push(m||f.length);k.attr("checked")&&i.push(m);j[c[e].name]=k.attr("value")}else j[c[e].name]=jQuery(this).html();e++});e>0&&f.push(j)});
jQuery(this).empty();jQuery(this).addClass("scroll");jQuery(this).jqGrid(jQuery.extend({datatype:"local",width:a,colNames:g,colModel:c,multiselect:d},o||{}));for(a=0;a<f.length;a++){b=null;if(h.length>0)if((b=h[a])&&b.replace)b=encodeURIComponent(b).replace(/[.\-%]/g,"_");if(b===null)b=a+1;jQuery(this).jqGrid("addRowData",b,f[a])}for(a=0;a<i.length;a++)jQuery(this).jqGrid("setSelection",i[a])}})};
(function(a){if(a.browser.msie&&a.browser.version==8)a.expr[":"].hidden=function(b){return b.offsetWidth===0||b.offsetHeight===0||b.style.display=="none"};a.jgrid._multiselect=false;if(a.ui)if(a.ui.multiselect){if(a.ui.multiselect.prototype._setSelected){var q=a.ui.multiselect.prototype._setSelected;a.ui.multiselect.prototype._setSelected=function(b,g){var c=q.call(this,b,g);if(g&&this.selectedList){var f=this.element;this.selectedList.find("li").each(function(){a(this).data("optionLink")&&a(this).data("optionLink").remove().appendTo(f)})}return c}}if(a.ui.multiselect.prototype.destroy)a.ui.multiselect.prototype.destroy=
function(){this.element.show();this.container.remove();a.Widget===undefined?a.widget.prototype.destroy.apply(this,arguments):a.Widget.prototype.destroy.apply(this,arguments)};a.jgrid._multiselect=true}a.jgrid.extend({sortableColumns:function(b){return this.each(function(){function g(){c.p.disableClick=true}var c=this,f={tolerance:"pointer",axis:"x",scrollSensitivity:"1",items:">th:not(:has(#jqgh_cb,#jqgh_rn,#jqgh_subgrid),:hidden)",placeholder:{element:function(h){return a(document.createElement(h[0].nodeName)).addClass(h[0].className+
" ui-sortable-placeholder ui-state-highlight").removeClass("ui-sortable-helper")[0]},update:function(h,j){j.height(h.currentItem.innerHeight()-parseInt(h.currentItem.css("paddingTop")||0,10)-parseInt(h.currentItem.css("paddingBottom")||0,10));j.width(h.currentItem.innerWidth()-parseInt(h.currentItem.css("paddingLeft")||0,10)-parseInt(h.currentItem.css("paddingRight")||0,10))}},update:function(h,j){var i=a(j.item).parent();i=a(">th",i);var m={};a.each(c.p.colModel,function(d){m[this.name]=d});var k=
[];i.each(function(){var d=a(">div",this).get(0).id.replace(/^jqgh_/,"");d in m&&k.push(m[d])});a(c).jqGrid("remapColumns",k,true,true);a.isFunction(c.p.sortable.update)&&c.p.sortable.update(k);setTimeout(function(){c.p.disableClick=false},50)}};if(c.p.sortable.options)a.extend(f,c.p.sortable.options);else if(a.isFunction(c.p.sortable))c.p.sortable={update:c.p.sortable};if(f.start){var e=f.start;f.start=function(h,j){g();e.call(this,h,j)}}else f.start=g;if(c.p.sortable.exclude)f.items+=":not("+c.p.sortable.exclude+
")";b.sortable(f).data("sortable").floating=true})},columnChooser:function(b){function g(d,l){if(d)if(typeof d=="string")a.fn[d]&&a.fn[d].apply(l,a.makeArray(arguments).slice(2));else a.isFunction(d)&&d.apply(l,a.makeArray(arguments).slice(2))}var c=this;if(!a("#colchooser_"+c[0].p.id).length){var f=a('<div id="colchooser_'+c[0].p.id+'" style="position:relative;overflow:hidden"><div><select multiple="multiple"></select></div></div>'),e=a("select",f);b=a.extend({width:420,height:240,classname:null,
done:function(d){d&&c.jqGrid("remapColumns",d,true)},msel:"multiselect",dlog:"dialog",dlog_opts:function(d){var l={};l[d.bSubmit]=function(){d.apply_perm();d.cleanup(false)};l[d.bCancel]=function(){d.cleanup(true)};return{buttons:l,close:function(){d.cleanup(true)},modal:d.modal?d.modal:false,resizable:d.resizable?d.resizable:true,width:d.width+20}},apply_perm:function(){a("option",e).each(function(){this.selected?c.jqGrid("showCol",h[this.value].name):c.jqGrid("hideCol",h[this.value].name)});var d=
[];a("option[selected]",e).each(function(){d.push(parseInt(this.value,10))});a.each(d,function(){delete i[h[parseInt(this,10)].name]});a.each(i,function(){var l=parseInt(this,10);var p=d,o=l;if(o>=0){var n=p.slice(),r=n.splice(o,Math.max(p.length-o,o));if(o>p.length)o=p.length;n[o]=l;d=n.concat(r)}else d=void 0});b.done&&b.done.call(c,d)},cleanup:function(d){g(b.dlog,f,"destroy");g(b.msel,e,"destroy");f.remove();d&&b.done&&b.done.call(c)},msel_opts:{}},a.jgrid.col,b||{});if(a.ui)if(a.ui.multiselect)if(b.msel==
"multiselect"){if(!a.jgrid._multiselect){alert("Multiselect plugin loaded after jqGrid. Please load the plugin before the jqGrid!");return}b.msel_opts=a.extend(a.ui.multiselect.defaults,b.msel_opts)}b.caption&&f.attr("title",b.caption);if(b.classname){f.addClass(b.classname);e.addClass(b.classname)}if(b.width){a(">div",f).css({width:b.width,margin:"0 auto"});e.css("width",b.width)}if(b.height){a(">div",f).css("height",b.height);e.css("height",b.height-10)}var h=c.jqGrid("getGridParam","colModel"),
j=c.jqGrid("getGridParam","colNames"),i={},m=[];e.empty();a.each(h,function(d){i[this.name]=d;if(this.hidedlg)this.hidden||m.push(d);else e.append("<option value='"+d+"' "+(this.hidden?"":"selected='selected'")+">"+j[d]+"</option>")});var k=a.isFunction(b.dlog_opts)?b.dlog_opts.call(c,b):b.dlog_opts;g(b.dlog,f,k);k=a.isFunction(b.msel_opts)?b.msel_opts.call(c,b):b.msel_opts;g(b.msel,e,k)}},sortableRows:function(b){return this.each(function(){var g=this;if(g.grid)if(!g.p.treeGrid)if(a.fn.sortable){b=
a.extend({cursor:"move",axis:"y",items:".jqgrow"},b||{});if(b.start&&a.isFunction(b.start)){b._start_=b.start;delete b.start}else b._start_=false;if(b.update&&a.isFunction(b.update)){b._update_=b.update;delete b.update}else b._update_=false;b.start=function(c,f){a(f.item).css("border-width","0px");a("td",f.item).each(function(j){this.style.width=g.grid.cols[j].style.width});if(g.p.subGrid){var e=a(f.item).attr("id");try{a(g).jqGrid("collapseSubGridRow",e)}catch(h){}}b._start_&&b._start_.apply(this,
[c,f])};b.update=function(c,f){a(f.item).css("border-width","");g.p.rownumbers===true&&a("td.jqgrid-rownum",g.rows).each(function(e){a(this).html(e+1)});b._update_&&b._update_.apply(this,[c,f])};a("tbody:first",g).sortable(b);a("tbody:first",g).disableSelection()}})},gridDnD:function(b){return this.each(function(){function g(){var e=a.data(c,"dnd");a("tr.jqgrow:not(.ui-draggable)",c).draggable(a.isFunction(e.drag)?e.drag.call(a(c),e):e.drag)}var c=this;if(c.grid)if(!c.p.treeGrid)if(a.fn.draggable&&
a.fn.droppable){a("#jqgrid_dnd").html()===null&&a("body").append("<table id='jqgrid_dnd' class='ui-jqgrid-dnd'></table>");if(typeof b=="string"&&b=="updateDnD"&&c.p.jqgdnd===true)g();else{b=a.extend({drag:function(e){return a.extend({start:function(h,j){if(c.p.subGrid){var i=a(j.helper).attr("id");try{a(c).jqGrid("collapseSubGridRow",i)}catch(m){}}for(i=0;i<a.data(c,"dnd").connectWith.length;i++)a(a.data(c,"dnd").connectWith[i]).jqGrid("getGridParam","reccount")=="0"&&a(a.data(c,"dnd").connectWith[i]).jqGrid("addRowData",
"jqg_empty_row",{});j.helper.addClass("ui-state-highlight");a("td",j.helper).each(function(k){this.style.width=c.grid.headers[k].width+"px"});e.onstart&&a.isFunction(e.onstart)&&e.onstart.call(a(c),h,j)},stop:function(h,j){if(j.helper.dropped){var i=a(j.helper).attr("id");a(c).jqGrid("delRowData",i)}for(i=0;i<a.data(c,"dnd").connectWith.length;i++)a(a.data(c,"dnd").connectWith[i]).jqGrid("delRowData","jqg_empty_row");e.onstop&&a.isFunction(e.onstop)&&e.onstop.call(a(c),h,j)}},e.drag_opts||{})},drop:function(e){return a.extend({accept:function(h){if(!a(h).hasClass("jqgrow"))return h;
var j=a(h).closest("table.ui-jqgrid-btable");if(j.length>0&&a.data(j[0],"dnd")!==undefined){h=a.data(j[0],"dnd").connectWith;return a.inArray("#"+this.id,h)!=-1?true:false}return h},drop:function(h,j){if(a(j.draggable).hasClass("jqgrow")){var i=a(j.draggable).attr("id");i=j.draggable.parent().parent().jqGrid("getRowData",i);if(!e.dropbyname){var m=0,k={},d,l=a("#"+this.id).jqGrid("getGridParam","colModel");try{for(var p in i){if(i.hasOwnProperty(p)&&l[m]){d=l[m].name;k[d]=i[p]}m++}i=k}catch(o){}}j.helper.dropped=
true;if(e.beforedrop&&a.isFunction(e.beforedrop)){d=e.beforedrop.call(this,h,j,i,a("#"+c.id),a(this));if(typeof d!="undefined"&&d!==null&&typeof d=="object")i=d}if(j.helper.dropped){var n;if(e.autoid)if(a.isFunction(e.autoid))n=e.autoid.call(this,i);else{n=Math.ceil(Math.random()*1E3);n=e.autoidprefix+n}a("#"+this.id).jqGrid("addRowData",n,i,e.droppos)}e.ondrop&&a.isFunction(e.ondrop)&&e.ondrop.call(this,h,j,i)}}},e.drop_opts||{})},onstart:null,onstop:null,beforedrop:null,ondrop:null,drop_opts:{activeClass:"ui-state-active",
hoverClass:"ui-state-hover"},drag_opts:{revert:"invalid",helper:"clone",cursor:"move",appendTo:"#jqgrid_dnd",zIndex:5E3},dropbyname:false,droppos:"first",autoid:true,autoidprefix:"dnd_"},b||{});if(b.connectWith){b.connectWith=b.connectWith.split(",");b.connectWith=a.map(b.connectWith,function(e){return a.trim(e)});a.data(c,"dnd",b);c.p.reccount!="0"&&!c.p.jqgdnd&&g();c.p.jqgdnd=true;for(var f=0;f<b.connectWith.length;f++)a(b.connectWith[f]).droppable(a.isFunction(b.drop)?b.drop.call(a(c),b):b.drop)}}}})},
gridResize:function(b){return this.each(function(){var g=this;if(g.grid&&a.fn.resizable){b=a.extend({},b||{});if(b.alsoResize){b._alsoResize_=b.alsoResize;delete b.alsoResize}else b._alsoResize_=false;if(b.stop&&a.isFunction(b.stop)){b._stop_=b.stop;delete b.stop}else b._stop_=false;b.stop=function(c,f){a(g).jqGrid("setGridParam",{height:a("#gview_"+g.p.id+" .ui-jqgrid-bdiv").height()});a(g).jqGrid("setGridWidth",f.size.width,b.shrinkToFit);b._stop_&&b._stop_.call(g,c,f)};b.alsoResize=b._alsoResize_?
eval("("+("{'#gview_"+g.p.id+" .ui-jqgrid-bdiv':true,'"+b._alsoResize_+"':true}")+")"):a(".ui-jqgrid-bdiv","#gview_"+g.p.id);delete b._alsoResize_;a("#gbox_"+g.p.id).resizable(b)}})}})})(jQuery);

}); // define()
;
define('jquery-layout', ['jquery'], function (jQuery) {
/**
 * @preserve
 * jquery.layout 1.3.0 - Release Candidate 30.7
 * $Date: 2012-10-13 08:00:00 (Sat, 13 Oct 2012) $
 * $Rev: 303007 $
 *
 * Copyright (c) 2012 
 *   Fabrizio Balliano (http://www.fabrizioballiano.net)
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc30.7
 *
 * Docs: http://layout.jquery-dev.net/documentation.html
 * Tips: http://layout.jquery-dev.net/tips.html
 * Help: http://groups.google.com/group/jquery-ui-layout
 */

/* JavaDoc Info: http://code.google.com/closure/compiler/docs/js-for-compiler.html
 * {!Object}	non-nullable type (never NULL)
 * {?string}	nullable type (sometimes NULL) - default for {Object}
 * {number=}	optional parameter
 * {*}			ALL types
 */

// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars

;(function ($) {

// alias Math methods - used a lot!
var	min		= Math.min
,	max		= Math.max
,	round	= Math.floor

,	isStr	=  function (v) { return $.type(v) === "string"; }

	/**
	* @param {!Object}			Instance
	* @param {Array.<string>}	a_fn
	*/
,	runPluginCallbacks = function (Instance, a_fn) {
		if ($.isArray(a_fn))
			for (var i=0, c=a_fn.length; i<c; i++) {
				var fn = a_fn[i];
				try {
					if (isStr(fn)) // 'name' of a function
						fn = eval(fn);
					if ($.isFunction(fn))
						g(fn)( Instance );
				} catch (ex) {}
			}
		function g (f) { return f; }; // compiler hack
	}

;


/*
 *	GENERIC $.layout METHODS - used by all layouts
 */
$.layout = {

	version:	"1.3.rc30.7"
,	revision:	0.033007 // 1.3.0 final = 1.0300 - major(n+).minor(nn)+patch(nn+)

	// can update code here if $.browser is phased out or logic changes
,	browser: {
		mozilla:	!!$.browser.mozilla
	,	webkit:		!!$.browser.webkit || !!$.browser.safari // webkit = jQ 1.4
	,	msie:		!!$.browser.msie
	,	isIE6:		$.browser.msie && $.browser.version == 6
	,	boxModel:	$.support.boxModel !== false || !$.browser.msie // ONLY IE reverts to old box-model - update for older jQ onReady
	,	version:	$.browser.version // not used in Layout core, but may be used by plugins
	}

	// *PREDEFINED* EFFECTS & DEFAULTS 
	// MUST list effect here - OR MUST set an fxSettings option (can be an empty hash: {})
,	effects: {

	//	Pane Open/Close Animations
		slide: {
			all:	{ duration:  "fast"	} // eg: duration: 1000, easing: "easeOutBounce"
		,	north:	{ direction: "up"	}
		,	south:	{ direction: "down"	}
		,	east:	{ direction: "right"}
		,	west:	{ direction: "left"	}
		}
	,	drop: {
			all:	{ duration:  "slow"	}
		,	north:	{ direction: "up"	}
		,	south:	{ direction: "down"	}
		,	east:	{ direction: "right"}
		,	west:	{ direction: "left"	}
		}
	,	scale: {
			all:	{ duration:	"fast"	}
		}
	//	these are not recommended, but can be used
	,	blind:		{}
	,	clip:		{}
	,	explode:	{}
	,	fade:		{}
	,	fold:		{}
	,	puff:		{}

	//	Pane Resize Animations
	,	size: {
			all:	{ easing:	"swing"	}
		}
	}

	// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
,	config: {
		optionRootKeys:	"effects,panes,north,south,west,east,center".split(",")
	,	allPanes:		"north,south,west,east,center".split(",")
	,	borderPanes:	"north,south,west,east".split(",")
	,	oppositeEdge: {
			north:	"south"
		,	south:	"north"
		,	east: 	"west"
		,	west: 	"east"
		}
	//	offscreen data
	,	offscreenCSS:	{ left: "-99999px", right: "auto" } // used by hide/close if useOffscreenClose=true
	,	offscreenReset:	"offscreenReset" // key used for data
	//	CSS used in multiple places
	,	hidden:		{ visibility: "hidden" }
	,	visible:	{ visibility: "visible" }
	//	layout element settings
	,	resizers: {
			cssReq: {
				position: 	"absolute"
			,	padding: 	0
			,	margin: 	0
			,	fontSize:	"1px"
			,	textAlign:	"left"	// to counter-act "center" alignment!
			,	overflow: 	"hidden" // prevent toggler-button from overflowing
			//	SEE $.layout.defaults.zIndexes.resizer_normal
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				background: "#DDD"
			,	border:		"none"
			}
		}
	,	togglers: {
			cssReq: {
				position: 	"absolute"
			,	display: 	"block"
			,	padding: 	0
			,	margin: 	0
			,	overflow:	"hidden"
			,	textAlign:	"center"
			,	fontSize:	"1px"
			,	cursor: 	"pointer"
			,	zIndex: 	1
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				background: "#AAA"
			}
		}
	,	content: {
			cssReq: {
				position:	"relative" /* contain floated or positioned elements */
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				overflow:	"auto"
			,	padding:	"10px"
			}
		,	cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div
				overflow:	"hidden"
			,	padding:	0
			}
		}
	,	panes: { // defaults for ALL panes - overridden by 'per-pane settings' below
			cssReq: {
				position: 	"absolute"
			,	margin:		0
			//	$.layout.defaults.zIndexes.pane_normal
			}
		,	cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
				padding:	"10px"
			,	background:	"#FFF"
			,	border:		"1px solid #BBB"
			,	overflow:	"auto"
			}
		}
	,	north: {
			side:			"top"
		,	sizeType:		"Height"
		,	dir:			"horz"
		,	cssReq: {
				top: 		0
			,	bottom: 	"auto"
			,	left: 		0
			,	right: 		0
			,	width: 		"auto"
			//	height: 	DYNAMIC
			}
		}
	,	south: {
			side:			"bottom"
		,	sizeType:		"Height"
		,	dir:			"horz"
		,	cssReq: {
				top: 		"auto"
			,	bottom: 	0
			,	left: 		0
			,	right: 		0
			,	width: 		"auto"
			//	height: 	DYNAMIC
			}
		}
	,	east: {
			side:			"right"
		,	sizeType:		"Width"
		,	dir:			"vert"
		,	cssReq: {
				left: 		"auto"
			,	right: 		0
			,	top: 		"auto" // DYNAMIC
			,	bottom: 	"auto" // DYNAMIC
			,	height: 	"auto"
			//	width: 		DYNAMIC
			}
		}
	,	west: {
			side:			"left"
		,	sizeType:		"Width"
		,	dir:			"vert"
		,	cssReq: {
				left: 		0
			,	right: 		"auto"
			,	top: 		"auto" // DYNAMIC
			,	bottom: 	"auto" // DYNAMIC
			,	height: 	"auto"
			//	width: 		DYNAMIC
			}
		}
	,	center: {
			dir:			"center"
		,	cssReq: {
				left: 		"auto" // DYNAMIC
			,	right: 		"auto" // DYNAMIC
			,	top: 		"auto" // DYNAMIC
			,	bottom: 	"auto" // DYNAMIC
			,	height: 	"auto"
			,	width: 		"auto"
			}
		}
	}

	// CALLBACK FUNCTION NAMESPACE - used to store reusable callback functions
,	callbacks: {}

,	getParentPaneElem: function (el) {
		// must pass either a container or pane element
		var $el = $(el)
		,	layout = $el.data("layout") || $el.data("parentLayout");
		if (layout) {
			var $cont = layout.container;
			// see if this container is directly-nested inside an outer-pane
			if ($cont.data("layoutPane")) return $cont;
			var $pane = $cont.closest("."+ $.layout.defaults.panes.paneClass);
			// if a pane was found, return it
			if ($pane.data("layoutPane")) return $pane;
		}
		return null;
	}

,	getParentPaneInstance: function (el) {
		// must pass either a container or pane element
		var $pane = $.layout.getParentPaneElem(el);
		return $pane ? $pane.data("layoutPane") : null;
	}

,	getParentLayoutInstance: function (el) {
		// must pass either a container or pane element
		var $pane = $.layout.getParentPaneElem(el);
		return $pane ? $pane.data("parentLayout") : null;
	}

,	getEventObject: function (evt) {
		return typeof evt === "object" && evt.stopPropagation ? evt : null;
	}
,	parsePaneName: function (evt_or_pane) {
		// getEventObject() automatically calls .stopPropagation(), WHICH MUST BE DONE!
		var evt = $.layout.getEventObject( evt_or_pane );
		if (evt) {
			// ALWAYS stop propagation of events triggered in Layout!
			evt.stopPropagation();
			return $(this).data("layoutEdge");
		}
		else
			return evt_or_pane;
	}


	// LAYOUT-PLUGIN REGISTRATION
	// more plugins can added beyond this default list
,	plugins: {
		draggable:		!!$.fn.draggable // resizing
	,	effects: {
			core:		!!$.effects		// animimations (specific effects tested by initOptions)
		,	slide:		$.effects && ($.effects.slide || $.effects.effect.slide) // default effect
		}
	}

//	arrays of plugin or other methods to be triggered for events in *each layout* - will be passed 'Instance'
,	onCreate:	[]	// runs when layout is just starting to be created - right after options are set
,	onLoad:		[]	// runs after layout container and global events init, but before initPanes is called
,	onReady:	[]	// runs after initialization *completes* - ie, after initPanes completes successfully
,	onDestroy:	[]	// runs after layout is destroyed
,	onUnload:	[]	// runs after layout is destroyed OR when page unloads
,	afterOpen:	[]	// runs after setAsOpen() completes
,	afterClose:	[]	// runs after setAsClosed() completes

	/*
	*	GENERIC UTILITY METHODS
	*/

	// calculate and return the scrollbar width, as an integer
,	scrollbarWidth:		function () { return window.scrollbarWidth  || $.layout.getScrollbarSize('width'); }
,	scrollbarHeight:	function () { return window.scrollbarHeight || $.layout.getScrollbarSize('height'); }
,	getScrollbarSize:	function (dim) {
		var $c	= $('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll;"></div>').appendTo("body");
		var d	= { width: $c.css("width") - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight };
		$c.remove();
		window.scrollbarWidth	= d.width;
		window.scrollbarHeight	= d.height;
		return dim.match(/^(width|height)$/) ? d[dim] : d;
	}


	/**
	* Returns hash container 'display' and 'visibility'
	*
	* @see	$.swap() - swaps CSS, runs callback, resets CSS
	* @param  {!Object}		$E				jQuery element
	* @param  {boolean=}	[force=false]	Run even if display != none
	* @return {!Object}						Returns current style props, if applicable
	*/
,	showInvisibly: function ($E, force) {
		if ($E && $E.length && (force || $E.css("display") === "none")) { // only if not *already hidden*
			var s = $E[0].style
				// save ONLY the 'style' props because that is what we must restore
			,	CSS = { display: s.display || '', visibility: s.visibility || '' };
			// show element 'invisibly' so can be measured
			$E.css({ display: "block", visibility: "hidden" });
			return CSS;
		}
		return {};
	}

	/**
	* Returns data for setting size of an element (container or a pane).
	*
	* @see  _create(), onWindowResize() for container, plus others for pane
	* @return JSON  Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
	*/
,	getElementDimensions: function ($E, inset) {
		var
		//	dimensions hash - start with current data IF passed
			d	= { css: {}, inset: {} }
		,	x	= d.css			// CSS hash
		,	i	= { bottom: 0 }	// TEMP insets (bottom = complier hack)
		,	N	= $.layout.cssNum
		,	off = $E.offset()
		,	b, p, ei			// TEMP border, padding
		;
		d.offsetLeft = off.left;
		d.offsetTop  = off.top;

		if (!inset) inset = {}; // simplify logic below

		$.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge
			b = x["border" + e] = $.layout.borderWidth($E, e);
			p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e);
			ei = e.toLowerCase();
			d.inset[ei] = inset[ei] >= 0 ? inset[ei] : p; // any missing insetX value = paddingX
			i[ei] = d.inset[ei] + b; // total offset of content from outer side
		});

		x.width		= $E.css("width");
		x.height	= $E.height();
		x.top		= N($E,"top",true);
		x.bottom	= N($E,"bottom",true);
		x.left		= N($E,"left",true);
		x.right		= N($E,"right",true);

		d.outerWidth	= $E.outerWidth();
		d.outerHeight	= $E.outerHeight();
		// calc the TRUE inner-dimensions, even in quirks-mode!
		d.innerWidth	= max(0, d.outerWidth  - i.left - i.right);
		d.innerHeight	= max(0, d.outerHeight - i.top  - i.bottom);
		// layoutWidth/Height is used in calcs for manual resizing
		// layoutW/H only differs from innerW/H when in quirks-mode - then is like outerW/H
		d.layoutWidth	= $E.innerWidth();
		d.layoutHeight	= $E.innerHeight();

		//if ($E.prop('tagName') === 'BODY') { debugData( d, $E.prop('tagName') ); } // DEBUG

		//d.visible	= $E.is(":visible");// && x.width > 0 && x.height > 0;

		return d;
	}

,	getElementStyles: function ($E, list) {
		var
			CSS	= {}
		,	style	= $E[0].style
		,	props	= list.split(",")
		,	sides	= "Top,Bottom,Left,Right".split(",")
		,	attrs	= "Color,Style,Width".split(",")
		,	p, s, a, i, j, k
		;
		for (i=0; i < props.length; i++) {
			p = props[i];
			if (p.match(/(border|padding|margin)$/))
				for (j=0; j < 4; j++) {
					s = sides[j];
					if (p === "border")
						for (k=0; k < 3; k++) {
							a = attrs[k];
							CSS[p+s+a] = style[p+s+a];
						}
					else
						CSS[p+s] = style[p+s];
				}
			else
				CSS[p] = style[p];
		};
		return CSS
	}

	/**
	* Return the innerWidth for the current browser/doctype
	*
	* @see  initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
	* @param  {Array.<Object>}	$E  Must pass a jQuery object - first element is processed
	* @param  {number=}			outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized
	* @return {number}			Returns the innerWidth of the elem by subtracting padding and borders
	*/
,	cssWidth: function ($E, outerWidth) {
		// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
		if (outerWidth <= 0) return 0;

		if (!$.layout.browser.boxModel) return outerWidth;

		// strip border and padding from outerWidth to get CSS Width
		var b = $.layout.borderWidth
		,	n = $.layout.cssNum
		,	W = outerWidth
				- b($E, "Left")
				- b($E, "Right")
				- n($E, "paddingLeft")		
				- n($E, "paddingRight");

		return max(0,W);
	}

	/**
	* Return the innerHeight for the current browser/doctype
	*
	* @see  initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
	* @param  {Array.<Object>}	$E  Must pass a jQuery object - first element is processed
	* @param  {number=}			outerHeight  (optional) Can pass a width, allowing calculations BEFORE element is resized
	* @return {number}			Returns the innerHeight of the elem by subtracting padding and borders
	*/
,	cssHeight: function ($E, outerHeight) {
		// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
		if (outerHeight <= 0) return 0;

		if (!$.layout.browser.boxModel) return outerHeight;

		// strip border and padding from outerHeight to get CSS Height
		var b = $.layout.borderWidth
		,	n = $.layout.cssNum
		,	H = outerHeight
			- b($E, "Top")
			- b($E, "Bottom")
			- n($E, "paddingTop")
			- n($E, "paddingBottom");

		return max(0,H);
	}

	/**
	* Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist
	*
	* @see  Called by many methods
	* @param {Array.<Object>}	$E					Must pass a jQuery object - first element is processed
	* @param {string}			prop				The name of the CSS property, eg: top, width, etc.
	* @param {boolean=}			[allowAuto=false]	true = return 'auto' if that is value; false = return 0
	* @return {(string|number)}						Usually used to get an integer value for position (top, left) or size (height, width)
	*/
,	cssNum: function ($E, prop, allowAuto) {
		if (!$E.jquery) $E = $($E);
		var CSS = $.layout.showInvisibly($E)
		,	p	= $.css($E[0], prop, true)
		,	v	= allowAuto && p=="auto" ? p : Math.round(parseFloat(p) || 0);
		$E.css( CSS ); // RESET
		return v;
	}

,	borderWidth: function (el, side) {
		if (el.jquery) el = el[0];
		var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left
		return $.css(el, b+"Style", true) === "none" ? 0 : Math.round(parseFloat($.css(el, b+"Width", true)) || 0);
	}

	/**
	* Mouse-tracking utility - FUTURE REFERENCE
	*
	* init: if (!window.mouse) {
	*			window.mouse = { x: 0, y: 0 };
	*			$(document).mousemove( $.layout.trackMouse );
	*		}
	*
	* @param {Object}		evt
	*
,	trackMouse: function (evt) {
		window.mouse = { x: evt.clientX, y: evt.clientY };
	}
	*/

	/**
	* SUBROUTINE for preventPrematureSlideClose option
	*
	* @param {Object}		evt
	* @param {Object=}		el
	*/
,	isMouseOverElem: function (evt, el) {
		var
			$E	= $(el || this)
		,	d	= $E.offset()
		,	T	= d.top
		,	L	= d.left
		,	R	= L + $E.outerWidth()
		,	B	= T + $E.outerHeight()
		,	x	= evt.pageX	// evt.clientX ?
		,	y	= evt.pageY	// evt.clientY ?
		;
		// if X & Y are < 0, probably means is over an open SELECT
		return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B));
	}

	/**
	* Message/Logging Utility
	*
	* @example $.layout.msg("My message");				// log text
	* @example $.layout.msg("My message", true);		// alert text
	* @example $.layout.msg({ foo: "bar" }, "Title");	// log hash-data, with custom title
	* @example $.layout.msg({ foo: "bar" }, true, "Title", { sort: false }); -OR-
	* @example $.layout.msg({ foo: "bar" }, "Title", { sort: false, display: true }); // alert hash-data
	*
	* @param {(Object|string)}			info			String message OR Hash/Array
	* @param {(Boolean|string|Object)=}	[popup=false]	True means alert-box - can be skipped
	* @param {(Object|string)=}			[debugTitle=""]	Title for Hash data - can be skipped
	* @param {Object=}					[debugOpts]		Extra options for debug output
	*/
,	msg: function (info, popup, debugTitle, debugOpts) {
		if ($.isPlainObject(info) && window.debugData) {
			if (typeof popup === "string") {
				debugOpts	= debugTitle;
				debugTitle	= popup;
			}
			else if (typeof debugTitle === "object") {
				debugOpts	= debugTitle;
				debugTitle	= null;
			}
			var t = debugTitle || "log( <object> )"
			,	o = $.extend({ sort: false, returnHTML: false, display: false }, debugOpts);
			if (popup === true || o.display)
				debugData( info, t, o );
			else if (window.console)
				console.log(debugData( info, t, o ));
		}
		else if (popup)
			alert(info);
		else if (window.console)
			console.log(info);
		else {
			var id	= "#layoutLogger"
			,	$l = $(id);
			if (!$l.length)
				$l = createLog();
			$l.children("ul").append('<li style="padding: 4px 10px; margin: 0; border-top: 1px solid #CCC;">'+ info.replace(/\</g,"&lt;").replace(/\>/g,"&gt;") +'</li>');
		}

		function createLog () {
			var pos = $.support.fixedPosition ? 'fixed' : 'absolute'
			,	$e = $('<div id="layoutLogger" style="position: '+ pos +'; top: 5px; z-index: 999999; max-width: 25%; overflow: hidden; border: 1px solid #000; border-radius: 5px; background: #FBFBFB; box-shadow: 0 2px 10px rgba(0,0,0,0.3);">'
				+	'<div style="font-size: 13px; font-weight: bold; padding: 5px 10px; background: #F6F6F6; border-radius: 5px 5px 0 0; cursor: move;">'
				+	'<span style="float: right; padding-left: 7px; cursor: pointer;" title="Remove Console" onclick="$(this).closest(\'#layoutLogger\').remove()">X</span>Layout console.log</div>'
				+	'<ul style="font-size: 13px; font-weight: none; list-style: none; margin: 0; padding: 0 0 2px;"></ul>'
				+ '</div>'
				).appendTo("body");
			$e.css('left', $(window).width() - $e.outerWidth() - 5)
			if ($.ui.draggable) $e.draggable({ handle: ':first-child' });
			return $e;
		};
	}

};

// DEFAULT OPTIONS
$.layout.defaults = {
/*
 *	LAYOUT & LAYOUT-CONTAINER OPTIONS
 *	- none of these options are applicable to individual panes
 */
	name:						""			// Not required, but useful for buttons and used for the state-cookie
,	containerClass:				"ui-layout-container" // layout-container element
,	inset:						null		// custom container-inset values (override padding)
,	scrollToBookmarkOnLoad:		true		// after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
,	resizeWithWindow:			true		// bind thisLayout.resizeAll() to the window.resize event
,	resizeWithWindowDelay:		200			// delay calling resizeAll because makes window resizing very jerky
,	resizeWithWindowMaxDelay:	0			// 0 = none - force resize every XX ms while window is being resized
,	maskPanesEarly:				false		// true = create pane-masks on resizer.mouseDown instead of waiting for resizer.dragstart
,	onresizeall_start:			null		// CALLBACK when resizeAll() STARTS	- NOT pane-specific
,	onresizeall_end:			null		// CALLBACK when resizeAll() ENDS	- NOT pane-specific
,	onload_start:				null		// CALLBACK when Layout inits - after options initialized, but before elements
,	onload_end:					null		// CALLBACK when Layout inits - after EVERYTHING has been initialized
,	onunload_start:				null		// CALLBACK when Layout is destroyed OR onWindowUnload
,	onunload_end:				null		// CALLBACK when Layout is destroyed OR onWindowUnload
,	initPanes:					true		// false = DO NOT initialize the panes onLoad - will init later
,	showErrorMessages:			true		// enables fatal error messages to warn developers of common errors
,	showDebugMessages:			false		// display console-and-alert debug msgs - IF this Layout version _has_ debugging code!
//	Changing this zIndex value will cause other zIndex values to automatically change
,	zIndex:						null		// the PANE zIndex - resizers and masks will be +1
//	DO NOT CHANGE the zIndex values below unless you clearly understand their relationships
,	zIndexes: {								// set _default_ z-index values here...
		pane_normal:			0			// normal z-index for panes
	,	content_mask:			1			// applied to overlays used to mask content INSIDE panes during resizing
	,	resizer_normal:			2			// normal z-index for resizer-bars
	,	pane_sliding:			100			// applied to *BOTH* the pane and its resizer when a pane is 'slid open'
	,	pane_animate:			1000		// applied to the pane when being animated - not applied to the resizer
	,	resizer_drag:			10000		// applied to the CLONED resizer-bar when being 'dragged'
	}
,	errors: {
		pane:					"pane"		// description of "layout pane element" - used only in error messages
	,	selector:				"selector"	// description of "jQuery-selector" - used only in error messages
	,	addButtonError:			"Error Adding Button \n\nInvalid "
	,	containerMissing:		"UI Layout Initialization Error\n\nThe specified layout-container does not exist."
	,	centerPaneMissing:		"UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element."
	,	noContainerHeight:		"UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!"
	,	callbackError:			"UI Layout Callback Error\n\nThe EVENT callback is not a valid function."
	}
/*
 *	PANE DEFAULT SETTINGS
 *	- settings under the 'panes' key become the default settings for *all panes*
 *	- ALL pane-options can also be set specifically for each panes, which will override these 'default values'
 */
,	panes: { // default options for 'all panes' - will be overridden by 'per-pane settings'
		applyDemoStyles: 		false		// NOTE: renamed from applyDefaultStyles for clarity
	,	closable:				true		// pane can open & close
	,	resizable:				true		// when open, pane can be resized 
	,	slidable:				true		// when closed, pane can 'slide open' over other panes - closes on mouse-out
	,	initClosed:				false		// true = init pane as 'closed'
	,	initHidden: 			false 		// true = init pane as 'hidden' - no resizer-bar/spacing
	//	SELECTORS
	//,	paneSelector:			""			// MUST be pane-specific - jQuery selector for pane
	,	contentSelector:		".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane!
	,	contentIgnoreSelector:	".ui-layout-ignore"	// element(s) to 'ignore' when measuring 'content'
	,	findNestedContent:		false		// true = $P.find(contentSelector), false = $P.children(contentSelector)
	//	GENERIC ROOT-CLASSES - for auto-generated classNames
	,	paneClass:				"ui-layout-pane"	// Layout Pane
	,	resizerClass:			"ui-layout-resizer"	// Resizer Bar
	,	togglerClass:			"ui-layout-toggler"	// Toggler Button
	,	buttonClass:			"ui-layout-button"	// CUSTOM Buttons	- eg: '[ui-layout-button]-toggle/-open/-close/-pin'
	//	ELEMENT SIZE & SPACING
	//,	size:					100			// MUST be pane-specific -initial size of pane
	,	minSize:				0			// when manually resizing a pane
	,	maxSize:				0			// ditto, 0 = no limit
	,	spacing_open:			6			// space between pane and adjacent panes - when pane is 'open'
	,	spacing_closed:			6			// ditto - when pane is 'closed'
	,	togglerLength_open:		50			// Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides
	,	togglerLength_closed: 	50			// 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
	,	togglerAlign_open:		"center"	// top/left, bottom/right, center, OR...
	,	togglerAlign_closed:	"center"	// 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
	,	togglerContent_open:	""			// text or HTML to put INSIDE the toggler
	,	togglerContent_closed:	""			// ditto
	//	RESIZING OPTIONS
	,	resizerDblClickToggle:	true		// 
	,	autoResize:				true		// IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes
	,	autoReopen:				true		// IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed
	,	resizerDragOpacity:		1			// option for ui.draggable
	//,	resizerCursor:			""			// MUST be pane-specific - cursor when over resizer-bar
	,	maskContents:			false		// true = add DIV-mask over-or-inside this pane so can 'drag' over IFRAMES
	,	maskObjects:			false		// true = add IFRAME-mask over-or-inside this pane to cover objects/applets - content-mask will overlay this mask
	,	maskZindex:				null		// will override zIndexes.content_mask if specified - not applicable to iframe-panes
	,	resizingGrid:			false		// grid size that the resizers will snap-to during resizing, eg: [20,20]
	,	livePaneResizing:		false		// true = LIVE Resizing as resizer is dragged
	,	liveContentResizing:	false		// true = re-measure header/footer heights as resizer is dragged
	,	liveResizingTolerance:	1			// how many px change before pane resizes, to control performance
	//	SLIDING OPTIONS
	,	sliderCursor:			"pointer"	// cursor when resizer-bar will trigger 'sliding'
	,	slideTrigger_open:		"click"		// click, dblclick, mouseenter
	,	slideTrigger_close:		"mouseleave"// click, mouseleave
	,	slideDelay_open:		300			// applies only for mouseenter event - 0 = instant open
	,	slideDelay_close:		300			// applies only for mouseleave event (300ms is the minimum!)
	,	hideTogglerOnSlide:		false		// when pane is slid-open, should the toggler show?
	,	preventQuickSlideClose:	$.layout.browser.webkit // Chrome triggers slideClosed as it is opening
	,	preventPrematureSlideClose: false	// handle incorrect mouseleave trigger, like when over a SELECT-list in IE
	//	PANE-SPECIFIC TIPS & MESSAGES
	,	tips: {
			Open:				"Open"		// eg: "Open Pane"
		,	Close:				"Close"
		,	Resize:				"Resize"
		,	Slide:				"Slide Open"
		,	Pin:				"Pin"
		,	Unpin:				"Un-Pin"
		,	noRoomToOpen:		"Not enough room to show this panel."	// alert if user tries to open a pane that cannot
		,	minSizeWarning:		"Panel has reached its minimum size"	// displays in browser statusbar
		,	maxSizeWarning:		"Panel has reached its maximum size"	// ditto
		}
	//	HOT-KEYS & MISC
	,	showOverflowOnHover:	false		// will bind allowOverflow() utility to pane.onMouseOver
	,	enableCursorHotkey:		true		// enabled 'cursor' hotkeys
	//,	customHotkey:			""			// MUST be pane-specific - EITHER a charCode OR a character
	,	customHotkeyModifier:	"SHIFT"		// either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
	//	PANE ANIMATION
	//	NOTE: fxSss_open, fxSss_close & fxSss_size options (eg: fxName_open) are auto-generated if not passed
	,	fxName:					"slide" 	// ('none' or blank), slide, drop, scale -- only relevant to 'open' & 'close', NOT 'size'
	,	fxSpeed:				null		// slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
	,	fxSettings:				{}			// can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
	,	fxOpacityFix:			true		// tries to fix opacity in IE to restore anti-aliasing after animation
	,	animatePaneSizing:		false		// true = animate resizing after dragging resizer-bar OR sizePane() is called
	/*  NOTE: Action-specific FX options are auto-generated from the options above if not specifically set:
		fxName_open:			"slide"		// 'Open' pane animation
		fnName_close:			"slide"		// 'Close' pane animation
		fxName_size:			"slide"		// 'Size' pane animation - when animatePaneSizing = true
		fxSpeed_open:			null
		fxSpeed_close:			null
		fxSpeed_size:			null
		fxSettings_open:		{}
		fxSettings_close:		{}
		fxSettings_size:		{}
	*/
	//	CHILD/NESTED LAYOUTS
	,	children:				null		// Layout-options for nested/child layout - even {} is valid as options
	,	containerSelector:		''			// if child is NOT 'directly nested', a selector to find it/them (can have more than one child layout!)
	,	initChildren:			true		// true = child layout will be created as soon as _this_ layout completes initialization
	,	destroyChildren:		true		// true = destroy child-layout if this pane is destroyed
	,	resizeChildren:			true		// true = trigger child-layout.resizeAll() when this pane is resized
	//	EVENT TRIGGERING
	,	triggerEventsOnLoad:	false		// true = trigger onopen OR onclose callbacks when layout initializes
	,	triggerEventsDuringLiveResize: true	// true = trigger onresize callback REPEATEDLY if livePaneResizing==true
	//	PANE CALLBACKS
	,	onshow_start:			null		// CALLBACK when pane STARTS to Show	- BEFORE onopen/onhide_start
	,	onshow_end:				null		// CALLBACK when pane ENDS being Shown	- AFTER  onopen/onhide_end
	,	onhide_start:			null		// CALLBACK when pane STARTS to Close	- BEFORE onclose_start
	,	onhide_end:				null		// CALLBACK when pane ENDS being Closed	- AFTER  onclose_end
	,	onopen_start:			null		// CALLBACK when pane STARTS to Open
	,	onopen_end:				null		// CALLBACK when pane ENDS being Opened
	,	onclose_start:			null		// CALLBACK when pane STARTS to Close
	,	onclose_end:			null		// CALLBACK when pane ENDS being Closed
	,	onresize_start:			null		// CALLBACK when pane STARTS being Resized ***FOR ANY REASON***
	,	onresize_end:			null		// CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
	,	onsizecontent_start:	null		// CALLBACK when sizing of content-element STARTS
	,	onsizecontent_end:		null		// CALLBACK when sizing of content-element ENDS
	,	onswap_start:			null		// CALLBACK when pane STARTS to Swap
	,	onswap_end:				null		// CALLBACK when pane ENDS being Swapped
	,	ondrag_start:			null		// CALLBACK when pane STARTS being ***MANUALLY*** Resized
	,	ondrag_end:				null		// CALLBACK when pane ENDS being ***MANUALLY*** Resized
	}
/*
 *	PANE-SPECIFIC SETTINGS
 *	- options listed below MUST be specified per-pane - they CANNOT be set under 'panes'
 *	- all options under the 'panes' key can also be set specifically for any pane
 *	- most options under the 'panes' key apply only to 'border-panes' - NOT the the center-pane
 */
,	north: {
		paneSelector:			".ui-layout-north"
	,	size:					"auto"		// eg: "auto", "30%", .30, 200
	,	resizerCursor:			"n-resize"	// custom = url(myCursor.cur)
	,	customHotkey:			""			// EITHER a charCode (43) OR a character ("o")
	}
,	south: {
		paneSelector:			".ui-layout-south"
	,	size:					"auto"
	,	resizerCursor:			"s-resize"
	,	customHotkey:			""
	}
,	east: {
		paneSelector:			".ui-layout-east"
	,	size:					200
	,	resizerCursor:			"e-resize"
	,	customHotkey:			""
	}
,	west: {
		paneSelector:			".ui-layout-west"
	,	size:					200
	,	resizerCursor:			"w-resize"
	,	customHotkey:			""
	}
,	center: {
		paneSelector:			".ui-layout-center"
	,	minWidth:				0
	,	minHeight:				0
	}
};

$.layout.optionsMap = {
	// layout/global options - NOT pane-options
	layout: ("name,instanceKey,stateManagement,effects,inset,zIndexes,errors,"
	+	"zIndex,scrollToBookmarkOnLoad,showErrorMessages,maskPanesEarly,"
	+	"outset,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"
	+	"onresizeall,onresizeall_start,onresizeall_end,onload,onunload").split(",")
//	borderPanes: [ ALL options that are NOT specified as 'layout' ]
	// default.panes options that apply to the center-pane (most options apply _only_ to border-panes)
,	center: ("paneClass,contentSelector,contentIgnoreSelector,findNestedContent,applyDemoStyles,triggerEventsOnLoad,"
	+	"showOverflowOnHover,maskContents,maskObjects,liveContentResizing,"
	+	"containerSelector,children,initChildren,resizeChildren,destroyChildren,"
	+	"onresize,onresize_start,onresize_end,onsizecontent,onsizecontent_start,onsizecontent_end").split(",")
	// options that MUST be specifically set 'per-pane' - CANNOT set in the panes (defaults) key
,	noDefault: ("paneSelector,resizerCursor,customHotkey").split(",")
};

/**
 * Processes options passed in converts flat-format data into subkey (JSON) format
 * In flat-format, subkeys are _currently_ separated with 2 underscores, like north__optName
 * Plugins may also call this method so they can transform their own data
 *
 * @param  {!Object}	hash			Data/options passed by user - may be a single level or nested levels
 * @param  {boolean=}	[addKeys=false]	Should the primary layout.options keys be added if they do not exist?
 * @return {Object}						Returns hash of minWidth & minHeight
 */
$.layout.transformData = function (hash, addKeys) {
	var	json = addKeys ? { panes: {}, center: {} } : {} // init return object
	,	branch, optKey, keys, key, val, i, c;

	if (typeof hash !== "object") return json; // no options passed

	// convert all 'flat-keys' to 'sub-key' format
	for (optKey in hash) {
		branch	= json;
		val		= hash[ optKey ];
		keys	= optKey.split("__"); // eg: west__size or north__fxSettings__duration
		c		= keys.length - 1;
		// convert underscore-delimited to subkeys
		for (i=0; i <= c; i++) {
			key = keys[i];
			if (i === c) {	// last key = value
				if ($.isPlainObject( val ))
					branch[key] = $.layout.transformData( val ); // RECURSE
				else
					branch[key] = val;
			}
			else {
				if (!branch[key])
					branch[key] = {}; // create the subkey
				// recurse to sub-key for next loop - if not done
				branch = branch[key];
			}
		}
	}
	return json;
};

// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
$.layout.backwardCompatibility = {
	// data used by renameOldOptions()
	map: {
	//	OLD Option Name:			NEW Option Name
		applyDefaultStyles:			"applyDemoStyles"
	//	CHILD/NESTED LAYOUTS
	,	childOptions:				"children"
	,	initChildLayout:			"initChildren"
	,	destroyChildLayout:			"destroyChildren"
	,	resizeChildLayout:			"resizeChildren"
	,	resizeNestedLayout:			"resizeChildren"
	//	MISC Options
	,	resizeWhileDragging:		"livePaneResizing"
	,	resizeContentWhileDragging:	"liveContentResizing"
	,	triggerEventsWhileDragging:	"triggerEventsDuringLiveResize"
	,	maskIframesOnResize:		"maskContents"
	//	STATE MANAGEMENT
	,	useStateCookie:				"stateManagement.enabled"
	,	"cookie.autoLoad":			"stateManagement.autoLoad"
	,	"cookie.autoSave":			"stateManagement.autoSave"
	,	"cookie.keys":				"stateManagement.stateKeys"
	,	"cookie.name":				"stateManagement.cookie.name"
	,	"cookie.domain":			"stateManagement.cookie.domain"
	,	"cookie.path":				"stateManagement.cookie.path"
	,	"cookie.expires":			"stateManagement.cookie.expires"
	,	"cookie.secure":			"stateManagement.cookie.secure"
	//	OLD Language options
	,	noRoomToOpenTip:			"tips.noRoomToOpen"
	,	togglerTip_open:			"tips.Close"	// open   = Close
	,	togglerTip_closed:			"tips.Open"		// closed = Open
	,	resizerTip:					"tips.Resize"
	,	sliderTip:					"tips.Slide"
	}

/**
* @param {Object}	opts
*/
,	renameOptions: function (opts) {
		var map = $.layout.backwardCompatibility.map
		,	oldData, newData, value
		;
		for (var itemPath in map) {
			oldData	= getBranch( itemPath );
			value	= oldData.branch[ oldData.key ];
			if (value !== undefined) {
				newData = getBranch( map[itemPath], true );
				newData.branch[ newData.key ] = value;
				delete oldData.branch[ oldData.key ];
			}
		}

		/**
		* @param {string}	path
		* @param {boolean=}	[create=false]	Create path if does not exist
		*/
		function getBranch (path, create) {
			var a = path.split(".") // split keys into array
			,	c = a.length - 1
			,	D = { branch: opts, key: a[c] } // init branch at top & set key (last item)
			,	i = 0, k, undef;
			for (; i<c; i++) { // skip the last key (data)
				k = a[i];
				if (D.branch[ k ] == undefined) { // child-key does not exist
					if (create) {
						D.branch = D.branch[ k ] = {}; // create child-branch
					}
					else // can't go any farther
						D.branch = {}; // branch is undefined
				}
				else
					D.branch = D.branch[ k ]; // get child-branch
			}
			return D;
		};
	}

/**
* @param {Object}	opts
*/
,	renameAllOptions: function (opts) {
		var ren = $.layout.backwardCompatibility.renameOptions;
		// rename root (layout) options
		ren( opts );
		// rename 'defaults' to 'panes'
		if (opts.defaults) {
			if (typeof opts.panes !== "object")
				opts.panes = {};
			$.extend(true, opts.panes, opts.defaults);
			delete opts.defaults;
		}
		// rename options in the the options.panes key
		if (opts.panes) ren( opts.panes );
		// rename options inside *each pane key*, eg: options.west
		$.each($.layout.config.allPanes, function (i, pane) {
			if (opts[pane]) ren( opts[pane] );
		});	
		return opts;
	}
};




/*	============================================================
 *	BEGIN WIDGET: $( selector ).layout( {options} );
 *	============================================================
 */
$.fn.layout = function (opts) {
	var

	// local aliases to global data
	browser	= $.layout.browser
,	_c		= $.layout.config

	// local aliases to utlity methods
,	cssW	= $.layout.cssWidth
,	cssH	= $.layout.cssHeight
,	elDims	= $.layout.getElementDimensions
,	styles	= $.layout.getElementStyles
,	evtObj	= $.layout.getEventObject
,	evtPane	= $.layout.parsePaneName

/**
 * options - populated by initOptions()
 */
,	options = $.extend(true, {}, $.layout.defaults)
,	effects	= options.effects = $.extend(true, {}, $.layout.effects)

/**
 * layout-state object
 */
,	state = {
		// generate unique ID to use for event.namespace so can unbind only events added by 'this layout'
		id:				"layout"+ $.now()	// code uses alias: sID
	,	initialized:	false
	,	paneResizing:	false
	,	panesSliding:	{}
	,	container:	{ 	// list all keys referenced in code to avoid compiler error msgs
			innerWidth:		0
		,	innerHeight:	0
		,	outerWidth:		0
		,	outerHeight:	0
		,	layoutWidth:	0
		,	layoutHeight:	0
		}
	,	north:		{ childIdx: 0 }
	,	south:		{ childIdx: 0 }
	,	east:		{ childIdx: 0 }
	,	west:		{ childIdx: 0 }
	,	center:		{ childIdx: 0 }
	}

/**
 * parent/child-layout pointers
 */
//,	hasParentLayout	= false	- exists ONLY inside Instance so can be set externally
,	children = {
		north:		null
	,	south:		null
	,	east:		null
	,	west:		null
	,	center:		null
	}

/*
 * ###########################
 *  INTERNAL HELPER FUNCTIONS
 * ###########################
 */

	/**
	* Manages all internal timers
	*/
,	timer = {
		data:	{}
	,	set:	function (s, fn, ms) { timer.clear(s); timer.data[s] = setTimeout(fn, ms); }
	,	clear:	function (s) { var t=timer.data; if (t[s]) {clearTimeout(t[s]); delete t[s];} }
	}

	/**
	* Alert or console.log a message - IF option is enabled.
	*
	* @param {(string|!Object)}	msg				Message (or debug-data) to display
	* @param {boolean=}			[popup=false]	True by default, means 'alert', false means use console.log
	* @param {boolean=}			[debug=false]	True means is a widget debugging message
	*/
,	_log = function (msg, popup, debug) {
		var o = options;
		if ((o.showErrorMessages && !debug) || (debug && o.showDebugMessages))
			$.layout.msg( o.name +' / '+ msg, (popup !== false) );
		return false;
	}

	/**
	* Executes a Callback function after a trigger event, like resize, open or close
	*
	* @param {string}				evtName					Name of the layout callback, eg "onresize_start"
	* @param {(string|boolean)=}	[pane=""]				This is passed only so we can pass the 'pane object' to the callback
	* @param {(string|boolean)=}	[skipBoundEvents=false]	True = do not run events bound to the elements - only the callbacks set in options
	*/
,	_runCallbacks = function (evtName, pane, skipBoundEvents) {
		var	hasPane	= pane && isStr(pane)
		,	s		= hasPane ? state[pane] : state
		,	o		= hasPane ? options[pane] : options
		,	lName	= options.name
			// names like onopen and onopen_end separate are interchangeable in options...
		,	lng		= evtName + (evtName.match(/_/) ? "" : "_end")
		,	shrt	= lng.match(/_end$/) ? lng.substr(0, lng.length - 4) : ""
		,	fn		= o[lng] || o[shrt]
		,	retVal	= "NC" // NC = No Callback
		,	args	= []
		,	$P
		;
		if ( !hasPane && $.type(pane) === 'boolean' ) {
			skipBoundEvents = pane; // allow pane param to be skipped for Layout callback
			pane = "";
		}

		// first trigger the callback set in the options
		if (fn) {
			try {
				// convert function name (string) to function object
				if (isStr( fn )) {
					if (fn.match(/,/)) {
						// function name cannot contain a comma, 
						// so must be a function name AND a parameter to pass
						args = fn.split(",")
						,	fn = eval(args[0]);
					}
					else // just the name of an external function?
						fn = eval(fn);
				}
				// execute the callback, if exists
				if ($.isFunction( fn )) {
					if (args.length)
						retVal = g(fn)(args[1]); // pass the argument parsed from 'list'
					else if ( hasPane )
						// pass data: pane-name, pane-element, pane-state, pane-options, and layout-name
						retVal = g(fn)( pane, $Ps[pane], s, o, lName );
					else // must be a layout/container callback - pass suitable info
						retVal = g(fn)( Instance, s, o, lName );
				}
			}
			catch (ex) {
				_log( options.errors.callbackError.replace(/EVENT/, $.trim((pane || "") +" "+ lng)), false );
			}
		}

		// trigger additional events bound directly to the pane
		if (!skipBoundEvents && retVal !== false) {
			if ( hasPane ) { // PANE events can be bound to each pane-elements
				$P	= $Ps[pane];
				o	= options[pane];
				s	= state[pane];
				$P.triggerHandler('layoutpane'+ lng, [ pane, $P, s, o, lName ]);
				if (shrt)
					$P.triggerHandler('layoutpane'+ shrt, [ pane, $P, s, o, lName ]);
			}
			else { // LAYOUT events can be bound to the container-element
				$N.triggerHandler('layout'+ lng, [ Instance, s, o, lName ]);
				if (shrt)
					$N.triggerHandler('layout'+ shrt, [ Instance, s, o, lName ]);
			}
		}

		// ALWAYS resizeChildren after a resize event - even during initialization
		if (hasPane && evtName === "onresize_end" || evtName === "onsizecontent_end")
			resizeChildren(pane+"", true); // compiler hack -force string

		return retVal;

		function g (f) { return f; }; // compiler hack
	}


	/**
	* cure iframe display issues in IE & other browsers
	*/
,	_fixIframe = function (pane) {
		if (browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow
		var $P = $Ps[pane];
		// if the 'pane' is an iframe, do it
		if (state[pane].tagName === "IFRAME")
			$P.css(_c.hidden).css(_c.visible); 
		else // ditto for any iframes INSIDE the pane
			$P.find('IFRAME').css(_c.hidden).css(_c.visible);
	}

	/**
	* @param  {string}		pane		Can accept ONLY a 'pane' (east, west, etc)
	* @param  {number=}		outerSize	(optional) Can pass a width, allowing calculations BEFORE element is resized
	* @return {number}		Returns the innerHeight/Width of el by subtracting padding and borders
	*/
,	cssSize = function (pane, outerSize) {
		var fn = _c[pane].dir=="horz" ? cssH : cssW;
		return fn($Ps[pane], outerSize);
	}

	/**
	* @param  {string}		pane		Can accept ONLY a 'pane' (east, west, etc)
	* @return {Object}		Returns hash of minWidth & minHeight
	*/
,	cssMinDims = function (pane) {
		// minWidth/Height means CSS width/height = 1px
		var	$P	= $Ps[pane]
		,	dir	= _c[pane].dir
		,	d	= {
				minWidth:	1001 - cssW($P, 1000)
			,	minHeight:	1001 - cssH($P, 1000)
			}
		;
		if (dir === "horz") d.minSize = d.minHeight;
		if (dir === "vert") d.minSize = d.minWidth;
		return d;
	}

	// TODO: see if these methods can be made more useful...
	// TODO: *maybe* return cssW/H from these so caller can use this info

	/**
	* @param {(string|!Object)}		el
	* @param {number=}				outerWidth
	* @param {boolean=}				[autoHide=false]
	*/
,	setOuterWidth = function (el, outerWidth, autoHide) {
		var $E = el, w;
		if (isStr(el)) $E = $Ps[el]; // west
		else if (!el.jquery) $E = $(el);
		w = cssW($E, outerWidth);
		$E.css({ width: w });
		if (w > 0) {
			if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) {
				$E.show().data('autoHidden', false);
				if (!browser.mozilla) // FireFox refreshes iframes - IE does not
					// make hidden, then visible to 'refresh' display after animation
					$E.css(_c.hidden).css(_c.visible);
			}
		}
		else if (autoHide && !$E.data('autoHidden'))
			$E.hide().data('autoHidden', true);
	}

	/**
	* @param {(string|!Object)}		el
	* @param {number=}				outerHeight
	* @param {boolean=}				[autoHide=false]
	*/
,	setOuterHeight = function (el, outerHeight, autoHide) {
		var $E = el, h;
		if (isStr(el)) $E = $Ps[el]; // west
		else if (!el.jquery) $E = $(el);
		h = cssH($E, outerHeight);
		$E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent
		if (h > 0 && $E.innerWidth() > 0) {
			if (autoHide && $E.data('autoHidden')) {
				$E.show().data('autoHidden', false);
				if (!browser.mozilla) // FireFox refreshes iframes - IE does not
					$E.css(_c.hidden).css(_c.visible);
			}
		}
		else if (autoHide && !$E.data('autoHidden'))
			$E.hide().data('autoHidden', true);
	}


	/**
	* Converts any 'size' params to a pixel/integer size, if not already
	* If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated
	*
	/**
	* @param  {string}				pane
	* @param  {(string|number)=}	size
	* @param  {string=}				[dir]
	* @return {number}
	*/
,	_parseSize = function (pane, size, dir) {
		if (!dir) dir = _c[pane].dir;

		if (isStr(size) && size.match(/%/))
			size = (size === '100%') ? -1 : parseInt(size, 10) / 100; // convert % to decimal

		if (size === 0)
			return 0;
		else if (size >= 1)
			return parseInt(size, 10);

		var o = options, avail = 0;
		if (dir=="horz") // north or south or center.minHeight
			avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0);
		else if (dir=="vert") // east or west or center.minWidth
			avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0);

		if (size === -1) // -1 == 100%
			return avail;
		else if (size > 0) // percentage, eg: .25
			return round(avail * size);
		else if (pane=="center")
			return 0;
		else { // size < 0 || size=='auto' || size==Missing || size==Invalid
			// auto-size the pane
			var	dim	= (dir === "horz" ? "height" : "width")
			,	$P	= $Ps[pane]
			,	$C	= dim === 'height' ? $Cs[pane] : false
			,	vis	= $.layout.showInvisibly($P) // show pane invisibly if hidden
			,	szP	= $P.css(dim) // SAVE current pane size
			,	szC	= $C ? $C.css(dim) : 0 // SAVE current content size
			;
			$P.css(dim, "auto");
			if ($C) $C.css(dim, "auto");
			size = (dim === "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE
			$P.css(dim, szP).css(vis); // RESET size & visibility
			if ($C) $C.css(dim, szC);
			return size;
		}
	}

	/**
	* Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added
	*
	* @param  {(string|!Object)}	pane
	* @param  {boolean=}			[inclSpace=false]
	* @return {number}				Returns EITHER Width for east/west panes OR Height for north/south panes
	*/
,	getPaneSize = function (pane, inclSpace) {
		var 
			$P	= $Ps[pane]
		,	o	= options[pane]
		,	s	= state[pane]
		,	oSp	= (inclSpace ? o.spacing_open : 0)
		,	cSp	= (inclSpace ? o.spacing_closed : 0)
		;
		if (!$P || s.isHidden)
			return 0;
		else if (s.isClosed || (s.isSliding && inclSpace))
			return cSp;
		else if (_c[pane].dir === "horz")
			return $P.outerHeight() + oSp;
		else // dir === "vert"
			return $P.outerWidth() + oSp;
	}

	/**
	* Calculate min/max pane dimensions and limits for resizing
	*
	* @param  {string}		pane
	* @param  {boolean=}	[slide=false]
	*/
,	setSizeLimits = function (pane, slide) {
		if (!isInitialized()) return;
		var 
			o				= options[pane]
		,	s				= state[pane]
		,	c				= _c[pane]
		,	dir				= c.dir
		,	type			= c.sizeType.toLowerCase()
		,	isSliding		= (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param
		,	$P				= $Ps[pane]
		,	paneSpacing		= o.spacing_open
		//	measure the pane on the *opposite side* from this pane
		,	altPane			= _c.oppositeEdge[pane]
		,	altS			= state[altPane]
		,	$altP			= $Ps[altPane]
		,	altPaneSize		= (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth()))
		,	altPaneSpacing	= ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0)
		//	limitSize prevents this pane from 'overlapping' opposite pane
		,	containerSize	= (dir=="horz" ? sC.innerHeight : sC.innerWidth)
		,	minCenterDims	= cssMinDims("center")
		,	minCenterSize	= dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth)
		//	if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them
		,	limitSize		= (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing)))
		,	minSize			= s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize )
		,	maxSize			= s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize )
		,	r				= s.resizerPosition = {} // used to set resizing limits
		,	top				= sC.inset.top
		,	left			= sC.inset.left
		,	W				= sC.innerWidth
		,	H				= sC.innerHeight
		,	rW				= o.spacing_open // subtract resizer-width to get top/left position for south/east
		;
		switch (pane) {
			case "north":	r.min = top + minSize;
							r.max = top + maxSize;
							break;
			case "west":	r.min = left + minSize;
							r.max = left + maxSize;
							break;
			case "south":	r.min = top + H - maxSize - rW;
							r.max = top + H - minSize - rW;
							break;
			case "east":	r.min = left + W - maxSize - rW;
							r.max = left + W - minSize - rW;
							break;
		};
	}

	/**
	* Returns data for setting the size/position of center pane. Also used to set Height for east/west panes
	*
	* @return JSON  Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
	*/
,	calcNewCenterPaneDims = function () {
		var d = {
			top:	getPaneSize("north", true) // true = include 'spacing' value for pane
		,	bottom:	getPaneSize("south", true)
		,	left:	getPaneSize("west", true)
		,	right:	getPaneSize("east", true)
		,	width:	0
		,	height:	0
		};

		// NOTE: sC = state.container
		// calc center-pane outer dimensions
		d.width		= sC.innerWidth - d.left - d.right;  // outerWidth
		d.height	= sC.innerHeight - d.bottom - d.top; // outerHeight
		// add the 'container border/padding' to get final positions relative to the container
		d.top		+= sC.inset.top;
		d.bottom	+= sC.inset.bottom;
		d.left		+= sC.inset.left;
		d.right		+= sC.inset.right;

		return d;
	}


	/**
	* @param {!Object}		el
	* @param {boolean=}		[allStates=false]
	*/
,	getHoverClasses = function (el, allStates) {
		var
			$El		= $(el)
		,	type	= $El.data("layoutRole")
		,	pane	= $El.data("layoutEdge")
		,	o		= options[pane]
		,	root	= o[type +"Class"]
		,	_pane	= "-"+ pane // eg: "-west"
		,	_open	= "-open"
		,	_closed	= "-closed"
		,	_slide	= "-sliding"
		,	_hover	= "-hover " // NOTE the trailing space
		,	_state	= $El.hasClass(root+_closed) ? _closed : _open
		,	_alt	= _state === _closed ? _open : _closed
		,	classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover)
		;
		if (allStates) // when 'removing' classes, also remove alternate-state classes
			classes += (root+_alt+_hover) + (root+_pane+_alt+_hover);

		if (type=="resizer" && $El.hasClass(root+_slide))
			classes += (root+_slide+_hover) + (root+_pane+_slide+_hover);

		return $.trim(classes);
	}
,	addHover	= function (evt, el) {
		var $E = $(el || this);
		if (evt && $E.data("layoutRole") === "toggler")
			evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar
		$E.addClass( getHoverClasses($E) );
	}
,	removeHover	= function (evt, el) {
		var $E = $(el || this);
		$E.removeClass( getHoverClasses($E, true) );
	}

,	onResizerEnter	= function (evt) { // ALSO called by toggler.mouseenter
		var pane	= $(this).data("layoutEdge")
		,	s		= state[pane]
		;
		// ignore closed-panes and mouse moving back & forth over resizer!
		// also ignore if ANY pane is currently resizing
		if ( s.isClosed || s.isResizing || state.paneResizing ) return;

		if ($.fn.disableSelection)
			$("body").disableSelection();
		if (options.maskPanesEarly)
			showMasks( pane );
	}
,	onResizerLeave	= function (evt, el) {
		var	e		= el || this // el is only passed when called by the timer
		,	pane	= $(e).data("layoutEdge")
		,	name	= pane +"ResizerLeave"
		;
		timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set
		timer.clear(name); // cancel enableSelection timer - may re/set below
		// this method calls itself on a timer because it needs to allow
		// enough time for dragging to kick-in and set the isResizing flag
		// dragging has a 100ms delay set, so this delay must be >100
		if (!el) // 1st call - mouseleave event
			timer.set(name, function(){ onResizerLeave(evt, e); }, 200);
		// if user is resizing, then dragStop will enableSelection(), so can skip it here
		else if ( !state.paneResizing ) { // 2nd call - by timer
			if ($.fn.enableSelection)
				$("body").enableSelection();
			if (options.maskPanesEarly)
				hideMasks();
		}
	}

/*
 * ###########################
 *   INITIALIZATION METHODS
 * ###########################
 */

	/**
	* Initialize the layout - called automatically whenever an instance of layout is created
	*
	* @see  none - triggered onInit
	* @return  mixed	true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort
	*/
,	_create = function () {
		// initialize config/options
		initOptions();
		var o = options
		,	s = state;

		// TEMP state so isInitialized returns true during init process
		s.creatingLayout = true;

		// init plugins for this layout, if there are any (eg: stateManagement)
		runPluginCallbacks( Instance, $.layout.onCreate );

		// options & state have been initialized, so now run beforeLoad callback
		// onload will CANCEL layout creation if it returns false
		if (false === _runCallbacks("onload_start"))
			return 'cancel';

		// initialize the container element
		_initContainer();

		// bind hotkey function - keyDown - if required
		initHotkeys();

		// bind window.onunload
		$(window).bind("unload."+ sID, unload);

		// init plugins for this layout, if there are any (eg: customButtons)
		runPluginCallbacks( Instance, $.layout.onLoad );

		// if layout elements are hidden, then layout WILL NOT complete initialization!
		// initLayoutElements will set initialized=true and run the onload callback IF successful
		if (o.initPanes) _initLayoutElements();

		delete s.creatingLayout;

		return state.initialized;
	}

	/**
	* Initialize the layout IF not already
	*
	* @see  All methods in Instance run this test
	* @return  boolean	true = layoutElements have been initialized | false = panes are not initialized (yet)
	*/
,	isInitialized = function () {
		if (state.initialized || state.creatingLayout) return true;	// already initialized
		else return _initLayoutElements();	// try to init panes NOW
	}

	/**
	* Initialize the layout - called automatically whenever an instance of layout is created
	*
	* @see  _create() & isInitialized
	* @param {boolean=}		[retry=false]	// indicates this is a 2nd try
	* @return  An object pointer to the instance created
	*/
,	_initLayoutElements = function (retry) {
		// initialize config/options
		var o = options;
		// CANNOT init panes inside a hidden container!
		if (!$N.is(":visible")) {
			// handle Chrome bug where popup window 'has no height'
			// if layout is BODY element, try again in 50ms
			// SEE: http://layout.jquery-dev.net/samples/test_popup_window.html
			if ( !retry && browser.webkit && $N[0].tagName === "BODY" )
				setTimeout(function(){ _initLayoutElements(true); }, 50);
			return false;
		}

		// a center pane is required, so make sure it exists
		if (!getPane("center").length) {
			return _log( o.errors.centerPaneMissing );
		}

		// TEMP state so isInitialized returns true during init process
		state.creatingLayout = true;

		// update Container dims
		$.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values

		// initialize all layout elements
		initPanes();	// size & position panes - calls initHandles() - which calls initResizable()

		if (o.scrollToBookmarkOnLoad) {
			var l = self.location;
			if (l.hash) l.replace( l.hash ); // scrollTo Bookmark
		}

		// check to see if this layout 'nested' inside a pane
		if (Instance.hasParentLayout)
			o.resizeWithWindow = false;
		// bind resizeAll() for 'this layout instance' to window.resize event
		else if (o.resizeWithWindow)
			$(window).bind("resize."+ sID, windowResize);

		delete state.creatingLayout;
		state.initialized = true;

		// init plugins for this layout, if there are any
		runPluginCallbacks( Instance, $.layout.onReady );

		// now run the onload callback, if exists
		_runCallbacks("onload_end");

		return true; // elements initialized successfully
	}

	/**
	* Initialize nested layouts for a specific pane - can optionally pass layout-options
	*
	* @param {(string|Object)}	evt_or_pane	The pane being opened, ie: north, south, east, or west
	* @param {Object=}			[opts]		Layout-options - if passed, will OVERRRIDE options[pane].children
	* @return  An object pointer to the layout instance created - or null
	*/
,	createChildren = function (evt_or_pane, opts) {
		var	pane = evtPane.call(this, evt_or_pane)
		,	$P	= $Ps[pane]
		;
		if (!$P) return;
		var	$C	= $Cs[pane]
		,	s	= state[pane]
		,	o	= options[pane]
		,	sm	= options.stateManagement || {}
		,	cos = opts ? (o.children = opts) : o.children
		;
		if ( $.isPlainObject( cos ) )
			cos = [ cos ]; // convert a hash to a 1-elem array
		else if (!cos || !$.isArray( cos ))
			return;

		$.each( cos, function (idx, co) {
			if ( !$.isPlainObject( co ) ) return;

			// determine which element is supposed to be the 'child container'
			// if pane has a 'containerSelector' OR a 'content-div', use those instead of the pane
			var $containers = co.containerSelector ? $P.find( co.containerSelector ) : ($C || $P);

			$containers.each(function(){
				var $cont	= $(this)
				,	child	= $cont.data("layout") //	see if a child-layout ALREADY exists on this element
				;
				// if no layout exists, but children are set, try to create the layout now
				if (!child) {
					// TODO: see about moving this to the stateManagement plugin, as a method
					// set a unique child-instance key for this layout, if not already set
					setInstanceKey({ container: $cont, options: co }, s );
					// If THIS layout has a hash in stateManagement.autoLoad,
					// then see if it also contains state-data for this child-layout
					// If so, copy the stateData to child.options.stateManagement.autoLoad
					if ( sm.includeChildren && state.stateData[pane] ) {
						//	THIS layout's state was cached when its state was loaded
						var	paneChildren = state.stateData[pane].children || {}
						,	childState	= paneChildren[ co.instanceKey ]
						,	co_sm		= co.stateManagement || (co.stateManagement = { autoLoad: true })
						;
						// COPY the stateData into the autoLoad key
						if ( co_sm.autoLoad === true && childState ) {
							co_sm.autoSave			= false; // disable autoSave because saving handled by parent-layout
							co_sm.includeChildren	= true;  // cascade option - FOR NOW
							co_sm.autoLoad = $.extend(true, {}, childState); // COPY the state-hash
						}
					}

					// create the layout
					child = $cont.layout( co );

					// if successful, update data
					if (child) {
						// add the child and update all layout-pointers
						// MAY have already been done by child-layout calling parent.refreshChildren()
						refreshChildren( pane, child );
					}
				}
			});
		});
	}

,	setInstanceKey = function (child, parentPaneState) {
		// create a named key for use in state and instance branches
		var	$c	= child.container
		,	o	= child.options
		,	sm	= o.stateManagement
		,	key	= o.instanceKey || $c.data("layoutInstanceKey")
		;
		if (!key) key = (sm && sm.cookie ? sm.cookie.name : '') || o.name; // look for a name/key
		if (!key) key = "layout"+ (++parentPaneState.childIdx);	// if no name/key found, generate one
		else key = key.replace(/[^\w-]/gi, '_').replace(/_{2,}/g, '_');	 // ensure is valid as a hash key
		o.instanceKey = key;
		$c.data("layoutInstanceKey", key); // useful if layout is destroyed and then recreated
		return key;
	}

	/**
	* @param {string}		pane		The pane being opened, ie: north, south, east, or west
	* @param {Object=}		newChild	New child-layout Instance to add to this pane
	*/
,	refreshChildren = function (pane, newChild) {
		var	$P	= $Ps[pane]
		,	pC	= children[pane]
		,	s	= state[pane]
		,	o
		;
		// check for destroy()ed layouts and update the child pointers & arrays
		if ($.isPlainObject( pC )) {
			$.each( pC, function (key, child) {
				if (child.destroyed) delete pC[key]
			});
			// if no more children, remove the children hash
			if ($.isEmptyObject( pC ))
				pC = children[pane] = null; // clear children hash
		}

		// see if there is a directly-nested layout inside this pane
		// if there is, then there can be only ONE child-layout, so check that...
		if (!newChild && !pC) {
			newChild = $P.data("layout");
		}

		// if a newChild instance was passed, add it to children[pane]
		if (newChild) {
			// update child.state
			newChild.hasParentLayout = true; // set parent-flag in child
			// instanceKey is a key-name used in both state and children
			o = newChild.options;
			// set a unique child-instance key for this layout, if not already set
			setInstanceKey( newChild, s );
			// add pointer to pane.children hash
			if (!pC) pC = children[pane] = {}; // create an empty children hash
			pC[ o.instanceKey ] = newChild.container.data("layout"); // add childLayout instance
		}

		// ALWAYS refresh the pane.children alias, even if null
		Instance[pane].children = children[pane];

		// if newChild was NOT passed - see if there is a child layout NOW
		if (!newChild) {
			createChildren(pane); // MAY create a child and re-call this method
		}
	}

,	windowResize = function () {
		var	o = options
		,	delay = Number(o.resizeWithWindowDelay);
		if (delay < 10) delay = 100; // MUST have a delay!
		// resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway
		timer.clear("winResize"); // if already running
		timer.set("winResize", function(){
			timer.clear("winResize");
			timer.clear("winResizeRepeater");
			var dims = elDims( $N, o.inset );
			// only trigger resizeAll() if container has changed size
			if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight)
				resizeAll();
		}, delay);
		// ALSO set fixed-delay timer, if not already running
		if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater();
	}

,	setWindowResizeRepeater = function () {
		var delay = Number(options.resizeWithWindowMaxDelay);
		if (delay > 0)
			timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay);
	}

,	unload = function () {
		var o = options;

		_runCallbacks("onunload_start");

		// trigger plugin callabacks for this layout (eg: stateManagement)
		runPluginCallbacks( Instance, $.layout.onUnload );

		_runCallbacks("onunload_end");
	}

	/**
	* Validate and initialize container CSS and events
	*
	* @see  _create()
	*/
,	_initContainer = function () {
		var
			N		= $N[0]	
		,	$H		= $("html")
		,	tag		= sC.tagName = N.tagName
		,	id		= sC.id = N.id
		,	cls		= sC.className = N.className
		,	o		= options
		,	name	= o.name
		,	props	= "position,margin,padding,border"
		,	css		= "layoutCSS"
		,	CSS		= {}
		,	hid		= "hidden" // used A LOT!
		//	see if this container is a 'pane' inside an outer-layout
		,	parent	= $N.data("parentLayout")	// parent-layout Instance
		,	pane	= $N.data("layoutEdge")		// pane-name in parent-layout
		,	isChild	= parent && pane
		,	num		= $.layout.cssNum
		,	$parent, n
		;
		// sC = state.container
		sC.selector = $N.selector.split(".slice")[0];
		sC.ref		= (o.name ? o.name +' layout / ' : '') + tag + (id ? "#"+id : cls ? '.['+cls+']' : ''); // used in messages
		sC.isBody	= (tag === "BODY");

		// try to find a parent-layout
		if (!isChild && !sC.isBody) {
			$parent = $N.closest("."+ $.layout.defaults.panes.paneClass);
			parent	= $parent.data("parentLayout");
			pane	= $parent.data("layoutEdge");
			isChild	= parent && pane;
		}

		$N	.data({
				layout: Instance
			,	layoutContainer: sID // FLAG to indicate this is a layout-container - contains unique internal ID
			})
			.addClass(o.containerClass)
		;
		var layoutMethods = {
			destroy:	''
		,	initPanes:	''
		,	resizeAll:	'resizeAll'
		,	resize:		'resizeAll'
		};
		// loop hash and bind all methods - include layoutID namespacing
		for (name in layoutMethods) {
			$N.bind("layout"+ name.toLowerCase() +"."+ sID, Instance[ layoutMethods[name] || name ]);
		}

		// if this container is another layout's 'pane', then set child/parent pointers
		if (isChild) {
			// update parent flag
			Instance.hasParentLayout = true;
			// set pointers to THIS child-layout (Instance) in parent-layout
			parent.refreshChildren( pane, Instance );
		}

		// SAVE original container CSS for use in destroy()
		if (!$N.data(css)) {
			// handle props like overflow different for BODY & HTML - has 'system default' values
			if (sC.isBody) {
				// SAVE <BODY> CSS
				$N.data(css, $.extend( styles($N, props), {
					height:		$N.css("height")
				,	overflow:	$N.css("overflow")
				,	overflowX:	$N.css("overflowX")
				,	overflowY:	$N.css("overflowY")
				}));
				// ALSO SAVE <HTML> CSS
				$H.data(css, $.extend( styles($H, 'padding'), {
					height:		"auto" // FF would return a fixed px-size!
				,	overflow:	$H.css("overflow")
				,	overflowX:	$H.css("overflowX")
				,	overflowY:	$H.css("overflowY")
				}));
			}
			else // handle props normally for non-body elements
				$N.data(css, styles($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY") );
		}

		try {
			// common container CSS
			CSS = {
				overflow:	hid
			,	overflowX:	hid
			,	overflowY:	hid
			};
			$N.css( CSS );

			if (o.inset && !$.isPlainObject(o.inset)) {
				// can specify a single number for equal outset all-around
				n = parseInt(o.inset, 10) || 0
				o.inset = {
					top:	n
				,	bottom:	n
				,	left:	n
				,	right:	n
				};
			}

			// format html & body if this is a full page layout
			if (sC.isBody) {
				// if HTML has padding, use this as an outer-spacing around BODY
				if (!o.outset) {
					// use padding from parent-elem (HTML) as outset
					o.outset = {
						top:	num($H, "paddingTop")
					,	bottom:	num($H, "paddingBottom")
					,	left:	num($H, "paddingLeft")
					,	right:	num($H, "paddingRight")
					};
				}
				else if (!$.isPlainObject(o.outset)) {
					// can specify a single number for equal outset all-around
					n = parseInt(o.outset, 10) || 0
					o.outset = {
						top:	n
					,	bottom:	n
					,	left:	n
					,	right:	n
					};
				}
				// HTML
				$H.css( CSS ).css({
					height:		"100%"
				,	border:		"none"	// no border or padding allowed when using height = 100%
				,	padding:	0		// ditto
				,	margin:		0
				});
				// BODY
				if (browser.isIE6) {
					// IE6 CANNOT use the trick of setting absolute positioning on all 4 sides - must have 'height'
					$N.css({
						width:		"100%"
					,	height:		"100%"
					,	border:		"none"	// no border or padding allowed when using height = 100%
					,	padding:	0		// ditto
					,	margin:		0
					,	position:	"relative"
					});
					// convert body padding to an inset option - the border cannot be measured in IE6!
					if (!o.inset) o.inset = elDims( $N ).inset;
				}
				else { // use absolute positioning for BODY to allow borders & padding without overflow
					$N.css({
						width:		"auto"
					,	height:		"auto"
					,	margin:		0
					,	position:	"absolute"	// allows for border and padding on BODY
					});
					// apply edge-positioning created above
					$N.css( o.outset );
				}
				// set current layout-container dimensions
				$.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values
			}
			else {
				// container MUST have 'position'
				var	p = $N.css("position");
				if (!p || !p.match(/(fixed|absolute|relative)/))
					$N.css("position","relative");

				// set current layout-container dimensions
				if ( $N.is(":visible") ) {
					$.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT change insetX (padding) values
					if (sC.innerHeight < 1) // container has no 'height' - warn developer
						_log( o.errors.noContainerHeight.replace(/CONTAINER/, sC.ref) );
				}
			}

			// if container has min-width/height, then enable scrollbar(s)
			if ( num($N, "minWidth")  ) $N.parent().css("overflowX","auto");
			if ( num($N, "minHeight") ) $N.parent().css("overflowY","auto");

		} catch (ex) {}
	}

	/**
	* Bind layout hotkeys - if options enabled
	*
	* @see  _create() and addPane()
	* @param {string=}	[panes=""]	The edge(s) to process
	*/
,	initHotkeys = function (panes) {
		panes = panes ? panes.split(",") : _c.borderPanes;
		// bind keyDown to capture hotkeys, if option enabled for ANY pane
		$.each(panes, function (i, pane) {
			var o = options[pane];
			if (o.enableCursorHotkey || o.customHotkey) {
				$(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE
				return false; // BREAK - binding was done
			}
		});
	}

	/**
	* Build final OPTIONS data
	*
	* @see  _create()
	*/
,	initOptions = function () {
		var data, d, pane, key, val, i, c, o;

		// reprocess user's layout-options to have correct options sub-key structure
		opts = $.layout.transformData( opts, true ); // panes = default subkey

		// auto-rename old options for backward compatibility
		opts = $.layout.backwardCompatibility.renameAllOptions( opts );

		// if user-options has 'panes' key (pane-defaults), clean it...
		if (!$.isEmptyObject(opts.panes)) {
			// REMOVE any pane-defaults that MUST be set per-pane
			data = $.layout.optionsMap.noDefault;
			for (i=0, c=data.length; i<c; i++) {
				key = data[i];
				delete opts.panes[key]; // OK if does not exist
			}
			// REMOVE any layout-options specified under opts.panes
			data = $.layout.optionsMap.layout;
			for (i=0, c=data.length; i<c; i++) {
				key = data[i];
				delete opts.panes[key]; // OK if does not exist
			}
		}

		// MOVE any NON-layout-options from opts-root to opts.panes
		data = $.layout.optionsMap.layout;
		var rootKeys = $.layout.config.optionRootKeys;
		for (key in opts) {
			val = opts[key];
			if ($.inArray(key, rootKeys) < 0 && $.inArray(key, data) < 0) {
				if (!opts.panes[key])
					opts.panes[key] = $.isPlainObject(val) ? $.extend(true, {}, val) : val;
				delete opts[key]
			}
		}

		// START by updating ALL options from opts
		$.extend(true, options, opts);

		// CREATE final options (and config) for EACH pane
		$.each(_c.allPanes, function (i, pane) {

			// apply 'pane-defaults' to CONFIG.[PANE]
			_c[pane] = $.extend(true, {}, _c.panes, _c[pane]);

			d = options.panes;
			o = options[pane];

			// center-pane uses SOME keys in defaults.panes branch
			if (pane === 'center') {
				// ONLY copy keys from opts.panes listed in: $.layout.optionsMap.center
				data = $.layout.optionsMap.center;		// list of 'center-pane keys'
				for (i=0, c=data.length; i<c; i++) {	// loop the list...
					key = data[i];
					// only need to use pane-default if pane-specific value not set
					if (!opts.center[key] && (opts.panes[key] || !o[key]))
						o[key] = d[key]; // pane-default
				}
			}
			else {
				// border-panes use ALL keys in defaults.panes branch
				o = options[pane] = $.extend(true, {}, d, o); // re-apply pane-specific opts AFTER pane-defaults
				createFxOptions( pane );
				// ensure all border-pane-specific base-classes exist
				if (!o.resizerClass)	o.resizerClass	= "ui-layout-resizer";
				if (!o.togglerClass)	o.togglerClass	= "ui-layout-toggler";
			}
			// ensure we have base pane-class (ALL panes)
			if (!o.paneClass) o.paneClass = "ui-layout-pane";
		});

		// update options.zIndexes if a zIndex-option specified
		var zo	= opts.zIndex
		,	z	= options.zIndexes;
		if (zo > 0) {
			z.pane_normal		= zo;
			z.content_mask		= max(zo+1, z.content_mask);	// MIN = +1
			z.resizer_normal	= max(zo+2, z.resizer_normal);	// MIN = +2
		}

		// DELETE 'panes' key now that we are done - values were copied to EACH pane
		delete options.panes;


		function createFxOptions ( pane ) {
			var	o = options[pane]
			,	d = options.panes;
			// ensure fxSettings key to avoid errors
			if (!o.fxSettings) o.fxSettings = {};
			if (!d.fxSettings) d.fxSettings = {};

			$.each(["_open","_close","_size"], function (i,n) { 
				var
					sName		= "fxName"+ n
				,	sSpeed		= "fxSpeed"+ n
				,	sSettings	= "fxSettings"+ n
					// recalculate fxName according to specificity rules
				,	fxName = o[sName] =
						o[sName]	// options.west.fxName_open
					||	d[sName]	// options.panes.fxName_open
					||	o.fxName	// options.west.fxName
					||	d.fxName	// options.panes.fxName
					||	"none"		// MEANS $.layout.defaults.panes.fxName == "" || false || null || 0
				;
				// validate fxName to ensure is valid effect - MUST have effect-config data in options.effects
				if (fxName === "none" || !options.effects[fxName] || !$.effects || (!$.effects[fxName] && !$.effects.effect[fxName]))
					fxName = o[sName] = "none"; // effect not loaded OR unrecognized fxName

				// set vars for effects subkeys to simplify logic
				var	fx		= options.effects[fxName] || {}	// effects.slide
				,	fx_all	= fx.all	|| null				// effects.slide.all
				,	fx_pane	= fx[pane]	|| null				// effects.slide.west
				;
				// create fxSpeed[_open|_close|_size]
				o[sSpeed] =
					o[sSpeed]				// options.west.fxSpeed_open
				||	d[sSpeed]				// options.west.fxSpeed_open
				||	o.fxSpeed				// options.west.fxSpeed
				||	d.fxSpeed				// options.panes.fxSpeed
				||	null					// DEFAULT - let fxSetting.duration control speed
				;
				// create fxSettings[_open|_close|_size]
				o[sSettings] = $.extend(
					true
				,	{}
				,	fx_all					// effects.slide.all
				,	fx_pane					// effects.slide.west
				,	d.fxSettings			// options.panes.fxSettings
				,	o.fxSettings			// options.west.fxSettings
				,	d[sSettings]			// options.panes.fxSettings_open
				,	o[sSettings]			// options.west.fxSettings_open
				);
			});

			// DONE creating action-specific-settings for this pane,
			// so DELETE generic options - are no longer meaningful
			delete o.fxName;
			delete o.fxSpeed;
			delete o.fxSettings;
		}
	}

	/**
	* Initialize module objects, styling, size and position for all panes
	*
	* @see  _initElements()
	* @param {string}	pane		The pane to process
	*/
,	getPane = function (pane) {
		var sel = options[pane].paneSelector
		if (sel.substr(0,1)==="#") // ID selector
			// NOTE: elements selected 'by ID' DO NOT have to be 'children'
			return $N.find(sel).eq(0);
		else { // class or other selector
			var $P = $N.children(sel).eq(0);
			// look for the pane nested inside a 'form' element
			return $P.length ? $P : $N.children("form:first").children(sel).eq(0);
		}
	}

	/**
	* @param {Object=}		evt
	*/
,	initPanes = function (evt) {
		// stopPropagation if called by trigger("layoutinitpanes") - use evtPane utility 
		evtPane(evt);

		// NOTE: do north & south FIRST so we can measure their height - do center LAST
		$.each(_c.allPanes, function (idx, pane) {
			addPane( pane, true );
		});

		// init the pane-handles NOW in case we have to hide or close the pane below
		initHandles();

		// now that all panes have been initialized and initially-sized,
		// make sure there is really enough space available for each pane
		$.each(_c.borderPanes, function (i, pane) {
			if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN
				setSizeLimits(pane);
				makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit()
			}
		});
		// size center-pane AGAIN in case we 'closed' a border-pane in loop above
		sizeMidPanes("center");

		//	Chrome/Webkit sometimes fires callbacks BEFORE it completes resizing!
		//	Before RC30.3, there was a 10ms delay here, but that caused layout 
		//	to load asynchrously, which is BAD, so try skipping delay for now

		// process pane contents and callbacks, and init/resize child-layout if exists
		$.each(_c.allPanes, function (idx, pane) {
			afterInitPane(pane);
		});
	}

	/**
	* Add a pane to the layout - subroutine of initPanes()
	*
	* @see  initPanes()
	* @param {string}	pane			The pane to process
	* @param {boolean=}	[force=false]	Size content after init
	*/
,	addPane = function (pane, force) {
		if (!force && !isInitialized()) return;
		var
			o		= options[pane]
		,	s		= state[pane]
		,	c		= _c[pane]
		,	dir		= c.dir
		,	fx		= s.fx
		,	spacing	= o.spacing_open || 0
		,	isCenter = (pane === "center")
		,	CSS		= {}
		,	$P		= $Ps[pane]
		,	size, minSize, maxSize, child
		;
		// if pane-pointer already exists, remove the old one first
		if ($P)
			removePane( pane, false, true, false );
		else
			$Cs[pane] = false; // init

		$P = $Ps[pane] = getPane(pane);
		if (!$P.length) {
			$Ps[pane] = false; // logic
			return;
		}

		// SAVE original Pane CSS
		if (!$P.data("layoutCSS")) {
			var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";
			$P.data("layoutCSS", styles($P, props));
		}

		// create alias for pane data in Instance - initHandles will add more
		Instance[pane] = {
			name:		pane
		,	pane:		$Ps[pane]
		,	content:	$Cs[pane]
		,	options:	options[pane]
		,	state:		state[pane]
		,	children:	children[pane]
		};

		// add classes, attributes & events
		$P	.data({
				parentLayout:	Instance		// pointer to Layout Instance
			,	layoutPane:		Instance[pane]	// NEW pointer to pane-alias-object
			,	layoutEdge:		pane
			,	layoutRole:		"pane"
			})
			.css(c.cssReq).css("zIndex", options.zIndexes.pane_normal)
			.css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles
			.addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
			.bind("mouseenter."+ sID, addHover )
			.bind("mouseleave."+ sID, removeHover )
			;
		var paneMethods = {
				hide:				''
			,	show:				''
			,	toggle:				''
			,	close:				''
			,	open:				''
			,	slideOpen:			''
			,	slideClose:			''
			,	slideToggle:		''
			,	size:				'sizePane'
			,	sizePane:			'sizePane'
			,	sizeContent:		''
			,	sizeHandles:		''
			,	enableClosable:		''
			,	disableClosable:	''
			,	enableSlideable:	''
			,	disableSlideable:	''
			,	enableResizable:	''
			,	disableResizable:	''
			,	swapPanes:			'swapPanes'
			,	swap:				'swapPanes'
			,	move:				'swapPanes'
			,	removePane:			'removePane'
			,	remove:				'removePane'
			,	createChildren:		''
			,	resizeChildren:		''
			,	resizeAll:			'resizeAll'
			,	resizeLayout:		'resizeAll'
			}
		,	name;
		// loop hash and bind all methods - include layoutID namespacing
		for (name in paneMethods) {
			$P.bind("layoutpane"+ name.toLowerCase() +"."+ sID, Instance[ paneMethods[name] || name ]);
		}

		// see if this pane has a 'scrolling-content element'
		initContent(pane, false); // false = do NOT sizeContent() - called later

		if (!isCenter) {
			// call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden)
			// if o.size is auto or not valid, then MEASURE the pane and use that as its 'size'
			size	= s.size = _parseSize(pane, o.size);
			minSize	= _parseSize(pane,o.minSize) || 1;
			maxSize	= _parseSize(pane,o.maxSize) || 100000;
			if (size > 0) size = max(min(size, maxSize), minSize);
			s.autoResize = o.autoResize; // used with percentage sizes

			// state for border-panes
			s.isClosed  = false; // true = pane is closed
			s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
			s.isResizing= false; // true = pane is in process of being resized
			s.isHidden	= false; // true = pane is hidden - no spacing, resizer or toggler is visible!

			// array for 'pin buttons' whose classNames are auto-updated on pane-open/-close
			if (!s.pins) s.pins = [];
		}
		//	states common to ALL panes
		s.tagName	= $P[0].tagName;
		s.edge		= pane;		// useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going)
		s.noRoom	= false;	// true = pane 'automatically' hidden due to insufficient room - will unhide automatically
		s.isVisible	= true;		// false = pane is invisible - closed OR hidden - simplify logic

		// init pane positioning
		setPanePosition( pane );

		// if pane is not visible, 
		if (dir === "horz") // north or south pane
			CSS.height = cssH($P, size);
		else if (dir === "vert") // east or west pane
			CSS.width = cssW($P, size);
		//else if (isCenter) {}

		$P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes
		if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback

		// close or hide the pane if specified in settings
		if (o.initClosed && o.closable && !o.initHidden)
			close(pane, true, true); // true, true = force, noAnimation
		else if (o.initHidden || o.initClosed)
			hide(pane); // will be completely invisible - no resizer or spacing
		else if (!s.noRoom)
			// make the pane visible - in case was initially hidden
			$P.css("display","block");
		// ELSE setAsOpen() - called later by initHandles()

		// RESET visibility now - pane will appear IF display:block
		$P.css("visibility","visible");

		// check option for auto-handling of pop-ups & drop-downs
		if (o.showOverflowOnHover)
			$P.hover( allowOverflow, resetOverflow );

		// if manually adding a pane AFTER layout initialization, then...
		if (state.initialized) {
			initHandles( pane );
			initHotkeys( pane );
			afterInitPane( pane );
		}
	}

,	afterInitPane = function (pane) {
		var	$P	= $Ps[pane]
		,	s	= state[pane]
		,	o	= options[pane]
		;
		if (!$P) return;

		// see if there is a directly-nested layout inside this pane
		if ($P.data("layout"))
			refreshChildren( pane, $P.data("layout") );

		// process pane contents and callbacks, and init/resize child-layout if exists
		if (s.isVisible) { // pane is OPEN
			if (state.initialized) // this pane was added AFTER layout was created
				resizeAll(); // will also sizeContent
			else
				sizeContent(pane);

			if (o.triggerEventsOnLoad)
				_runCallbacks("onresize_end", pane);
			else // automatic if onresize called, otherwise call it specifically
				// resize child - IF inner-layout already exists (created before this layout)
				resizeChildren(pane, true); // a previously existing childLayout
		}

		// init childLayouts - even if pane is not visible
		if (o.initChildren && o.children)
			createChildren(pane);
	}

	/**
	* @param {string=}	panes		The pane(s) to process
	*/
,	setPanePosition = function (panes) {
		panes = panes ? panes.split(",") : _c.borderPanes;

		// create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
		$.each(panes, function (i, pane) {
			var $P	= $Ps[pane]
			,	$R	= $Rs[pane]
			,	o	= options[pane]
			,	s	= state[pane]
			,	side =  _c[pane].side
			,	CSS	= {}
			;
			if (!$P) return; // pane does not exist - skip

			// set css-position to account for container borders & padding
			switch (pane) {
				case "north": 	CSS.top 	= sC.inset.top;
								CSS.left 	= sC.inset.left;
								CSS.right	= sC.inset.right;
								break;
				case "south": 	CSS.bottom	= sC.inset.bottom;
								CSS.left 	= sC.inset.left;
								CSS.right 	= sC.inset.right;
								break;
				case "west": 	CSS.left 	= sC.inset.left; // top, bottom & height set by sizeMidPanes()
								break;
				case "east": 	CSS.right 	= sC.inset.right; // ditto
								break;
				case "center":	// top, left, width & height set by sizeMidPanes()
			}
			// apply position
			$P.css(CSS); 

			// update resizer position
			if ($R && s.isClosed)
				$R.css(side, sC.inset[side]);
			else if ($R && !s.isHidden)
				$R.css(side, sC.inset[side] + getPaneSize(pane));
		});
	}

	/**
	* Initialize module objects, styling, size and position for all resize bars and toggler buttons
	*
	* @see  _create()
	* @param {string=}	[panes=""]	The edge(s) to process
	*/
,	initHandles = function (panes) {
		panes = panes ? panes.split(",") : _c.borderPanes;

		// create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
		$.each(panes, function (i, pane) {
			var $P		= $Ps[pane];
			$Rs[pane]	= false; // INIT
			$Ts[pane]	= false;
			if (!$P) return; // pane does not exist - skip

			var 
				o		= options[pane]
			,	s		= state[pane]
			,	c		= _c[pane]
			,	paneId	= o.paneSelector.substr(0,1) === "#" ? o.paneSelector.substr(1) : ""
			,	rClass	= o.resizerClass
			,	tClass	= o.togglerClass
			,	spacing	= (s.isVisible ? o.spacing_open : o.spacing_closed)
			,	_pane	= "-"+ pane // used for classNames
			,	_state	= (s.isVisible ? "-open" : "-closed") // used for classNames
			,	I		= Instance[pane]
				// INIT RESIZER BAR
			,	$R		= I.resizer = $Rs[pane] = $("<div></div>")
				// INIT TOGGLER BUTTON
			,	$T		= I.toggler = (o.closable ? $Ts[pane] = $("<div></div>") : false)
			;

			//if (s.isVisible && o.resizable) ... handled by initResizable
			if (!s.isVisible && o.slidable)
				$R.attr("title", o.tips.Slide).css("cursor", o.sliderCursor);

			$R	// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
				.attr("id", paneId ? paneId +"-resizer" : "" )
				.data({
					parentLayout:	Instance
				,	layoutPane:		Instance[pane]	// NEW pointer to pane-alias-object
				,	layoutEdge:		pane
				,	layoutRole:		"resizer"
				})
				.css(_c.resizers.cssReq).css("zIndex", options.zIndexes.resizer_normal)
				.css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles
				.addClass(rClass +" "+ rClass+_pane)
				.hover(addHover, removeHover) // ALWAYS add hover-classes, even if resizing is not enabled - handle with CSS instead
				.hover(onResizerEnter, onResizerLeave) // ALWAYS NEED resizer.mouseleave to balance toggler.mouseenter
				.appendTo($N) // append DIV to container
			;
			if (o.resizerDblClickToggle)
				$R.bind("dblclick."+ sID, toggle );

			if ($T) {
				$T	// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler"
					.attr("id", paneId ? paneId +"-toggler" : "" )
					.data({
						parentLayout:	Instance
					,	layoutPane:		Instance[pane]	// NEW pointer to pane-alias-object
					,	layoutEdge:		pane
					,	layoutRole:		"toggler"
					})
					.css(_c.togglers.cssReq) // add base/required styles
					.css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles
					.addClass(tClass +" "+ tClass+_pane)
					.hover(addHover, removeHover) // ALWAYS add hover-classes, even if toggling is not enabled - handle with CSS instead
					.bind("mouseenter", onResizerEnter) // NEED toggler.mouseenter because mouseenter MAY NOT fire on resizer
					.appendTo($R) // append SPAN to resizer DIV
				;
				// ADD INNER-SPANS TO TOGGLER
				if (o.togglerContent_open) // ui-layout-open
					$("<span>"+ o.togglerContent_open +"</span>")
						.data({
							layoutEdge:		pane
						,	layoutRole:		"togglerContent"
						})
						.data("layoutRole", "togglerContent")
						.data("layoutEdge", pane)
						.addClass("content content-open")
						.css("display","none")
						.appendTo( $T )
						//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead!
					;
				if (o.togglerContent_closed) // ui-layout-closed
					$("<span>"+ o.togglerContent_closed +"</span>")
						.data({
							layoutEdge:		pane
						,	layoutRole:		"togglerContent"
						})
						.addClass("content content-closed")
						.css("display","none")
						.appendTo( $T )
						//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead!
					;
				// ADD TOGGLER.click/.hover
				enableClosable(pane);
			}

			// add Draggable events
			initResizable(pane);

			// ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open"
			if (s.isVisible)
				setAsOpen(pane);	// onOpen will be called, but NOT onResize
			else {
				setAsClosed(pane);	// onClose will be called
				bindStartSlidingEvents(pane, true); // will enable events IF option is set
			}

		});

		// SET ALL HANDLE DIMENSIONS
		sizeHandles();
	}


	/**
	* Initialize scrolling ui-layout-content div - if exists
	*
	* @see  initPane() - or externally after an Ajax injection
	* @param {string}	pane			The pane to process
	* @param {boolean=}	[resize=true]	Size content after init
	*/
,	initContent = function (pane, resize) {
		if (!isInitialized()) return;
		var 
			o	= options[pane]
		,	sel	= o.contentSelector
		,	I	= Instance[pane]
		,	$P	= $Ps[pane]
		,	$C
		;
		if (sel) $C = I.content = $Cs[pane] = (o.findNestedContent)
			? $P.find(sel).eq(0) // match 1-element only
			: $P.children(sel).eq(0)
		;
		if ($C && $C.length) {
			$C.data("layoutRole", "content");
			// SAVE original Content CSS
			if (!$C.data("layoutCSS"))
				$C.data("layoutCSS", styles($C, "height"));
			$C.css( _c.content.cssReq );
			if (o.applyDemoStyles) {
				$C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div
				$P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane
			}
			// ensure no vertical scrollbar on pane - will mess up measurements
			if ($P.css("overflowX").match(/(scroll|auto)/)) {
				$P.css("overflow", "hidden");
			}
			state[pane].content = {}; // init content state
			if (resize !== false) sizeContent(pane);
			// sizeContent() is called AFTER init of all elements
		}
		else
			I.content = $Cs[pane] = false;
	}


	/**
	* Add resize-bars to all panes that specify it in options
	* -dependancy: $.fn.resizable - will skip if not found
	*
	* @see  _create()
	* @param {string=}	[panes=""]	The edge(s) to process
	*/
,	initResizable = function (panes) {
		var	draggingAvailable = $.layout.plugins.draggable
		,	side // set in start()
		;
		panes = panes ? panes.split(",") : _c.borderPanes;

		$.each(panes, function (idx, pane) {
			var o = options[pane];
			if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
				o.resizable = false;
				return true; // skip to next
			}

			var s		= state[pane]
			,	z		= options.zIndexes
			,	c		= _c[pane]
			,	side	= c.dir=="horz" ? "top" : "left"
			,	$P 		= $Ps[pane]
			,	$R		= $Rs[pane]
			,	base	= o.resizerClass
			,	lastPos	= 0 // used when live-resizing
			,	r, live // set in start because may change
			//	'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
			,	resizerClass		= base+"-drag"				// resizer-drag
			,	resizerPaneClass	= base+"-"+pane+"-drag"		// resizer-north-drag
			//	'helper' class is applied to the CLONED resizer-bar while it is being dragged
			,	helperClass			= base+"-dragging"			// resizer-dragging
			,	helperPaneClass		= base+"-"+pane+"-dragging" // resizer-north-dragging
			,	helperLimitClass	= base+"-dragging-limit"	// resizer-drag
			,	helperPaneLimitClass = base+"-"+pane+"-dragging-limit"	// resizer-north-drag
			,	helperClassesSet	= false 					// logic var
			;

			if (!s.isClosed)
				$R.attr("title", o.tips.Resize)
				  .css("cursor", o.resizerCursor); // n-resize, s-resize, etc

			$R.draggable({
				containment:	$N[0] // limit resizing to layout container
			,	axis:			(c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis
			,	delay:			0
			,	distance:		1
			,	grid:			o.resizingGrid
			//	basic format for helper - style it using class: .ui-draggable-dragging
			,	helper:			"clone"
			,	opacity:		o.resizerDragOpacity
			,	addClasses:		false // avoid ui-state-disabled class when disabled
			//,	iframeFix:		o.draggableIframeFix // TODO: consider using when bug is fixed
			,	zIndex:			z.resizer_drag

			,	start: function (e, ui) {
					// REFRESH options & state pointers in case we used swapPanes
					o = options[pane];
					s = state[pane];
					// re-read options
					live = o.livePaneResizing;

					// ondrag_start callback - will CANCEL hide if returns false
					// TODO: dragging CANNOT be cancelled like this, so see if there is a way?
					if (false === _runCallbacks("ondrag_start", pane)) return false;

					s.isResizing		= true; // prevent pane from closing while resizing
					state.paneResizing	= pane; // easy to see if ANY pane is resizing
					timer.clear(pane+"_closeSlider"); // just in case already triggered

					// SET RESIZER LIMITS - used in drag()
					setSizeLimits(pane); // update pane/resizer state
					r = s.resizerPosition;
					lastPos = ui.position[ side ]

					$R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes
					helperClassesSet = false; // reset logic var - see drag()

					// DISABLE TEXT SELECTION (probably already done by resizer.mouseOver)
					$('body').disableSelection(); 

					// MASK PANES CONTAINING IFRAMES, APPLETS OR OTHER TROUBLESOME ELEMENTS
					showMasks( pane );
				}

			,	drag: function (e, ui) {
					if (!helperClassesSet) { // can only add classes after clone has been added to the DOM
						//$(".ui-draggable-dragging")
						ui.helper
							.addClass( helperClass +" "+ helperPaneClass ) // add helper classes
							.css({ right: "auto", bottom: "auto" })	// fix dir="rtl" issue
							.children().css("visibility","hidden")	// hide toggler inside dragged resizer-bar
						;
						helperClassesSet = true;
						// draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
						if (s.isSliding) $Ps[pane].css("zIndex", z.pane_sliding);
					}
					// CONTAIN RESIZER-BAR TO RESIZING LIMITS
					var limit = 0;
					if (ui.position[side] < r.min) {
						ui.position[side] = r.min;
						limit = -1;
					}
					else if (ui.position[side] > r.max) {
						ui.position[side] = r.max;
						limit = 1;
					}
					// ADD/REMOVE dragging-limit CLASS
					if (limit) {
						ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit
						window.defaultStatus = (limit>0 && pane.match(/(north|west)/)) || (limit<0 && pane.match(/(south|east)/)) ? o.tips.maxSizeWarning : o.tips.minSizeWarning;
					}
					else {
						ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit
						window.defaultStatus = "";
					}
					// DYNAMICALLY RESIZE PANES IF OPTION ENABLED
					// won't trigger unless resizer has actually moved!
					if (live && Math.abs(ui.position[side] - lastPos) >= o.liveResizingTolerance) {
						lastPos = ui.position[side];
						resizePanes(e, ui, pane)
					}
				}

			,	stop: function (e, ui) {
					$('body').enableSelection(); // RE-ENABLE TEXT SELECTION
					window.defaultStatus = ""; // clear 'resizing limit' message from statusbar
					$R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer
					s.isResizing		= false;
					state.paneResizing	= false; // easy to see if ANY pane is resizing
					resizePanes(e, ui, pane, true); // true = resizingDone
				}

			});
		});

		/**
		* resizePanes
		*
		* Sub-routine called from stop() - and drag() if livePaneResizing
		*
		* @param {!Object}		evt
		* @param {!Object}		ui
		* @param {string}		pane
		* @param {boolean=}		[resizingDone=false]
		*/
		var resizePanes = function (evt, ui, pane, resizingDone) {
			var	dragPos	= ui.position
			,	c		= _c[pane]
			,	o		= options[pane]
			,	s		= state[pane]
			,	resizerPos
			;
			switch (pane) {
				case "north":	resizerPos = dragPos.top; break;
				case "west":	resizerPos = dragPos.left; break;
				case "south":	resizerPos = sC.layoutHeight - dragPos.top  - o.spacing_open; break;
				case "east":	resizerPos = sC.layoutWidth  - dragPos.left - o.spacing_open; break;
			};
			// remove container margin from resizer position to get the pane size
			var newSize = resizerPos - sC.inset[c.side];

			// Disable OR Resize Mask(s) created in drag.start
			if (!resizingDone) {
				// ensure we meet liveResizingTolerance criteria
				if (Math.abs(newSize - s.size) < o.liveResizingTolerance)
					return; // SKIP resize this time
				// resize the pane
				manualSizePane(pane, newSize, false, true); // true = noAnimation
				sizeMasks(); // resize all visible masks
			}
			else { // resizingDone
				// ondrag_end callback
				if (false !== _runCallbacks("ondrag_end", pane))
					manualSizePane(pane, newSize, false, true); // true = noAnimation
				hideMasks(true); // true = force hiding all masks even if one is 'sliding'
				if (s.isSliding) // RE-SHOW 'object-masks' so objects won't show through sliding pane
					showMasks( pane );
			}
		};
	}

	/**
	*	sizeMask
	*
	*	Needed to overlay a DIV over an IFRAME-pane because mask CANNOT be *inside* the pane
	*	Called when mask created, and during livePaneResizing
	*/
,	sizeMask = function () {
		var $M		= $(this)
		,	pane	= $M.data("layoutMask") // eg: "west"
		,	s		= state[pane]
		;
		// only masks over an IFRAME-pane need manual resizing
		if (s.tagName == "IFRAME" && s.isVisible) // no need to mask closed/hidden panes
			$M.css({
				top:	s.offsetTop
			,	left:	s.offsetLeft
			,	width:	s.outerWidth
			,	height:	s.outerHeight
			});
		/* ALT Method...
		var $P = $Ps[pane];
		$M.css( $P.position() ).css({ width: $P[0].offsetWidth, height: $P[0].offsetHeight });
		*/
	}
,	sizeMasks = function () {
		$Ms.each( sizeMask ); // resize all 'visible' masks
	}

	/**
	* @param {string}	pane		The pane being resized, animated or isSliding
	* @param {Object=}	[args]		(optional) Options: which masks to apply, and to which panes
	*/
,	showMasks = function (pane, args) {
		var	c		= _c[pane]
		,	panes	=  ["center"]
		,	z		= options.zIndexes
		,	a		= $.extend({ objectsOnly: false, forSliding: state[pane].isSliding }, args )
		,	o, s
		;
		if (a.forSliding)
			panes.push( _c.oppositeEdge[pane] ); // ADD the oppositeEdge-pane

		if (c.dir === "horz") {
			panes.push("west");
			panes.push("east");
		}
		$.each(panes, function(i,p){
			s = state[p];
			o = options[p];
			if (s.isVisible && ( o.maskObjects || (!a.objectsOnly && o.maskContents) )) {
				getMasks(p).each(function(){
					sizeMask.call(this);
					this.style.zIndex = s.isSliding ? z.pane_sliding+1 : z.pane_normal+1
					this.style.display = "block";
				});
			}
		});
	}

	/**
	* @param {boolean=}	force		Hide masks even if a pane is sliding
	*/
,	hideMasks = function (force) {
		// ensure no pane is resizing - could be a timing issue
		if (force || !state.paneResizing) {
			$Ms.hide(); // hide ALL masks
		}
		// if ANY pane is sliding, then DO NOT remove masks from panes with maskObjects enabled
		else if (!force && !$.isEmptyObject( state.panesSliding )) {
			var	i = $Ms.length - 1
			,	p, $M;
			for (; i >= 0; i--) {
				$M	= $Ms.eq(i);
				p	= $M.data("layoutMask");
				if (!options[p].maskObjects) {
					$M.hide();
				}
			}
		}
	}

	/**
	* @param {string}	pane
	*/
,	getMasks = function (pane) {
		var $Masks	= $([])
		,	$M, i = 0, c = $Ms.length
		;
		for (; i<c; i++) {
			$M = $Ms.eq(i);
			if ($M.data("layoutMask") === pane)
				$Masks = $Masks.add( $M );
		}
		if ($Masks.length)
			return $Masks;
		else
			return createMasks(pane);
	}

	/**
	* createMasks
	*
	* Generates both DIV (ALWAYS used) and IFRAME (optional) elements as masks
	* An IFRAME mask is created *under* the DIV when maskObjects=true, because a DIV cannot mask an applet
	*
	* @param {string}	pane
	*/
,	createMasks = function (pane) {
		var
			$P		= $Ps[pane]
		,	s		= state[pane]
		,	o		= options[pane]
		,	z		= options.zIndexes
		//,	objMask	= o.maskObjects && s.tagName != "IFRAME" // check for option
		,	$Masks	= $([])
		,	isIframe, el, $M, css, i
		;
		if (!o.maskContents && !o.maskObjects) return $Masks;
		// if o.maskObjects=true, then loop TWICE to create BOTH kinds of mask, else only create a DIV
		for (i=0; i < (o.maskObjects ? 2 : 1); i++) {
			isIframe = o.maskObjects && i==0;
			el = document.createElement( isIframe ? "iframe" : "div" );
			$M = $(el).data("layoutMask", pane); // add data to relate mask to pane
			el.className = "ui-layout-mask ui-layout-mask-"+ pane; // for user styling
			css = el.style;
			// styles common to both DIVs and IFRAMES
			css.display		= "block";
			css.position	= "absolute";
			css.background	= "#FFF";
			if (isIframe) { // IFRAME-only props
				el.frameborder = 0;
				el.src		= "about:blank";
				//el.allowTransparency = true; - for IE, but breaks masking ability!
				css.opacity	= 0;
				css.filter	= "Alpha(Opacity='0')";
				css.border	= 0;
			}
			// if pane is an IFRAME, then must mask the pane itself
			if (s.tagName == "IFRAME") {
				// NOTE sizing done by a subroutine so can be called during live-resizing
				css.zIndex	= z.pane_normal+1; // 1-higher than pane
				$N.append( el ); // append to LAYOUT CONTAINER
			}
			// otherwise put masks *inside the pane* to mask its contents
			else {
				$M.addClass("ui-layout-mask-inside-pane");
				css.zIndex	= o.maskZindex || z.content_mask; // usually 1, but customizable
				css.top		= 0;
				css.left	= 0;
				css.width	= "100%";
				css.height	= "100%";
				$P.append( el ); // append INSIDE pane element
			}
			// add to return object
			$Masks = $Masks.add( el );
			// add Mask to cached array so can be resized & reused
			$Ms = $Ms.add( el );
		}
		return $Masks;
	}


	/**
	* Destroy this layout and reset all elements
	*
	* @param {boolean=}	[destroyChildren=false]		Destory Child-Layouts first?
	*/
,	destroy = function (evt_or_destroyChildren, destroyChildren) {
		// UNBIND layout events and remove global object
		$(window).unbind("."+ sID);		// resize & unload
		$(document).unbind("."+ sID);	// keyDown (hotkeys)

		if (typeof evt_or_destroyChildren === "object")
			// stopPropagation if called by trigger("layoutdestroy") - use evtPane utility 
			evtPane(evt_or_destroyChildren);
		else // no event, so transfer 1st param to destroyChildren param
			destroyChildren = evt_or_destroyChildren;

		// need to look for parent layout BEFORE we remove the container data, else skips a level
		//var parentPane = Instance.hasParentLayout ? $.layout.getParentPaneInstance( $N ) : null;

		// reset layout-container
		$N	.clearQueue()
			.removeData("layout")
			.removeData("layoutContainer")
			.removeClass(options.containerClass)
			.unbind("."+ sID) // remove ALL Layout events
		;

		// remove all mask elements that have been created
		$Ms.remove();

		// loop all panes to remove layout classes, attributes and bindings
		$.each(_c.allPanes, function (i, pane) {
			removePane( pane, false, true, destroyChildren ); // true = skipResize
		});

		// do NOT reset container CSS if is a 'pane' (or 'content') in an outer-layout - ie, THIS layout is 'nested'
		var css = "layoutCSS";
		if ($N.data(css) && !$N.data("layoutRole")) // RESET CSS
			$N.css( $N.data(css) ).removeData(css);

		// for full-page layouts, also reset the <HTML> CSS
		if (sC.tagName === "BODY" && ($N = $("html")).data(css)) // RESET <HTML> CSS
			$N.css( $N.data(css) ).removeData(css);

		// trigger plugins for this layout, if there are any
		runPluginCallbacks( Instance, $.layout.onDestroy );

		// trigger state-management and onunload callback
		unload();

		// clear the Instance of everything except for container & options (so could recreate)
		// RE-CREATE: myLayout = myLayout.container.layout( myLayout.options );
		for (var n in Instance)
			if (!n.match(/^(container|options)$/)) delete Instance[ n ];
		// add a 'destroyed' flag to make it easy to check
		Instance.destroyed = true;

		// if this is a child layout, CLEAR the child-pointer in the parent
		/* for now the pointer REMAINS, but with only container, options and destroyed keys
		if (parentPane) {
			var layout	= parentPane.pane.data("parentLayout")
			,	key		= layout.options.instanceKey || 'error';
			// THIS SYNTAX MAY BE WRONG!
			parentPane.children[key] = layout.children[ parentPane.name ].children[key] = null;
		}
		*/

		return Instance; // for coding convenience
	}

	/**
	* Remove a pane from the layout - subroutine of destroy()
	*
	* @see  destroy()
	* @param {(string|Object)}	evt_or_pane			The pane to process
	* @param {boolean=}			[remove=false]		Remove the DOM element?
	* @param {boolean=}			[skipResize=false]	Skip calling resizeAll()?
	* @param {boolean=}			[destroyChild=true]	Destroy Child-layouts? If not passed, obeys options setting
	*/
,	removePane = function (evt_or_pane, remove, skipResize, destroyChild) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$P	= $Ps[pane]
		,	$C	= $Cs[pane]
		,	$R	= $Rs[pane]
		,	$T	= $Ts[pane]
		;
		// NOTE: elements can still exist even after remove()
		//		so check for missing data(), which is cleared by removed()
		if ($P && $.isEmptyObject( $P.data() )) $P = false;
		if ($C && $.isEmptyObject( $C.data() )) $C = false;
		if ($R && $.isEmptyObject( $R.data() )) $R = false;
		if ($T && $.isEmptyObject( $T.data() )) $T = false;

		if ($P) $P.stop(true, true);

		var	o	= options[pane]
		,	s	= state[pane]
		,	d	= "layout"
		,	css	= "layoutCSS"
		,	pC	= children[pane]
		,	hasChildren	= $.isPlainObject( pC ) && !$.isEmptyObject( pC )
		,	destroy		= destroyChild !== undefined ? destroyChild : o.destroyChildren
		;
		// FIRST destroy the child-layout(s)
		if (hasChildren && destroy) {
			$.each( pC, function (key, child) {
				if (!child.destroyed)
					child.destroy(true);// tell child-layout to destroy ALL its child-layouts too
				if (child.destroyed)	// destroy was successful
					delete pC[key];
			});
			// if no more children, remove the children hash
			if ($.isEmptyObject( pC )) {
				pC = children[pane] = null; // clear children hash
				hasChildren = false;
			}
		}

		// Note: can't 'remove' a pane element with non-destroyed children
		if ($P && remove && !hasChildren)
			$P.remove(); // remove the pane-element and everything inside it
		else if ($P && $P[0]) {
			//	create list of ALL pane-classes that need to be removed
			var	root	= o.paneClass // default="ui-layout-pane"
			,	pRoot	= root +"-"+ pane // eg: "ui-layout-pane-west"
			,	_open	= "-open"
			,	_sliding= "-sliding"
			,	_closed	= "-closed"
			,	classes	= [	root, root+_open, root+_closed, root+_sliding,		// generic classes
							pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ]	// pane-specific classes
			;
			$.merge(classes, getHoverClasses($P, true)); // ADD hover-classes
			// remove all Layout classes from pane-element
			$P	.removeClass( classes.join(" ") ) // remove ALL pane-classes
				.removeData("parentLayout")
				.removeData("layoutPane")
				.removeData("layoutRole")
				.removeData("layoutEdge")
				.removeData("autoHidden")	// in case set
				.unbind("."+ sID) // remove ALL Layout events
				// TODO: remove these extra unbind commands when jQuery is fixed
				//.unbind("mouseenter"+ sID)
				//.unbind("mouseleave"+ sID)
			;
			// do NOT reset CSS if this pane/content is STILL the container of a nested layout!
			// the nested layout will reset its 'container' CSS when/if it is destroyed
			if (hasChildren && $C) {
				// a content-div may not have a specific width, so give it one to contain the Layout
				$C.width( $C.width() );
				$.each( pC, function (key, child) {
					child.resizeAll(); // resize the Layout
				});
			}
			else if ($C)
				$C.css( $C.data(css) ).removeData(css).removeData("layoutRole");
			// remove pane AFTER content in case there was a nested layout
			if (!$P.data(d))
				$P.css( $P.data(css) ).removeData(css);
		}

		// REMOVE pane resizer and toggler elements
		if ($T) $T.remove();
		if ($R) $R.remove();

		// CLEAR all pointers and state data
		Instance[pane] = $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = false;
		s = { removed: true };

		if (!skipResize)
			resizeAll();
	}


/*
 * ###########################
 *	   ACTION METHODS
 * ###########################
 */

	/**
	* @param {string}	pane
	*/
,	_hidePane = function (pane) {
		var $P	= $Ps[pane]
		,	o	= options[pane]
		,	s	= $P[0].style
		;
		if (o.useOffscreenClose) {
			if (!$P.data(_c.offscreenReset))
				$P.data(_c.offscreenReset, { left: s.left, right: s.right });
			$P.css( _c.offscreenCSS );
		}
		else
			$P.hide().removeData(_c.offscreenReset);
	}

	/**
	* @param {string}	pane
	*/
,	_showPane = function (pane) {
		var $P	= $Ps[pane]
		,	o	= options[pane]
		,	off	= _c.offscreenCSS
		,	old	= $P.data(_c.offscreenReset)
		,	s	= $P[0].style
		;
		$P	.show() // ALWAYS show, just in case
			.removeData(_c.offscreenReset);
		if (o.useOffscreenClose && old) {
			if (s.left == off.left)
				s.left = old.left;
			if (s.right == off.right)
				s.right = old.right;
		}
	}


	/**
	* Completely 'hides' a pane, including its spacing - as if it does not exist
	* The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
	*
	* @param {(string|Object)}	evt_or_pane			The pane being hidden, ie: north, south, east, or west
	* @param {boolean=}			[noAnimation=false]	
	*/
,	hide = function (evt_or_pane, noAnimation) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	o	= options[pane]
		,	s	= state[pane]
		,	$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		;
		if (!$P || s.isHidden) return; // pane does not exist OR is already hidden

		// onhide_start callback - will CANCEL hide if returns false
		if (state.initialized && false === _runCallbacks("onhide_start", pane)) return;

		s.isSliding = false; // just in case
		delete state.panesSliding[pane];

		// now hide the elements
		if ($R) $R.hide(); // hide resizer-bar
		if (!state.initialized || s.isClosed) {
			s.isClosed = true; // to trigger open-animation on show()
			s.isHidden  = true;
			s.isVisible = false;
			if (!state.initialized)
				_hidePane(pane); // no animation when loading page
			sizeMidPanes(_c[pane].dir === "horz" ? "" : "center");
			if (state.initialized || o.triggerEventsOnLoad)
				_runCallbacks("onhide_end", pane);
		}
		else {
			s.isHiding = true; // used by onclose
			close(pane, false, noAnimation); // adjust all panes to fit
		}
	}

	/**
	* Show a hidden pane - show as 'closed' by default unless openPane = true
	*
	* @param {(string|Object)}	evt_or_pane			The pane being opened, ie: north, south, east, or west
	* @param {boolean=}			[openPane=false]
	* @param {boolean=}			[noAnimation=false]
	* @param {boolean=}			[noAlert=false]
	*/
,	show = function (evt_or_pane, openPane, noAnimation, noAlert) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	o	= options[pane]
		,	s	= state[pane]
		,	$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		;
		if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden

		// onshow_start callback - will CANCEL show if returns false
		if (false === _runCallbacks("onshow_start", pane)) return;

		s.isShowing = true; // used by onopen/onclose
		//s.isHidden  = false; - will be set by open/close - if not cancelled
		s.isSliding = false; // just in case
		delete state.panesSliding[pane];

		// now show the elements
		//if ($R) $R.show(); - will be shown by open/close
		if (openPane === false)
			close(pane, true); // true = force
		else
			open(pane, false, noAnimation, noAlert); // adjust all panes to fit
	}


	/**
	* Toggles a pane open/closed by calling either open or close
	*
	* @param {(string|Object)}	evt_or_pane		The pane being toggled, ie: north, south, east, or west
	* @param {boolean=}			[slide=false]
	*/
,	toggle = function (evt_or_pane, slide) {
		if (!isInitialized()) return;
		var	evt		= evtObj(evt_or_pane)
		,	pane	= evtPane.call(this, evt_or_pane)
		,	s		= state[pane]
		;
		if (evt) // called from to $R.dblclick OR triggerPaneEvent
			evt.stopImmediatePropagation();
		if (s.isHidden)
			show(pane); // will call 'open' after unhiding it
		else if (s.isClosed)
			open(pane, !!slide);
		else
			close(pane);
	}


	/**
	* Utility method used during init or other auto-processes
	*
	* @param {string}	pane   The pane being closed
	* @param {boolean=}	[setHandles=false]
	*/
,	_closePane = function (pane, setHandles) {
		var
			$P	= $Ps[pane]
		,	s	= state[pane]
		;
		_hidePane(pane);
		s.isClosed = true;
		s.isVisible = false;
		// UNUSED: if (setHandles) setAsClosed(pane, true); // true = force
	}

	/**
	* Close the specified pane (animation optional), and resize all other panes as needed
	*
	* @param {(string|Object)}	evt_or_pane			The pane being closed, ie: north, south, east, or west
	* @param {boolean=}			[force=false]
	* @param {boolean=}			[noAnimation=false]
	* @param {boolean=}			[skipCallback=false]
	*/
,	close = function (evt_or_pane, force, noAnimation, skipCallback) {
		var	pane = evtPane.call(this, evt_or_pane);
		// if pane has been initialized, but NOT the complete layout, close pane instantly
		if (!state.initialized && $Ps[pane]) {
			_closePane(pane); // INIT pane as closed
			return;
		}
		if (!isInitialized()) return;

		var
			$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		,	$T	= $Ts[pane]
		,	o	= options[pane]
		,	s	= state[pane]
		,	c	= _c[pane]
		,	doFX, isShowing, isHiding, wasSliding;

		// QUEUE in case another action/animation is in progress
		$N.queue(function( queueNext ){

			if ( !$P
			||	(!o.closable && !s.isShowing && !s.isHiding)	// invalid request // (!o.resizable && !o.closable) ???
			||	(!force && s.isClosed && !s.isShowing)			// already closed
			) return queueNext();

			// onclose_start callback - will CANCEL hide if returns false
			// SKIP if just 'showing' a hidden pane as 'closed'
			var abort = !s.isShowing && false === _runCallbacks("onclose_start", pane);

			// transfer logic vars to temp vars
			isShowing	= s.isShowing;
			isHiding	= s.isHiding;
			wasSliding	= s.isSliding;
			// now clear the logic vars (REQUIRED before aborting)
			delete s.isShowing;
			delete s.isHiding;

			if (abort) return queueNext();

			doFX		= !noAnimation && !s.isClosed && (o.fxName_close != "none");
			s.isMoving	= true;
			s.isClosed	= true;
			s.isVisible	= false;
			// update isHidden BEFORE sizing panes
			if (isHiding) s.isHidden = true;
			else if (isShowing) s.isHidden = false;

			if (s.isSliding) // pane is being closed, so UNBIND trigger events
				bindStopSlidingEvents(pane, false); // will set isSliding=false
			else // resize panes adjacent to this one
				sizeMidPanes(_c[pane].dir === "horz" ? "" : "center", false); // false = NOT skipCallback

			// if this pane has a resizer bar, move it NOW - before animation
			setAsClosed(pane);

			// CLOSE THE PANE
			if (doFX) { // animate the close
				lockPaneForFX(pane, true);	// need to set left/top so animation will work
				$P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () {
					lockPaneForFX(pane, false); // undo
					if (s.isClosed) close_2();
					queueNext();
				});
			}
			else { // hide the pane without animation
				_hidePane(pane);
				close_2();
				queueNext();
			};
		});

		// SUBROUTINE
		function close_2 () {
			s.isMoving	= false;
			bindStartSlidingEvents(pane, true); // will enable if o.slidable = true

			// if opposite-pane was autoClosed, see if it can be autoOpened now
			var altPane = _c.oppositeEdge[pane];
			if (state[ altPane ].noRoom) {
				setSizeLimits( altPane );
				makePaneFit( altPane );
			}

			if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) {
				// onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
				if (!isShowing)	_runCallbacks("onclose_end", pane);
				// onhide OR onshow callback
				if (isShowing)	_runCallbacks("onshow_end", pane);
				if (isHiding)	_runCallbacks("onhide_end", pane);
			}
		}
	}

	/**
	* @param {string}	pane	The pane just closed, ie: north, south, east, or west
	*/
,	setAsClosed = function (pane) {
		var
			$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	$T		= $Ts[pane]
		,	o		= options[pane]
		,	s		= state[pane]
		,	side	= _c[pane].side
		,	rClass	= o.resizerClass
		,	tClass	= o.togglerClass
		,	_pane	= "-"+ pane // used for classNames
		,	_open	= "-open"
		,	_sliding= "-sliding"
		,	_closed	= "-closed"
		;
		$R
			.css(side, sC.inset[side]) // move the resizer
			.removeClass( rClass+_open +" "+ rClass+_pane+_open )
			.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
			.addClass( rClass+_closed +" "+ rClass+_pane+_closed )
		;
		// DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvents?
		if (o.resizable && $.layout.plugins.draggable)
			$R
				.draggable("disable")
				.removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here
				.css("cursor", "default")
				.attr("title","")
			;

		// if pane has a toggler button, adjust that too
		if ($T) {
			$T
				.removeClass( tClass+_open +" "+ tClass+_pane+_open )
				.addClass( tClass+_closed +" "+ tClass+_pane+_closed )
				.attr("title", o.tips.Open) // may be blank
			;
			// toggler-content - if exists
			$T.children(".content-open").hide();
			$T.children(".content-closed").css("display","block");
		}

		// sync any 'pin buttons'
		syncPinBtns(pane, false);

		if (state.initialized) {
			// resize 'length' and position togglers for adjacent panes
			sizeHandles();
		}
	}

	/**
	* Open the specified pane (animation optional), and resize all other panes as needed
	*
	* @param {(string|Object)}	evt_or_pane			The pane being opened, ie: north, south, east, or west
	* @param {boolean=}			[slide=false]
	* @param {boolean=}			[noAnimation=false]
	* @param {boolean=}			[noAlert=false]
	*/
,	open = function (evt_or_pane, slide, noAnimation, noAlert) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		,	$T	= $Ts[pane]
		,	o	= options[pane]
		,	s	= state[pane]
		,	c	= _c[pane]
		,	doFX, isShowing
		;
		// QUEUE in case another action/animation is in progress
		$N.queue(function( queueNext ){

			if ( !$P
			||	(!o.resizable && !o.closable && !s.isShowing)	// invalid request
			||	(s.isVisible && !s.isSliding)					// already open
			) return queueNext();

			// pane can ALSO be unhidden by just calling show(), so handle this scenario
			if (s.isHidden && !s.isShowing) {
				queueNext(); // call before show() because it needs the queue free
				show(pane, true);
				return;
			}

			if (s.autoResize && s.size != o.size) // resize pane to original size set in options
				sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize
			else
				// make sure there is enough space available to open the pane
				setSizeLimits(pane, slide);

			// onopen_start callback - will CANCEL open if returns false
			var cbReturn = _runCallbacks("onopen_start", pane);

			if (cbReturn === "abort")
				return queueNext();

			// update pane-state again in case options were changed in onopen_start
			if (cbReturn !== "NC") // NC = "No Callback"
				setSizeLimits(pane, slide);

			if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN!
				syncPinBtns(pane, false); // make sure pin-buttons are reset
				if (!noAlert && o.tips.noRoomToOpen)
					alert(o.tips.noRoomToOpen);
				return queueNext(); // ABORT
			}

			if (slide) // START Sliding - will set isSliding=true
				bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
			else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead
				bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false
			else if (o.slidable)
				bindStartSlidingEvents(pane, false); // UNBIND trigger events

			s.noRoom = false; // will be reset by makePaneFit if 'noRoom'
			makePaneFit(pane);

			// transfer logic var to temp var
			isShowing = s.isShowing;
			// now clear the logic var
			delete s.isShowing;

			doFX		= !noAnimation && s.isClosed && (o.fxName_open != "none");
			s.isMoving	= true;
			s.isVisible	= true;
			s.isClosed	= false;
			// update isHidden BEFORE sizing panes - WHY??? Old?
			if (isShowing) s.isHidden = false;

			if (doFX) { // ANIMATE
				// mask adjacent panes with objects
				lockPaneForFX(pane, true);	// need to set left/top so animation will work
					$P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
					lockPaneForFX(pane, false); // undo
					if (s.isVisible) open_2(); // continue
					queueNext();
				});
			}
			else { // no animation
				_showPane(pane);// just show pane and...
				open_2();		// continue
				queueNext();
			};
		});

		// SUBROUTINE
		function open_2 () {
			s.isMoving	= false;

			// cure iframe display issues
			_fixIframe(pane);

			// NOTE: if isSliding, then other panes are NOT 'resized'
			if (!s.isSliding) { // resize all panes adjacent to this one
				sizeMidPanes(_c[pane].dir=="vert" ? "center" : "", false); // false = NOT skipCallback
			}

			// set classes, position handles and execute callbacks...
			setAsOpen(pane);
		};
	
	}

	/**
	* @param {string}	pane		The pane just opened, ie: north, south, east, or west
	* @param {boolean=}	[skipCallback=false]
	*/
,	setAsOpen = function (pane, skipCallback) {
		var 
			$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	$T		= $Ts[pane]
		,	o		= options[pane]
		,	s		= state[pane]
		,	side	= _c[pane].side
		,	rClass	= o.resizerClass
		,	tClass	= o.togglerClass
		,	_pane	= "-"+ pane // used for classNames
		,	_open	= "-open"
		,	_closed	= "-closed"
		,	_sliding= "-sliding"
		;
		$R
			.css(side, sC.inset[side] + getPaneSize(pane)) // move the resizer
			.removeClass( rClass+_closed +" "+ rClass+_pane+_closed )
			.addClass( rClass+_open +" "+ rClass+_pane+_open )
		;
		if (s.isSliding)
			$R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
		else // in case 'was sliding'
			$R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )

		removeHover( 0, $R ); // remove hover classes
		if (o.resizable && $.layout.plugins.draggable)
			$R	.draggable("enable")
				.css("cursor", o.resizerCursor)
				.attr("title", o.tips.Resize);
		else if (!s.isSliding)
			$R.css("cursor", "default"); // n-resize, s-resize, etc

		// if pane also has a toggler button, adjust that too
		if ($T) {
			$T	.removeClass( tClass+_closed +" "+ tClass+_pane+_closed )
				.addClass( tClass+_open +" "+ tClass+_pane+_open )
				.attr("title", o.tips.Close); // may be blank
			removeHover( 0, $T ); // remove hover classes
			// toggler-content - if exists
			$T.children(".content-closed").hide();
			$T.children(".content-open").css("display","block");
		}

		// sync any 'pin buttons'
		syncPinBtns(pane, !s.isSliding);

		// update pane-state dimensions - BEFORE resizing content
		$.extend(s, elDims($P));

		if (state.initialized) {
			// resize resizer & toggler sizes for all panes
			sizeHandles();
			// resize content every time pane opens - to be sure
			sizeContent(pane, true); // true = remeasure headers/footers, even if 'pane.isMoving'
		}

		if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) {
			// onopen callback
			_runCallbacks("onopen_end", pane);
			// onshow callback - TODO: should this be here?
			if (s.isShowing) _runCallbacks("onshow_end", pane);

			// ALSO call onresize because layout-size *may* have changed while pane was closed
			if (state.initialized)
				_runCallbacks("onresize_end", pane);
		}

		// TODO: Somehow sizePane("north") is being called after this point???
	}


	/**
	* slideOpen / slideClose / slideToggle
	*
	* Pass-though methods for sliding
	*/
,	slideOpen = function (evt_or_pane) {
		if (!isInitialized()) return;
		var	evt		= evtObj(evt_or_pane)
		,	pane	= evtPane.call(this, evt_or_pane)
		,	s		= state[pane]
		,	delay	= options[pane].slideDelay_open
		;
		// prevent event from triggering on NEW resizer binding created below
		if (evt) evt.stopImmediatePropagation();

		if (s.isClosed && evt && evt.type === "mouseenter" && delay > 0)
			// trigger = mouseenter - use a delay
			timer.set(pane+"_openSlider", open_NOW, delay);
		else
			open_NOW(); // will unbind events if is already open

		/**
		* SUBROUTINE for timed open
		*/
		function open_NOW () {
			if (!s.isClosed) // skip if no longer closed!
				bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
			else if (!s.isMoving)
				open(pane, true); // true = slide - open() will handle binding
		};
	}

,	slideClose = function (evt_or_pane) {
		if (!isInitialized()) return;
		var	evt		= evtObj(evt_or_pane)
		,	pane	= evtPane.call(this, evt_or_pane)
		,	o		= options[pane]
		,	s		= state[pane]
		,	delay	= s.isMoving ? 1000 : 300 // MINIMUM delay - option may override
		;
		if (s.isClosed || s.isResizing)
			return; // skip if already closed OR in process of resizing
		else if (o.slideTrigger_close === "click")
			close_NOW(); // close immediately onClick
		else if (o.preventQuickSlideClose && s.isMoving)
			return; // handle Chrome quick-close on slide-open
		else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane]))
			return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE
		else if (evt) // trigger = mouseleave - use a delay
			// 1 sec delay if 'opening', else .3 sec
			timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay));
		else // called programically
			close_NOW();

		/**
		* SUBROUTINE for timed close
		*/
		function close_NOW () {
			if (s.isClosed) // skip 'close' if already closed!
				bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here?
			else if (!s.isMoving)
				close(pane); // close will handle unbinding
		};
	}

	/**
	* @param {(string|Object)}	evt_or_pane		The pane being opened, ie: north, south, east, or west
	*/
,	slideToggle = function (evt_or_pane) {
		var pane = evtPane.call(this, evt_or_pane);
		toggle(pane, true);
	}


	/**
	* Must set left/top on East/South panes so animation will work properly
	*
	* @param {string}	pane	The pane to lock, 'east' or 'south' - any other is ignored!
	* @param {boolean}	doLock  true = set left/top, false = remove
	*/
,	lockPaneForFX = function (pane, doLock) {
		var $P	= $Ps[pane]
		,	s	= state[pane]
		,	o	= options[pane]
		,	z	= options.zIndexes
		;
		if (doLock) {
			showMasks( pane, { objectsOnly: true });
			$P.css({ zIndex: z.pane_animate }); // overlay all elements during animation
			if (pane=="south")
				$P.css({ top: sC.inset.top + sC.innerHeight - $P.outerHeight() });
			else if (pane=="east")
				$P.css({ left: sC.inset.left + sC.innerWidth - $P.outerWidth() });
		}
		else { // animation DONE - RESET CSS
			hideMasks();
			$P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) });
			if (pane=="south")
				$P.css({ top: "auto" });
			// if pane is positioned 'off-screen', then DO NOT screw with it!
			else if (pane=="east" && !$P.css("left").match(/\-99999/))
				$P.css({ left: "auto" });
			// fix anti-aliasing in IE - only needed for animations that change opacity
			if (browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1)
				$P[0].style.removeAttribute('filter');
		}
	}


	/**
	* Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
	*
	* @see  open(), close()
	* @param {string}	pane	The pane to enable/disable, 'north', 'south', etc.
	* @param {boolean}	enable	Enable or Disable sliding?
	*/
,	bindStartSlidingEvents = function (pane, enable) {
		var o		= options[pane]
		,	$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	evtName	= o.slideTrigger_open.toLowerCase()
		;
		if (!$R || (enable && !o.slidable)) return;

		// make sure we have a valid event
		if (evtName.match(/mouseover/))
			evtName = o.slideTrigger_open = "mouseenter";
		else if (!evtName.match(/(click|dblclick|mouseenter)/)) 
			evtName = o.slideTrigger_open = "click";

		// must remove double-click-toggle when using dblclick-slide
		if (o.resizerDblClickToggle && evtName.match(/click/)) {
			$R[enable ? "unbind" : "bind"]('dblclick.'+ sID, toggle)
		}

		$R
			// add or remove event
			[enable ? "bind" : "unbind"](evtName +'.'+ sID, slideOpen)
			// set the appropriate cursor & title/tip
			.css("cursor", enable ? o.sliderCursor : "default")
			.attr("title", enable ? o.tips.Slide : "")
		;
	}

	/**
	* Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed
	* Also increases zIndex when pane is sliding open
	* See bindStartSlidingEvents for code to control 'slide open'
	*
	* @see  slideOpen(), slideClose()
	* @param {string}	pane	The pane to process, 'north', 'south', etc.
	* @param {boolean}	enable	Enable or Disable events?
	*/
,	bindStopSlidingEvents = function (pane, enable) {
		var	o		= options[pane]
		,	s		= state[pane]
		,	c		= _c[pane]
		,	z		= options.zIndexes
		,	evtName	= o.slideTrigger_close.toLowerCase()
		,	action	= (enable ? "bind" : "unbind")
		,	$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		;
		timer.clear(pane+"_closeSlider"); // just in case

		if (enable) {
			s.isSliding = true;
			state.panesSliding[pane] = true;
			// remove 'slideOpen' event from resizer
			// ALSO will raise the zIndex of the pane & resizer
			bindStartSlidingEvents(pane, false);
		}
		else {
			s.isSliding = false;
			delete state.panesSliding[pane];
		}

		// RE/SET zIndex - increases when pane is sliding-open, resets to normal when not
		$P.css("zIndex", enable ? z.pane_sliding : z.pane_normal);
		$R.css("zIndex", enable ? z.pane_sliding+2 : z.resizer_normal); // NOTE: mask = pane_sliding+1

		// make sure we have a valid event
		if (!evtName.match(/(click|mouseleave)/))
			evtName = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout'

		// add/remove slide triggers
		$R[action](evtName, slideClose); // base event on resize
		// need extra events for mouseleave
		if (evtName === "mouseleave") {
			// also close on pane.mouseleave
			$P[action]("mouseleave."+ sID, slideClose);
			// cancel timer when mouse moves between 'pane' and 'resizer'
			$R[action]("mouseenter."+ sID, cancelMouseOut);
			$P[action]("mouseenter."+ sID, cancelMouseOut);
		}

		if (!enable)
			timer.clear(pane+"_closeSlider");
		else if (evtName === "click" && !o.resizable) {
			// IF pane is not resizable (which already has a cursor and tip) 
			// then set the a cursor & title/tip on resizer when sliding
			$R.css("cursor", enable ? o.sliderCursor : "default");
			$R.attr("title", enable ? o.tips.Close : ""); // use Toggler-tip, eg: "Close Pane"
		}

		// SUBROUTINE for mouseleave timer clearing
		function cancelMouseOut (evt) {
			timer.clear(pane+"_closeSlider");
			evt.stopPropagation();
		}
	}


	/**
	* Hides/closes a pane if there is insufficient room - reverses this when there is room again
	* MUST have already called setSizeLimits() before calling this method
	*
	* @param {string}	pane					The pane being resized
	* @param {boolean=}	[isOpening=false]		Called from onOpen?
	* @param {boolean=}	[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}	[force=false]
	*/
,	makePaneFit = function (pane, isOpening, skipCallback, force) {
		var
			o	= options[pane]
		,	s	= state[pane]
		,	c	= _c[pane]
		,	$P	= $Ps[pane]
		,	$R	= $Rs[pane]
		,	isSidePane 	= c.dir==="vert"
		,	hasRoom		= false
		;
		// special handling for center & east/west panes
		if (pane === "center" || (isSidePane && s.noVerticalRoom)) {
			// see if there is enough room to display the pane
			// ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth);
			hasRoom = (s.maxHeight >= 0);
			if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now
				_showPane(pane);
				if ($R) $R.show();
				s.isVisible = true;
				s.noRoom = false;
				if (isSidePane) s.noVerticalRoom = false;
				_fixIframe(pane);
			}
			else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now
				_hidePane(pane);
				if ($R) $R.hide();
				s.isVisible = false;
				s.noRoom = true;
			}
		}

		// see if there is enough room to fit the border-pane
		if (pane === "center") {
			// ignore center in this block
		}
		else if (s.minSize <= s.maxSize) { // pane CAN fit
			hasRoom = true;
			if (s.size > s.maxSize) // pane is too big - shrink it
				sizePane(pane, s.maxSize, skipCallback, true, force); // true = noAnimation
			else if (s.size < s.minSize) // pane is too small - enlarge it
				sizePane(pane, s.minSize, skipCallback, true, force); // true = noAnimation
			// need s.isVisible because new pseudoClose method keeps pane visible, but off-screen
			else if ($R && s.isVisible && $P.is(":visible")) {
				// make sure resizer-bar is positioned correctly
				// handles situation where nested layout was 'hidden' when initialized
				var	pos = s.size + sC.inset[c.side];
				if ($.layout.cssNum( $R, c.side ) != pos) $R.css( c.side, pos );
			}

			// if was previously hidden due to noRoom, then RESET because NOW there is room
			if (s.noRoom) {
				// s.noRoom state will be set by open or show
				if (s.wasOpen && o.closable) {
					if (o.autoReopen)
						open(pane, false, true, true); // true = noAnimation, true = noAlert
					else // leave the pane closed, so just update state
						s.noRoom = false;
				}
				else
					show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert
			}
		}
		else { // !hasRoom - pane CANNOT fit
			if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now...
				s.noRoom = true; // update state
				s.wasOpen = !s.isClosed && !s.isSliding;
				if (s.isClosed){} // SKIP
				else if (o.closable) // 'close' if possible
					close(pane, true, true); // true = force, true = noAnimation
				else // 'hide' pane if cannot just be closed
					hide(pane, true); // true = noAnimation
			}
		}
	}


	/**
	* sizePane / manualSizePane
	* sizePane is called only by internal methods whenever a pane needs to be resized
	* manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized'
	*
	* @param {(string|Object)}	evt_or_pane				The pane being resized
	* @param {number}			size					The *desired* new size for this pane - will be validated
	* @param {boolean=}			[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}			[noAnimation=false]
	* @param {boolean=}			[force=false]			Force resizing even if does not seem necessary
	*/
,	manualSizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	o	= options[pane]
		,	s	= state[pane]
		//	if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete...
		,	forceResize = force || (o.livePaneResizing && !s.isResizing)
		;
		// ANY call to manualSizePane disables autoResize - ie, percentage sizing
		s.autoResize = false;
		// flow-through...
		sizePane(pane, size, skipCallback, noAnimation, forceResize); // will animate resize if option enabled
	}

	/**
	* @param {(string|Object)}	evt_or_pane				The pane being resized
	* @param {number}			size					The *desired* new size for this pane - will be validated
	* @param {boolean=}			[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}			[noAnimation=false]
	* @param {boolean=}			[force=false]			Force resizing even if does not seem necessary
	*/
,	sizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) {
		if (!isInitialized()) return;
		var	pane	= evtPane.call(this, evt_or_pane) // probably NEVER called from event?
		,	o		= options[pane]
		,	s		= state[pane]
		,	$P		= $Ps[pane]
		,	$R		= $Rs[pane]
		,	side	= _c[pane].side
		,	dimName	= _c[pane].sizeType.toLowerCase()
		,	skipResizeWhileDragging = s.isResizing && !o.triggerEventsDuringLiveResize
		,	doFX	= noAnimation !== true && o.animatePaneSizing
		,	oldSize, newSize
		;
		// QUEUE in case another action/animation is in progress
		$N.queue(function( queueNext ){
			// calculate 'current' min/max sizes
			setSizeLimits(pane); // update pane-state
			oldSize = s.size;
			size = _parseSize(pane, size); // handle percentages & auto
			size = max(size, _parseSize(pane, o.minSize));
			size = min(size, s.maxSize);
			if (size < s.minSize) { // not enough room for pane!
				queueNext(); // call before makePaneFit() because it needs the queue free
				makePaneFit(pane, false, skipCallback);	// will hide or close pane
				return;
			}

			// IF newSize is same as oldSize, then nothing to do - abort
			if (!force && size === oldSize)
				return queueNext();

			// onresize_start callback CANNOT cancel resizing because this would break the layout!
			if (!skipCallback && state.initialized && s.isVisible)
				_runCallbacks("onresize_start", pane);

			// resize the pane, and make sure its visible
			newSize = cssSize(pane, size);

			if (doFX && $P.is(":visible")) { // ANIMATE
				var fx		= $.layout.effects.size[pane] || $.layout.effects.size.all
				,	easing	= o.fxSettings_size.easing || fx.easing
				,	z		= options.zIndexes
				,	props	= {};
				props[ dimName ] = newSize +'px';
				s.isMoving = true;
				// overlay all elements during animation
				$P.css({ zIndex: z.pane_animate })
				  .show().animate( props, o.fxSpeed_size, easing, function(){
					// reset zIndex after animation
					$P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) });
					s.isMoving = false;
					sizePane_2(); // continue
					queueNext();
				});
			}
			else { // no animation
				$P.css( dimName, newSize );	// resize pane
				// if pane is visible, then 
				if ($P.is(":visible"))
					sizePane_2(); // continue
				else {
					// pane is NOT VISIBLE, so just update state data...
					// when pane is *next opened*, it will have the new size
					s.size = size;				// update state.size
					$.extend(s, elDims($P));	// update state dimensions
				}
				queueNext();
			};

		});

		// SUBROUTINE
		function sizePane_2 () {
			/*	Panes are sometimes not sized precisely in some browsers!?
			 *	This code will resize the pane up to 3 times to nudge the pane to the correct size
			 */
			var	actual	= dimName==='width' ? $P.outerWidth() : $P.outerHeight()
			,	tries	= [{
						   	pane:		pane
						,	count:		1
						,	target:		size
						,	actual:		actual
						,	correct:	(size === actual)
						,	attempt:	size
						,	cssSize:	newSize
						}]
			,	lastTry = tries[0]
			,	thisTry	= {}
			,	msg		= 'Inaccurate size after resizing the '+ pane +'-pane.'
			;
			while ( !lastTry.correct ) {
				thisTry = { pane: pane, count: lastTry.count+1, target: size };

				if (lastTry.actual > size)
					thisTry.attempt = max(0, lastTry.attempt - (lastTry.actual - size));
				else // lastTry.actual < size
					thisTry.attempt = max(0, lastTry.attempt + (size - lastTry.actual));

				thisTry.cssSize = cssSize(pane, thisTry.attempt);
				$P.css( dimName, thisTry.cssSize );

				thisTry.actual	= dimName=='width' ? $P.outerWidth() : $P.outerHeight();
				thisTry.correct	= (size === thisTry.actual);

				// log attempts and alert the user of this *non-fatal error* (if showDebugMessages)
				if ( tries.length === 1) {
					_log(msg, false, true);
					_log(lastTry, false, true);
				}
				_log(thisTry, false, true);
				// after 4 tries, is as close as its gonna get!
				if (tries.length > 3) break;

				tries.push( thisTry );
				lastTry = tries[ tries.length - 1 ];
			}
			// END TESTING CODE

			// update pane-state dimensions
			s.size	= size;
			$.extend(s, elDims($P));

			if (s.isVisible && $P.is(":visible")) {
				// reposition the resizer-bar
				if ($R) $R.css( side, size + sC.inset[side] );
				// resize the content-div
				sizeContent(pane);
			}

			if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible)
				_runCallbacks("onresize_end", pane);

			// resize all the adjacent panes, and adjust their toggler buttons
			// when skipCallback passed, it means the controlling method will handle 'other panes'
			if (!skipCallback) {
				// also no callback if live-resize is in progress and NOT triggerEventsDuringLiveResize
				if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "" : "center", skipResizeWhileDragging, force);
				sizeHandles();
			}

			// if opposite-pane was autoClosed, see if it can be autoOpened now
			var altPane = _c.oppositeEdge[pane];
			if (size < oldSize && state[ altPane ].noRoom) {
				setSizeLimits( altPane );
				makePaneFit( altPane, false, skipCallback );
			}

			// DEBUG - ALERT user/developer so they know there was a sizing problem
			if (tries.length > 1)
				_log(msg +'\nSee the Error Console for details.', true, true);
		}
	}

	/**
	* @see  initPanes(), sizePane(), 	resizeAll(), open(), close(), hide()
	* @param {(Array.<string>|string)}	panes					The pane(s) being resized, comma-delmited string
	* @param {boolean=}					[skipCallback=false]	Should the onresize callback be run?
	* @param {boolean=}					[force=false]
	*/
,	sizeMidPanes = function (panes, skipCallback, force) {
		panes = (panes ? panes : "east,west,center").split(",");

		$.each(panes, function (i, pane) {
			if (!$Ps[pane]) return; // NO PANE - skip
			var 
				o		= options[pane]
			,	s		= state[pane]
			,	$P		= $Ps[pane]
			,	$R		= $Rs[pane]
			,	isCenter= (pane=="center")
			,	hasRoom	= true
			,	CSS		= {}
			//	if pane is not visible, show it invisibly NOW rather than for *each call* in this script
			,	visCSS	= $.layout.showInvisibly($P)

			,	newCenter	= calcNewCenterPaneDims()
			;

			// update pane-state dimensions
			$.extend(s, elDims($P));

			if (pane === "center") {
				if (!force && s.isVisible && newCenter.width === s.outerWidth && newCenter.height === s.outerHeight) {
					$P.css(visCSS);
					return true; // SKIP - pane already the correct size
				}
				// set state for makePaneFit() logic
				$.extend(s, cssMinDims(pane), {
					maxWidth:	newCenter.width
				,	maxHeight:	newCenter.height
				});
				CSS = newCenter;
				// convert OUTER width/height to CSS width/height 
				CSS.width	= cssW($P, CSS.width);
				// NEW - allow pane to extend 'below' visible area rather than hide it
				CSS.height	= cssH($P, CSS.height);
				hasRoom		= CSS.width >= 0 && CSS.height >= 0; // height >= 0 = ALWAYS TRUE NOW

				// during layout init, try to shrink east/west panes to make room for center
				if (!state.initialized && o.minWidth > newCenter.width) {
					var
						reqPx	= o.minWidth - s.outerWidth
					,	minE	= options.east.minSize || 0
					,	minW	= options.west.minSize || 0
					,	sizeE	= state.east.size
					,	sizeW	= state.west.size
					,	newE	= sizeE
					,	newW	= sizeW
					;
					if (reqPx > 0 && state.east.isVisible && sizeE > minE) {
						newE = max( sizeE-minE, sizeE-reqPx );
						reqPx -= sizeE-newE;
					}
					if (reqPx > 0 && state.west.isVisible && sizeW > minW) {
						newW = max( sizeW-minW, sizeW-reqPx );
						reqPx -= sizeW-newW;
					}
					// IF we found enough extra space, then resize the border panes as calculated
					if (reqPx === 0) {
						if (sizeE && sizeE != minE)
							sizePane('east', newE, true, true, force); // true = skipCallback/noAnimation - initPanes will handle when done
						if (sizeW && sizeW != minW)
							sizePane('west', newW, true, true, force); // true = skipCallback/noAnimation
						// now start over!
						sizeMidPanes('center', skipCallback, force);
						$P.css(visCSS);
						return; // abort this loop
					}
				}
			}
			else { // for east and west, set only the height, which is same as center height
				// set state.min/maxWidth/Height for makePaneFit() logic
				if (s.isVisible && !s.noVerticalRoom)
					$.extend(s, elDims($P), cssMinDims(pane))
				if (!force && !s.noVerticalRoom && newCenter.height === s.outerHeight) {
					$P.css(visCSS);
					return true; // SKIP - pane already the correct size
				}
				// east/west have same top, bottom & height as center
				CSS.top		= newCenter.top;
				CSS.bottom	= newCenter.bottom;
				// NEW - allow pane to extend 'below' visible area rather than hide it
				CSS.height	= cssH($P, newCenter.height);
				s.maxHeight	= CSS.height;
				hasRoom		= (s.maxHeight >= 0); // ALWAYS TRUE NOW
				if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic
			}

			if (hasRoom) {
				// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
				if (!skipCallback && state.initialized)
					_runCallbacks("onresize_start", pane);

				$P.css(CSS); // apply the CSS to pane
				if (pane !== "center")
					sizeHandles(pane); // also update resizer length
				if (s.noRoom && !s.isClosed && !s.isHidden)
					makePaneFit(pane); // will re-open/show auto-closed/hidden pane
				if (s.isVisible) {
					$.extend(s, elDims($P)); // update pane dimensions
					if (state.initialized) sizeContent(pane); // also resize the contents, if exists
				}
			}
			else if (!s.noRoom && s.isVisible) // no room for pane
				makePaneFit(pane); // will hide or close pane

			// reset visibility, if necessary
			$P.css(visCSS);

			if (!s.isVisible)
				return true; // DONE - next pane

			/*
			* Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
			* Normally these panes have only 'left' & 'right' positions so pane auto-sizes
			* ALSO required when pane is an IFRAME because will NOT default to 'full width'
			*	TODO: Can I use width:100% for a north/south iframe?
			*	TODO: Sounds like a job for $P.outerWidth( sC.innerWidth ) SETTER METHOD
			*/
			if (pane === "center") { // finished processing midPanes
				var fix = browser.isIE6 || !browser.boxModel;
				if ($Ps.north && (fix || state.north.tagName=="IFRAME")) 
					$Ps.north.css("width", cssW($Ps.north, sC.innerWidth));
				if ($Ps.south && (fix || state.south.tagName=="IFRAME"))
					$Ps.south.css("width", cssW($Ps.south, sC.innerWidth));
			}

			// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
			if (!skipCallback && state.initialized)
				_runCallbacks("onresize_end", pane);
		});
	}


	/**
	* @see  window.onresize(), callbacks or custom code
	* @param {(Object|boolean)=}	evt_or_refresh	If 'true', then also reset pane-positioning
	*/
,	resizeAll = function (evt_or_refresh) {
		var	oldW	= sC.innerWidth
		,	oldH	= sC.innerHeight
		;
		// stopPropagation if called by trigger("layoutdestroy") - use evtPane utility 
		evtPane(evt_or_refresh);

		// cannot size layout when 'container' is hidden or collapsed
		if (!$N.is(":visible")) return;

		if (!state.initialized) {
			_initLayoutElements();
			return; // no need to resize since we just initialized!
		}

		$.extend(sC, elDims( $N, options.inset )); // UPDATE container dimensions
		if (!sC.outerHeight) return;

		// if 'true' passed, refresh pane & handle positioning too
		if (evt_or_refresh === true)
			setPanePosition();

		// onresizeall_start will CANCEL resizing if returns false
		// state.container has already been set, so user can access this info for calcuations
		if (false === _runCallbacks("onresizeall_start")) return false;

		var	// see if container is now 'smaller' than before
			shrunkH	= (sC.innerHeight < oldH)
		,	shrunkW	= (sC.innerWidth < oldW)
		,	$P, o, s
		;
		// NOTE special order for sizing: S-N-E-W
		$.each(["south","north","east","west"], function (i, pane) {
			if (!$Ps[pane]) return; // no pane - SKIP
			o = options[pane];
			s = state[pane];
			if (s.autoResize && s.size != o.size) // resize pane to original size set in options
				sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize
			else {
				setSizeLimits(pane);
				makePaneFit(pane, false, true, true); // true=skipCallback/forceResize
			}
		});

		sizeMidPanes("", true, true); // true=skipCallback/forceResize
		sizeHandles(); // reposition the toggler elements

		// trigger all individual pane callbacks AFTER layout has finished resizing
		$.each(_c.allPanes, function (i, pane) {
			$P = $Ps[pane];
			if (!$P) return; // SKIP
			if (state[pane].isVisible) // undefined for non-existent panes
				_runCallbacks("onresize_end", pane); // callback - if exists
		});

		_runCallbacks("onresizeall_end");
		//_triggerLayoutEvent(pane, 'resizeall');
	}

	/**
	* Whenever a pane resizes or opens that has a nested layout, trigger resizeAll
	*
	* @param {(string|Object)}	evt_or_pane		The pane just resized or opened
	*/
,	resizeChildren = function (evt_or_pane, skipRefresh) {
		var	pane = evtPane.call(this, evt_or_pane);

		if (!options[pane].resizeChildren) return;

		// ensure the pane-children are up-to-date
		if (!skipRefresh) refreshChildren( pane );
		var pC = children[pane];
		if ($.isPlainObject( pC )) {
			// resize one or more children
			$.each( pC, function (key, child) {
				child.resizeAll();
			});
		}
	}

	/**
	* IF pane has a content-div, then resize all elements inside pane to fit pane-height
	*
	* @param {(string|Object)}	evt_or_panes		The pane(s) being resized
	* @param {boolean=}			[remeasure=false]	Should the content (header/footer) be remeasured?
	*/
,	sizeContent = function (evt_or_panes, remeasure) {
		if (!isInitialized()) return;

		var panes = evtPane.call(this, evt_or_panes);
		panes = panes ? panes.split(",") : _c.allPanes;

		$.each(panes, function (idx, pane) {
			var
				$P	= $Ps[pane]
			,	$C	= $Cs[pane]
			,	o	= options[pane]
			,	s	= state[pane]
			,	m	= s.content // m = measurements
			;
			if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip

			// if content-element was REMOVED, update OR remove the pointer
			if (!$C.length) {
				initContent(pane, false);	// false = do NOT sizeContent() - already there!
				if (!$C) return;			// no replacement element found - pointer have been removed
			}

			// onsizecontent_start will CANCEL resizing if returns false
			if (false === _runCallbacks("onsizecontent_start", pane)) return;

			// skip re-measuring offsets if live-resizing
			if ((!s.isMoving && !s.isResizing) || o.liveContentResizing || remeasure || m.top == undefined) {
				_measure();
				// if any footers are below pane-bottom, they may not measure correctly,
				// so allow pane overflow and re-measure
				if (m.hiddenFooters > 0 && $P.css("overflow") === "hidden") {
					$P.css("overflow", "visible");
					_measure(); // remeasure while overflowing
					$P.css("overflow", "hidden");
				}
			}
			// NOTE: spaceAbove/Below *includes* the pane paddingTop/Bottom, but not pane.borders
			var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom);

			if (!$C.is(":visible") || m.height != newH) {
				// size the Content element to fit new pane-size - will autoHide if not enough room
				setOuterHeight($C, newH, true); // true=autoHide
				m.height = newH; // save new height
			};

			if (state.initialized)
				_runCallbacks("onsizecontent_end", pane);

			function _below ($E) {
				return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0));
			};

			function _measure () {
				var
					ignore	= options[pane].contentIgnoreSelector
				,	$Fs		= $C.nextAll().not(".ui-layout-mask").not(ignore || ":lt(0)") // not :lt(0) = ALL
				,	$Fs_vis	= $Fs.filter(':visible')
				,	$F		= $Fs_vis.filter(':last')
				;
				m = {
					top:			$C[0].offsetTop
				,	height:			$C.outerHeight()
				,	numFooters:		$Fs.length
				,	hiddenFooters:	$Fs.length - $Fs_vis.length
				,	spaceBelow:		0 // correct if no content footer ($E)
				}
					m.spaceAbove	= m.top; // just for state - not used in calc
					m.bottom		= m.top + m.height;
				if ($F.length)
					//spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom)
					m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F);
				else // no footer - check marginBottom on Content element itself
					m.spaceBelow = _below($C);
			};
		});
	}


	/**
	* Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
	*
	* @see  initHandles(), open(), close(), resizeAll()
	* @param {(string|Object)=}		evt_or_panes	The pane(s) being resized
	*/
,	sizeHandles = function (evt_or_panes) {
		var panes = evtPane.call(this, evt_or_panes)
		panes = panes ? panes.split(",") : _c.borderPanes;

		$.each(panes, function (i, pane) {
			var 
				o	= options[pane]
			,	s	= state[pane]
			,	$P	= $Ps[pane]
			,	$R	= $Rs[pane]
			,	$T	= $Ts[pane]
			,	$TC
			;
			if (!$P || !$R) return;

			var
				dir			= _c[pane].dir
			,	_state		= (s.isClosed ? "_closed" : "_open")
			,	spacing		= o["spacing"+ _state]
			,	togAlign	= o["togglerAlign"+ _state]
			,	togLen		= o["togglerLength"+ _state]
			,	paneLen
			,	left
			,	offset
			,	CSS = {}
			;

			if (spacing === 0) {
				$R.hide();
				return;
			}
			else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
				$R.show(); // in case was previously hidden

			// Resizer Bar is ALWAYS same width/height of pane it is attached to
			if (dir === "horz") { // north/south
				//paneLen = $P.outerWidth(); // s.outerWidth || 
				paneLen = sC.innerWidth; // handle offscreen-panes
				s.resizerLength = paneLen;
				left = $.layout.cssNum($P, "left")
				$R.css({
					width:	cssW($R, paneLen) // account for borders & padding
				,	height:	cssH($R, spacing) // ditto
				,	left:	left > -9999 ? left : sC.inset.left // handle offscreen-panes
				});
			}
			else { // east/west
				paneLen = $P.outerHeight(); // s.outerHeight || 
				s.resizerLength = paneLen;
				$R.css({
					height:	cssH($R, paneLen) // account for borders & padding
				,	width:	cssW($R, spacing) // ditto
				,	top:	sC.inset.top + getPaneSize("north", true) // TODO: what if no North pane?
				//,	top:	$.layout.cssNum($Ps["center"], "top")
				});
			}

			// remove hover classes
			removeHover( o, $R );

			if ($T) {
				if (togLen === 0 || (s.isSliding && o.hideTogglerOnSlide)) {
					$T.hide(); // always HIDE the toggler when 'sliding'
					return;
				}
				else
					$T.show(); // in case was previously hidden

				if (!(togLen > 0) || togLen === "100%" || togLen > paneLen) {
					togLen = paneLen;
					offset = 0;
				}
				else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
					if (isStr(togAlign)) {
						switch (togAlign) {
							case "top":
							case "left":	offset = 0;
											break;
							case "bottom":
							case "right":	offset = paneLen - togLen;
											break;
							case "middle":
							case "center":
							default:		offset = round((paneLen - togLen) / 2); // 'default' catches typos
						}
					}
					else { // togAlign = number
						var x = parseInt(togAlign, 10); //
						if (togAlign >= 0) offset = x;
						else offset = paneLen - togLen + x; // NOTE: x is negative!
					}
				}

				if (dir === "horz") { // north/south
					var width = cssW($T, togLen);
					$T.css({
						width:	width  // account for borders & padding
					,	height:	cssH($T, spacing) // ditto
					,	left:	offset // TODO: VERIFY that toggler  positions correctly for ALL values
					,	top:	0
					});
					// CENTER the toggler content SPAN
					$T.children(".content").each(function(){
						$TC = $(this);
						$TC.css("marginLeft", round((width-$TC.outerWidth())/2)); // could be negative
					});
				}
				else { // east/west
					var height = cssH($T, togLen);
					$T.css({
						height:	height // account for borders & padding
					,	width:	cssW($T, spacing) // ditto
					,	top:	offset // POSITION the toggler
					,	left:	0
					});
					// CENTER the toggler content SPAN
					$T.children(".content").each(function(){
						$TC = $(this);
						$TC.css("marginTop", round((height-$TC.outerHeight())/2)); // could be negative
					});
				}

				// remove ALL hover classes
				removeHover( 0, $T );
			}

			// DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
			if (!state.initialized && (o.initHidden || s.noRoom)) {
				$R.hide();
				if ($T) $T.hide();
			}
		});
	}


	/**
	* @param {(string|Object)}	evt_or_pane
	*/
,	enableClosable = function (evt_or_pane) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$T	= $Ts[pane]
		,	o	= options[pane]
		;
		if (!$T) return;
		o.closable = true;
		$T	.bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); })
			.css("visibility", "visible")
			.css("cursor", "pointer")
			.attr("title", state[pane].isClosed ? o.tips.Open : o.tips.Close) // may be blank
			.show();
	}
	/**
	* @param {(string|Object)}	evt_or_pane
	* @param {boolean=}			[hide=false]
	*/
,	disableClosable = function (evt_or_pane, hide) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$T	= $Ts[pane]
		;
		if (!$T) return;
		options[pane].closable = false;
		// is closable is disable, then pane MUST be open!
		if (state[pane].isClosed) open(pane, false, true);
		$T	.unbind("."+ sID)
			.css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues
			.css("cursor", "default")
			.attr("title", "");
	}


	/**
	* @param {(string|Object)}	evt_or_pane
	*/
,	enableSlidable = function (evt_or_pane) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$R	= $Rs[pane]
		;
		if (!$R || !$R.data('draggable')) return;
		options[pane].slidable = true; 
		if (state[pane].isClosed)
			bindStartSlidingEvents(pane, true);
	}
	/**
	* @param {(string|Object)}	evt_or_pane
	*/
,	disableSlidable = function (evt_or_pane) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$R	= $Rs[pane]
		;
		if (!$R) return;
		options[pane].slidable = false; 
		if (state[pane].isSliding)
			close(pane, false, true);
		else {
			bindStartSlidingEvents(pane, false);
			$R	.css("cursor", "default")
				.attr("title", "");
			removeHover(null, $R[0]); // in case currently hovered
		}
	}


	/**
	* @param {(string|Object)}	evt_or_pane
	*/
,	enableResizable = function (evt_or_pane) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$R	= $Rs[pane]
		,	o	= options[pane]
		;
		if (!$R || !$R.data('draggable')) return;
		o.resizable = true; 
		$R.draggable("enable");
		if (!state[pane].isClosed)
			$R	.css("cursor", o.resizerCursor)
			 	.attr("title", o.tips.Resize);
	}
	/**
	* @param {(string|Object)}	evt_or_pane
	*/
,	disableResizable = function (evt_or_pane) {
		if (!isInitialized()) return;
		var	pane = evtPane.call(this, evt_or_pane)
		,	$R	= $Rs[pane]
		;
		if (!$R || !$R.data('draggable')) return;
		options[pane].resizable = false; 
		$R	.draggable("disable")
			.css("cursor", "default")
			.attr("title", "");
		removeHover(null, $R[0]); // in case currently hovered
	}


	/**
	* Move a pane from source-side (eg, west) to target-side (eg, east)
	* If pane exists on target-side, move that to source-side, ie, 'swap' the panes
	*
	* @param {(string|Object)}	evt_or_pane1	The pane/edge being swapped
	* @param {string}			pane2			ditto
	*/
,	swapPanes = function (evt_or_pane1, pane2) {
		if (!isInitialized()) return;
		var pane1 = evtPane.call(this, evt_or_pane1);
		// change state.edge NOW so callbacks can know where pane is headed...
		state[pane1].edge = pane2;
		state[pane2].edge = pane1;
		// run these even if NOT state.initialized
		if (false === _runCallbacks("onswap_start", pane1)
		 ||	false === _runCallbacks("onswap_start", pane2)
		) {
			state[pane1].edge = pane1; // reset
			state[pane2].edge = pane2;
			return;
		}

		var
			oPane1	= copy( pane1 )
		,	oPane2	= copy( pane2 )
		,	sizes	= {}
		;
		sizes[pane1] = oPane1 ? oPane1.state.size : 0;
		sizes[pane2] = oPane2 ? oPane2.state.size : 0;

		// clear pointers & state
		$Ps[pane1] = false; 
		$Ps[pane2] = false;
		state[pane1] = {};
		state[pane2] = {};
		
		// ALWAYS remove the resizer & toggler elements
		if ($Ts[pane1]) $Ts[pane1].remove();
		if ($Ts[pane2]) $Ts[pane2].remove();
		if ($Rs[pane1]) $Rs[pane1].remove();
		if ($Rs[pane2]) $Rs[pane2].remove();
		$Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false;

		// transfer element pointers and data to NEW Layout keys
		move( oPane1, pane2 );
		move( oPane2, pane1 );

		// cleanup objects
		oPane1 = oPane2 = sizes = null;

		// make panes 'visible' again
		if ($Ps[pane1]) $Ps[pane1].css(_c.visible);
		if ($Ps[pane2]) $Ps[pane2].css(_c.visible);

		// fix any size discrepancies caused by swap
		resizeAll();

		// run these even if NOT state.initialized
		_runCallbacks("onswap_end", pane1);
		_runCallbacks("onswap_end", pane2);

		return;

		function copy (n) { // n = pane
			var
				$P	= $Ps[n]
			,	$C	= $Cs[n]
			;
			return !$P ? false : {
				pane:		n
			,	P:			$P ? $P[0] : false
			,	C:			$C ? $C[0] : false
			,	state:		$.extend(true, {}, state[n])
			,	options:	$.extend(true, {}, options[n])
			}
		};

		function move (oPane, pane) {
			if (!oPane) return;
			var
				P		= oPane.P
			,	C		= oPane.C
			,	oldPane = oPane.pane
			,	c		= _c[pane]
			//	save pane-options that should be retained
			,	s		= $.extend(true, {}, state[pane])
			,	o		= options[pane]
			//	RETAIN side-specific FX Settings - more below
			,	fx		= { resizerCursor: o.resizerCursor }
			,	re, size, pos
			;
			$.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) {
				fx[k +"_open"]  = o[k +"_open"];
				fx[k +"_close"] = o[k +"_close"];
				fx[k +"_size"]  = o[k +"_size"];
			});

			// update object pointers and attributes
			$Ps[pane] = $(P)
				.data({
					layoutPane:		Instance[pane]	// NEW pointer to pane-alias-object
				,	layoutEdge:		pane
				})
				.css(_c.hidden)
				.css(c.cssReq)
			;
			$Cs[pane] = C ? $(C) : false;

			// set options and state
			options[pane]	= $.extend(true, {}, oPane.options, fx);
			state[pane]		= $.extend(true, {}, oPane.state);

			// change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west
			re = new RegExp(o.paneClass +"-"+ oldPane, "g");
			P.className = P.className.replace(re, o.paneClass +"-"+ pane);

			// ALWAYS regenerate the resizer & toggler elements
			initHandles(pane); // create the required resizer & toggler

			// if moving to different orientation, then keep 'target' pane size
			if (c.dir != _c[oldPane].dir) {
				size = sizes[pane] || 0;
				setSizeLimits(pane); // update pane-state
				size = max(size, state[pane].minSize);
				// use manualSizePane to disable autoResize - not useful after panes are swapped
				manualSizePane(pane, size, true, true); // true/true = skipCallback/noAnimation
			}
			else // move the resizer here
				$Rs[pane].css(c.side, sC.inset[c.side] + (state[pane].isVisible ? getPaneSize(pane) : 0));


			// ADD CLASSNAMES & SLIDE-BINDINGS
			if (oPane.state.isVisible && !s.isVisible)
				setAsOpen(pane, true); // true = skipCallback
			else {
				setAsClosed(pane);
				bindStartSlidingEvents(pane, true); // will enable events IF option is set
			}

			// DESTROY the object
			oPane = null;
		};
	}


	/**
	* INTERNAL method to sync pin-buttons when pane is opened or closed
	* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
	*
	* @see  open(), setAsOpen(), setAsClosed()
	* @param {string}	pane   These are the params returned to callbacks by layout()
	* @param {boolean}	doPin  True means set the pin 'down', False means 'up'
	*/
,	syncPinBtns = function (pane, doPin) {
		if ($.layout.plugins.buttons)
			$.each(state[pane].pins, function (i, selector) {
				$.layout.buttons.setPinState(Instance, $(selector), pane, doPin);
			});
	}

;	// END var DECLARATIONS

	/**
	* Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
	*
	* @see  document.keydown()
	*/
	function keyDown (evt) {
		if (!evt) return true;
		var code = evt.keyCode;
		if (code < 33) return true; // ignore special keys: ENTER, TAB, etc

		var
			PANE = {
				38: "north" // Up Cursor	- $.ui.keyCode.UP
			,	40: "south" // Down Cursor	- $.ui.keyCode.DOWN
			,	37: "west"  // Left Cursor	- $.ui.keyCode.LEFT
			,	39: "east"  // Right Cursor	- $.ui.keyCode.RIGHT
			}
		,	ALT		= evt.altKey // no worky!
		,	SHIFT	= evt.shiftKey
		,	CTRL	= evt.ctrlKey
		,	CURSOR	= (CTRL && code >= 37 && code <= 40)
		,	o, k, m, pane
		;

		if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
			pane = PANE[code];
		else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey
			$.each(_c.borderPanes, function (i, p) { // loop each pane to check its hotkey
				o = options[p];
				k = o.customHotkey;
				m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
				if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches
					if (k && code === (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
						pane = p;
						return false; // BREAK
					}
				}
			});

		// validate pane
		if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden)
			return true;

		toggle(pane);

		evt.stopPropagation();
		evt.returnValue = false; // CANCEL key
		return false;
	};


/*
 * ######################################
 *	UTILITY METHODS
 *	called externally or by initButtons
 * ######################################
 */

	/**
	* Change/reset a pane overflow setting & zIndex to allow popups/drop-downs to work
	*
	* @param {Object=}   [el]	(optional) Can also be 'bound' to a click, mouseOver, or other event
	*/
	function allowOverflow (el) {
		if (!isInitialized()) return;
		if (this && this.tagName) el = this; // BOUND to element
		var $P;
		if (isStr(el))
			$P = $Ps[el];
		else if ($(el).data("layoutRole"))
			$P = $(el);
		else
			$(el).parents().each(function(){
				if ($(this).data("layoutRole")) {
					$P = $(this);
					return false; // BREAK
				}
			});
		if (!$P || !$P.length) return; // INVALID

		var
			pane	= $P.data("layoutEdge")
		,	s		= state[pane]
		;

		// if pane is already raised, then reset it before doing it again!
		// this would happen if allowOverflow is attached to BOTH the pane and an element 
		if (s.cssSaved)
			resetOverflow(pane); // reset previous CSS before continuing

		// if pane is raised by sliding or resizing, or its closed, then abort
		if (s.isSliding || s.isResizing || s.isClosed) {
			s.cssSaved = false;
			return;
		}

		var
			newCSS	= { zIndex: (options.zIndexes.resizer_normal + 1) }
		,	curCSS	= {}
		,	of		= $P.css("overflow")
		,	ofX		= $P.css("overflowX")
		,	ofY		= $P.css("overflowY")
		;
		// determine which, if any, overflow settings need to be changed
		if (of != "visible") {
			curCSS.overflow = of;
			newCSS.overflow = "visible";
		}
		if (ofX && !ofX.match(/(visible|auto)/)) {
			curCSS.overflowX = ofX;
			newCSS.overflowX = "visible";
		}
		if (ofY && !ofY.match(/(visible|auto)/)) {
			curCSS.overflowY = ofX;
			newCSS.overflowY = "visible";
		}

		// save the current overflow settings - even if blank!
		s.cssSaved = curCSS;

		// apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
		$P.css( newCSS );

		// make sure the zIndex of all other panes is normal
		$.each(_c.allPanes, function(i, p) {
			if (p != pane) resetOverflow(p);
		});

	};
	/**
	* @param {Object=}   [el]	(optional) Can also be 'bound' to a click, mouseOver, or other event
	*/
	function resetOverflow (el) {
		if (!isInitialized()) return;
		if (this && this.tagName) el = this; // BOUND to element
		var $P;
		if (isStr(el))
			$P = $Ps[el];
		else if ($(el).data("layoutRole"))
			$P = $(el);
		else
			$(el).parents().each(function(){
				if ($(this).data("layoutRole")) {
					$P = $(this);
					return false; // BREAK
				}
			});
		if (!$P || !$P.length) return; // INVALID

		var
			pane	= $P.data("layoutEdge")
		,	s		= state[pane]
		,	CSS		= s.cssSaved || {}
		;
		// reset the zIndex
		if (!s.isSliding && !s.isResizing)
			$P.css("zIndex", options.zIndexes.pane_normal);

		// reset Overflow - if necessary
		$P.css( CSS );

		// clear var
		s.cssSaved = false;
	};

/*
 * #####################
 * CREATE/RETURN LAYOUT
 * #####################
 */

	// validate that container exists
	var $N = $(this).eq(0); // FIRST matching Container element
	if (!$N.length) {
		return _log( options.errors.containerMissing );
	};

	// Users retrieve Instance of a layout with: $N.layout() OR $N.data("layout")
	// return the Instance-pointer if layout has already been initialized
	if ($N.data("layoutContainer") && $N.data("layout"))
		return $N.data("layout"); // cached pointer

	// init global vars
	var 
		$Ps	= {}	// Panes x5		- set in initPanes()
	,	$Cs	= {}	// Content x5	- set in initPanes()
	,	$Rs	= {}	// Resizers x4	- set in initHandles()
	,	$Ts	= {}	// Togglers x4	- set in initHandles()
	,	$Ms	= $([])	// Masks - up to 2 masks per pane (IFRAME + DIV)
	//	aliases for code brevity
	,	sC	= state.container // alias for easy access to 'container dimensions'
	,	sID	= state.id // alias for unique layout ID/namespace - eg: "layout435"
	;

	// create Instance object to expose data & option Properties, and primary action Methods
	var Instance = {
	//	layout data
		options:			options			// property - options hash
	,	state:				state			// property - dimensions hash
	//	object pointers
	,	container:			$N				// property - object pointers for layout container
	,	panes:				$Ps				// property - object pointers for ALL Panes: panes.north, panes.center
	,	contents:			$Cs				// property - object pointers for ALL Content: contents.north, contents.center
	,	resizers:			$Rs				// property - object pointers for ALL Resizers, eg: resizers.north
	,	togglers:			$Ts				// property - object pointers for ALL Togglers, eg: togglers.north
	//	border-pane open/close
	,	hide:				hide			// method - ditto
	,	show:				show			// method - ditto
	,	toggle:				toggle			// method - pass a 'pane' ("north", "west", etc)
	,	open:				open			// method - ditto
	,	close:				close			// method - ditto
	,	slideOpen:			slideOpen		// method - ditto
	,	slideClose:			slideClose		// method - ditto
	,	slideToggle:		slideToggle		// method - ditto
	//	pane actions
	,	setSizeLimits:		setSizeLimits	// method - pass a 'pane' - update state min/max data
	,	_sizePane:			sizePane		// method -intended for user by plugins only!
	,	sizePane:			manualSizePane	// method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto'
	,	sizeContent:		sizeContent		// method - pass a 'pane'
	,	swapPanes:			swapPanes		// method - pass TWO 'panes' - will swap them
	,	showMasks:			showMasks		// method - pass a 'pane' OR list of panes - default = all panes with mask option set
	,	hideMasks:			hideMasks		// method - ditto'
	//	pane element methods
	,	initContent:		initContent		// method - ditto
	,	addPane:			addPane			// method - pass a 'pane'
	,	removePane:			removePane		// method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem
	,	createChildren:		createChildren	// method - pass a 'pane' and (optional) layout-options (OVERRIDES options[pane].children
	,	refreshChildren:	refreshChildren	// method - pass a 'pane' and a layout-instance
	//	special pane option setting
	,	enableClosable:		enableClosable	// method - pass a 'pane'
	,	disableClosable:	disableClosable	// method - ditto
	,	enableSlidable:		enableSlidable	// method - ditto
	,	disableSlidable:	disableSlidable	// method - ditto
	,	enableResizable:	enableResizable	// method - ditto
	,	disableResizable:	disableResizable// method - ditto
	//	utility methods for panes
	,	allowOverflow:		allowOverflow	// utility - pass calling element (this)
	,	resetOverflow:		resetOverflow	// utility - ditto
	//	layout control
	,	destroy:			destroy			// method - no parameters
	,	initPanes:			isInitialized	// method - no parameters
	,	resizeAll:			resizeAll		// method - no parameters
	//	callback triggering
	,	runCallbacks:		_runCallbacks	// method - pass evtName & pane (if a pane-event), eg: trigger("onopen", "west")
	//	alias collections of options, state and children - created in addPane and extended elsewhere
	,	hasParentLayout:	false			// set by initContainer()
	,	children:			children		// pointers to child-layouts, eg: Instance.children.west.layoutName
	,	north:				false			// alias group: { name: pane, pane: $Ps[pane], options: options[pane], state: state[pane], children: children[pane] }
	,	south:				false			// ditto
	,	west:				false			// ditto
	,	east:				false			// ditto
	,	center:				false			// ditto
	};

	// create the border layout NOW
	if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation
		return null;
	else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later
		return Instance; // return the Instance object

}


/*	OLD versions of jQuery only set $.support.boxModel after page is loaded
 *	so if this is IE, use support.boxModel to test for quirks-mode (ONLY IE changes boxModel).
 */
$(function(){
	var b = $.layout.browser;
	if (b.msie) b.boxModel = $.support.boxModel;
});


})( jQuery );
// END Layout - keep internal vars internal!



// START Plugins - shared wrapper, no global vars
(function ($) {


/**
 * jquery.layout.state 1.0
 * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
 *
 * Copyright (c) 2012 
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * @requires: UI Layout 1.3.0.rc30.1 or higher
 * @requires: $.ui.cookie (above)
 *
 * @see: http://groups.google.com/group/jquery-ui-layout
 */
/*
 *	State-management options stored in options.stateManagement, which includes a .cookie hash
 *	Default options saves ALL KEYS for ALL PANES, ie: pane.size, pane.isClosed, pane.isHidden
 *
 *	// STATE/COOKIE OPTIONS
 *	@example $(el).layout({
				stateManagement: {
					enabled:	true
				,	stateKeys:	"east.size,west.size,east.isClosed,west.isClosed"
				,	cookie:		{ name: "appLayout", path: "/" }
				}
			})
 *	@example $(el).layout({ stateManagement__enabled: true }) // enable auto-state-management using cookies
 *	@example $(el).layout({ stateManagement__cookie: { name: "appLayout", path: "/" } })
 *	@example $(el).layout({ stateManagement__cookie__name: "appLayout", stateManagement__cookie__path: "/" })
 *
 *	// STATE/COOKIE METHODS
 *	@example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} );
 *	@example myLayout.loadCookie();
 *	@example myLayout.deleteCookie();
 *	@example var JSON = myLayout.readState();	// CURRENT Layout State
 *	@example var JSON = myLayout.readCookie();	// SAVED Layout State (from cookie)
 *	@example var JSON = myLayout.state.stateData;	// LAST LOADED Layout State (cookie saved in layout.state hash)
 *
 *	CUSTOM STATE-MANAGEMENT (eg, saved in a database)
 *	@example var JSON = myLayout.readState( "west.isClosed,north.size,south.isHidden" );
 *	@example myLayout.loadState( JSON );
 */

/**
 *	UI COOKIE UTILITY
 *
 *	A $.cookie OR $.ui.cookie namespace *should be standard*, but until then...
 *	This creates $.ui.cookie so Layout does not need the cookie.jquery.js plugin
 *	NOTE: This utility is REQUIRED by the layout.state plugin
 *
 *	Cookie methods in Layout are created as part of State Management 
 */
if (!$.ui) $.ui = {};
$.ui.cookie = {

	// cookieEnabled is not in DOM specs, but DOES works in all browsers,including IE6
	acceptsCookies: !!navigator.cookieEnabled

,	read: function (name) {
		var
			c		= document.cookie
		,	cs		= c ? c.split(';') : []
		,	pair	// loop var
		;
		for (var i=0, n=cs.length; i < n; i++) {
			pair = $.trim(cs[i]).split('='); // name=value pair
			if (pair[0] == name) // found the layout cookie
				return decodeURIComponent(pair[1]);

		}
		return null;
	}

,	write: function (name, val, cookieOpts) {
		var
			params	= ''
		,	date	= ''
		,	clear	= false
		,	o		= cookieOpts || {}
		,	x		= o.expires
		;
		if (x && x.toUTCString)
			date = x;
		else if (x === null || typeof x === 'number') {
			date = new Date();
			if (x > 0)
				date.setDate(date.getDate() + x);
			else {
				date.setFullYear(1970);
				clear = true;
			}
		}
		if (date)		params += ';expires='+ date.toUTCString();
		if (o.path)		params += ';path='+ o.path;
		if (o.domain)	params += ';domain='+ o.domain;
		if (o.secure)	params += ';secure';
		document.cookie = name +'='+ (clear ? "" : encodeURIComponent( val )) + params; // write or clear cookie
	}

,	clear: function (name) {
		$.ui.cookie.write(name, '', {expires: -1});
	}

};
// if cookie.jquery.js is not loaded, create an alias to replicate it
// this may be useful to other plugins or code dependent on that plugin
if (!$.cookie) $.cookie = function (k, v, o) {
	var C = $.ui.cookie;
	if (v === null)
		C.clear(k);
	else if (v === undefined)
		return C.read(k);
	else
		C.write(k, v, o);
};


// tell Layout that the state plugin is available
$.layout.plugins.stateManagement = true;

//	Add State-Management options to layout.defaults
$.layout.config.optionRootKeys.push("stateManagement");
$.layout.defaults.stateManagement = {
	enabled:		false	// true = enable state-management, even if not using cookies
,	autoSave:		true	// Save a state-cookie when page exits?
,	autoLoad:		true	// Load the state-cookie when Layout inits?
,	includeChildren: true	// recurse into child layouts to include their state as well
	// List state-data to save - must be pane-specific
,	stateKeys:	"north.size,south.size,east.size,west.size,"+
				"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
				"north.isHidden,south.isHidden,east.isHidden,west.isHidden"
,	cookie: {
		name:	""	// If not specified, will use Layout.name, else just "Layout"
	,	domain:	""	// blank = current domain
	,	path:	""	// blank = current page, '/' = entire website
	,	expires: ""	// 'days' to keep cookie - leave blank for 'session cookie'
	,	secure:	false
	}
};
// Set stateManagement as a layout-option, NOT a pane-option
$.layout.optionsMap.layout.push("stateManagement");

/*
 *	State Management methods
 */
$.layout.state = {

	/**
	 * Get the current layout state and save it to a cookie
	 *
	 * myLayout.saveCookie( keys, cookieOpts )
	 *
	 * @param {Object}			inst
	 * @param {(string|Array)=}	keys
	 * @param {Object=}			cookieOpts
	 */
	saveCookie: function (inst, keys, cookieOpts) {
		var o	= inst.options
		,	sm	= o.stateManagement
		,	oC	= $.extend(true, {}, sm.cookie, cookieOpts || null)
		,	data = inst.state.stateData = inst.readState( keys || sm.stateKeys ) // read current panes-state
		;
		$.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC );
		return $.extend(true, {}, data); // return COPY of state.stateData data
	}

	/**
	 * Remove the state cookie
	 *
	 * @param {Object}	inst
	 */
,	deleteCookie: function (inst) {
		var o = inst.options;
		$.ui.cookie.clear( o.stateManagement.cookie.name || o.name || "Layout" );
	}

	/**
	 * Read & return data from the cookie - as JSON
	 *
	 * @param {Object}	inst
	 */
,	readCookie: function (inst) {
		var o = inst.options;
		var c = $.ui.cookie.read( o.stateManagement.cookie.name || o.name || "Layout" );
		// convert cookie string back to a hash and return it
		return c ? $.layout.state.decodeJSON(c) : {};
	}

	/**
	 * Get data from the cookie and USE IT to loadState
	 *
	 * @param {Object}	inst
	 */
,	loadCookie: function (inst) {
		var c = $.layout.state.readCookie(inst); // READ the cookie
		if (c) {
			inst.state.stateData = $.extend(true, {}, c); // SET state.stateData
			inst.loadState(c); // LOAD the retrieved state
		}
		return c;
	}

	/**
	 * Update layout options from the cookie, if one exists
	 *
	 * @param {Object}		inst
	 * @param {Object=}		stateData
	 * @param {boolean=}	animate
	 */
,	loadState: function (inst, stateData, opts) {
		stateData = $.layout.transformData( stateData ); // panes = default subkey

		if (!$.isPlainObject( stateData ) || $.isEmptyObject( stateData )) return;
		// cache in the state object
		inst.state.stateData = stateData;

		// MUST remove any pane.children keys before applying to options
		var s = $.extend(true, {}, stateData);
		$.each($.layout.config.allPanes, function (idx, pane) {
			if (s[pane]) delete s[pane].children;		   
		 });
		// update CURRENT layout-options with saved state data
		$.extend(true, inst.options, s);


		if (1 === 1) return; // avoid compiler warning about unreachable code

console.data( stateData, 'loadState - stateData' );



		// add missing/default options
		opts = $.extend({
			animate:	true
		,	children:	false
		}, opts );

		var vis, lo, co, sm, h, c, o, state // ,s
		,	childState, childOpts
		,	noAnimate = !opts.animate
		,	s_sm = "stateManagement"
		;
 
		/*	WAS WORKING - TEMPORARILY DISABLED
		 *
		 *	If layout has already been initialized, then UPDATE layout state
		 */
		if (inst.state.initialized) {
			$.each($.layout.config.borderPanes, function (idx, pane) {
				state = inst.state[pane];
				o = stateData[ pane ];
				if (typeof o != 'object') return; // no key, continue
				s	= o.size;
				c	= o.initClosed;
				h	= o.initHidden;
				vis	= state.isVisible;
				// reset autoResize
				state.autoResize = o.autoResize;
				// resize BEFORE opening
				if (!vis)
					inst._sizePane(pane, s, false, false, false); // false=skipCallback/noAnimation/forceResize
				if (h === true)			inst.hide(pane, noAnimate);
				else if (c === false)	inst.open (pane, false, noAnimate);
				else if (c === true)	inst.close(pane, false, noAnimate);
				else if (h === false)	inst.show (pane, false, noAnimate);
				// resize AFTER any other actions
				if (vis)
					inst._sizePane(pane, s, false, false, noAnimate); // animate resize if option passed
			});
		};
		
		// TODO: Add recursion for child-layouts
		/*
		// if layout has already been initialized, then UPDATE layout state
		if (inst.state.initialized) {
			$.each( $.layout.config.borderPanes, resetLayout );
			// TODO: Add recursion for child-layouts
			if (opts.includeChildren) //  || true
				$.each($.layout.config.allPanes, function (idx, pane) {
					if (children[pane])
						resetLayout.call( children[pane] );
				});
		};
		
		function resetLayout (layout) {
			var layout		= this
			,	noAnimate	= !opts.animate
			,	vis, state, o, s, h, c
			;
			$.each($.layout.config.borderPanes, function (idx, pane) {
				state = layout.state[pane];
				o	= stateData[ pane ];
				if (typeof o != 'object') return; // no key, continue
				s	= o.size;
				c	= o.initClosed;
				h	= o.initHidden;
				vis	= state.isVisible;
				// reset autoResize
				state.autoResize = o.autoResize;
				// resize BEFORE opening
				if (!vis)
					layout._sizePane(pane, s, false, false, false); // false=skipCallback/noAnimation/forceResize
				if (h === true)			layout.hide(pane, noAnimate);
				else if (c === false)	layout.open (pane, false, noAnimate);
				else if (c === true)	layout.close(pane, false, noAnimate);
				else if (h === false)	layout.show (pane, false, noAnimate);
				// resize AFTER any other actions
				if (vis)
					layout._sizePane(pane, s, false, false, noAnimate); // animate resize if option passed
			});
			// reset autoResize
			state.autoResize = o.autoResize;
			// resize BEFORE opening
			if (!vis)
				layout._sizePane(pane, s, false, false, false); // false=skipCallback/noAnimation/forceResize
			if (h === true)			layout.hide(pane, noAnimate);
			else if (c === false)	layout.open (pane, false, noAnimate);
			else if (c === true)	layout.close(pane, false, noAnimate);
			else if (h === false)	layout.show (pane, false, noAnimate);
			// resize AFTER any other actions
			if (vis)
				layout._sizePane(pane, s, false, false, noAnimate); // animate resize if option passed
		};
		*/
	}

	/**
	 * Get the *current layout state* and return it as a hash
	 *
	 * @param {Object=}		inst	// Layout instance to get state for
	 * @param {object=}		[opts]	// State-Managements override options
	 */
,	readState: function (inst, opts) {
		// backward compatility
		if ($.type(opts) === 'string') opts = { keys: opts };
		if (!opts) opts = {};
		var	sm		= inst.options.stateManagement
		,	ic		= opts.includeChildren
		,	recurse	= ic !== undefined ? ic : sm.includeChildren
		,	keys	= opts.stateKeys || sm.stateKeys
		,	alt		= { isClosed: 'initClosed', isHidden: 'initHidden' }
		,	state	= inst.state
		,	panes	= $.layout.config.allPanes
		,	data	= {}
		,	pair, pane, key, val
		,	ps, pC, child, array, count, branch
		;
		if ($.isArray(keys)) keys = keys.join(",");
		// convert keys to an array and change delimiters from '__' to '.'
		keys = keys.replace(/__/g, ".").split(',');
		// loop keys and create a data hash
		for (var i=0, n=keys.length; i < n; i++) {
			pair = keys[i].split(".");
			pane = pair[0];
			key  = pair[1];
			if ($.inArray(pane, panes) < 0) continue; // bad pane!
			val = state[ pane ][ key ];
			if (val == undefined) continue;
			if (key=="isClosed" && state[pane]["isSliding"])
				val = true; // if sliding, then *really* isClosed
			( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val;
		}

		// recurse into the child-layouts for each pane
		if (recurse) {
			$.each(panes, function (idx, pane) {
				pC = inst.children[pane];
				ps = state.stateData[pane];
				if ($.isPlainObject( pC ) && !$.isEmptyObject( pC )) {
					// ensure a key exists for this 'pane', eg: branch = data.center
					branch = data[pane] || (data[pane] = {});
					if (!branch.children) branch.children = {};
					$.each( pC, function (key, child) {
						// ONLY read state from an initialize layout
						if ( child.state.initialized )
							branch.children[ key ] = $.layout.state.readState( child );
						// if we have PREVIOUS (onLoad) state for this child-layout, KEEP IT!
						else if ( ps && ps.children && ps.children[ key ] ) {
							branch.children[ key ] = $.extend(true, {}, ps.children[ key ] );
						}
					});
				}
			});
		}

		return data;
	}

	/**
	 *	Stringify a JSON hash so can save in a cookie or db-field
	 */
,	encodeJSON: function (JSON) {
		return parse(JSON);
		function parse (h) {
			var D=[], i=0, k, v, t // k = key, v = value
			,	a = $.isArray(h)
			;
			for (k in h) {
				v = h[k];
				t = typeof v;
				if (t == 'string')		// STRING - add quotes
					v = '"'+ v +'"';
				else if (t == 'object')	// SUB-KEY - recurse into it
					v = parse(v);
				D[i++] = (!a ? '"'+ k +'":' : '') + v;
			}
			return (a ? '[' : '{') + D.join(',') + (a ? ']' : '}');
		};
	}

	/**
	 *	Convert stringified JSON back to a hash object
	 *	@see		$.parseJSON(), adding in jQuery 1.4.1
	 */
,	decodeJSON: function (str) {
		try { return $.parseJSON ? $.parseJSON(str) : window["eval"]("("+ str +")") || {}; }
		catch (e) { return {}; }
	}


,	_create: function (inst) {
		var _	= $.layout.state
		,	o	= inst.options
		,	sm	= o.stateManagement
		;
		//	ADD State-Management plugin methods to inst
		 $.extend( inst, {
		//	readCookie - update options from cookie - returns hash of cookie data
			readCookie:		function () { return _.readCookie(inst); }
		//	deleteCookie
		,	deleteCookie:	function () { _.deleteCookie(inst); }
		//	saveCookie - optionally pass keys-list and cookie-options (hash)
		,	saveCookie:		function (keys, cookieOpts) { return _.saveCookie(inst, keys, cookieOpts); }
		//	loadCookie - readCookie and use to loadState() - returns hash of cookie data
		,	loadCookie:		function () { return _.loadCookie(inst); }
		//	loadState - pass a hash of state to use to update options
		,	loadState:		function (stateData, animate) { _.loadState(inst, stateData, animate); }
		//	readState - returns hash of current layout-state
		,	readState:		function (keys) { return _.readState(inst, keys); }
		//	add JSON utility methods too...
		,	encodeJSON:		_.encodeJSON
		,	decodeJSON:		_.decodeJSON
		});

		// init state.stateData key, even if plugin is initially disabled
		inst.state.stateData = {};

		// autoLoad MUST BE one of: data-array, data-hash, callback-function, or TRUE
		if ( !sm.autoLoad ) return;

		//	When state-data exists in the autoLoad key USE IT,
		//	even if stateManagement.enabled == false
		if ($.isPlainObject( sm.autoLoad )) {
			if (!$.isEmptyObject( sm.autoLoad )) {
				inst.loadState( sm.autoLoad );
			}
		}
		else if ( sm.enabled ) {
			// update the options from cookie or callback
			// if options is a function, call it to get stateData
			if ($.isFunction( sm.autoLoad )) {
				var d = {};
				try {
					d = sm.autoLoad( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn
				} catch (e) {}
				if (d && $.isPlainObject( d ) && !$.isEmptyObject( d ))
					inst.loadState(d);
			}
			else // any other truthy value will trigger loadCookie
				inst.loadCookie();
		}
	}

,	_unload: function (inst) {
		var sm = inst.options.stateManagement;
		if (sm.enabled && sm.autoSave) {
			// if options is a function, call it to save the stateData
			if ($.isFunction( sm.autoSave )) {
				try {
					sm.autoSave( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn
				} catch (e) {}
			}
			else // any truthy value will trigger saveCookie
				inst.saveCookie();
		}
	}

};

// add state initialization method to Layout's onCreate array of functions
$.layout.onCreate.push( $.layout.state._create );
$.layout.onUnload.push( $.layout.state._unload );




/**
 * jquery.layout.buttons 1.0
 * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
 *
 * Copyright (c) 2012 
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * @requires: UI Layout 1.3.0.rc30.1 or higher
 *
 * @see: http://groups.google.com/group/jquery-ui-layout
 *
 * Docs: [ to come ]
 * Tips: [ to come ]
 */

// tell Layout that the state plugin is available
$.layout.plugins.buttons = true;

//	Add buttons options to layout.defaults
$.layout.defaults.autoBindCustomButtons = false;
// Specify autoBindCustomButtons as a layout-option, NOT a pane-option
$.layout.optionsMap.layout.push("autoBindCustomButtons");

/*
 *	Button methods
 */
$.layout.buttons = {

	/**
	* Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons
	*
	* @see  _create()
	*
	* @param  {Object}		inst	Layout Instance object
	*/
	init: function (inst) {
		var pre		= "ui-layout-button-"
		,	layout	= inst.options.name || ""
		,	name;
		$.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) {
			$.each($.layout.config.borderPanes, function (ii, pane) {
				$("."+pre+action+"-"+pane).each(function(){
					// if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name'
					name = $(this).data("layoutName") || $(this).attr("layoutName");
					if (name == undefined || name === layout)
						inst.bindButton(this, action, pane);
				});
			});
		});
	}

	/**
	* Helper function to validate params received by addButton utilities
	*
	* Two classes are added to the element, based on the buttonClass...
	* The type of button is appended to create the 2nd className:
	*  - ui-layout-button-pin		// action btnClass
	*  - ui-layout-button-pin-west	// action btnClass + pane
	*  - ui-layout-button-toggle
	*  - ui-layout-button-open
	*  - ui-layout-button-close
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}   		pane 		Name of the pane the button is for: 'north', 'south', etc.
	*
	* @return {Array.<Object>}	If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null
	*/
,	get: function (inst, selector, pane, action) {
		var $E	= $(selector)
		,	o	= inst.options
		,	err	= o.errors.addButtonError
		;
		if (!$E.length) { // element not found
			$.layout.msg(err +" "+ o.errors.selector +": "+ selector, true);
		}
		else if ($.inArray(pane, $.layout.config.borderPanes) < 0) { // invalid 'pane' sepecified
			$.layout.msg(err +" "+ o.errors.pane +": "+ pane, true);
			$E = $("");  // NO BUTTON
		}
		else { // VALID
			var btn = o[pane].buttonClass +"-"+ action;
			$E	.addClass( btn +" "+ btn +"-"+ pane )
				.data("layoutName", o.name); // add layout identifier - even if blank!
		}
		return $E;
	}


	/**
	* NEW syntax for binding layout-buttons - will eventually replace addToggle, addOpen, etc.
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}			action
	* @param {string}			pane
	*/
,	bind: function (inst, selector, action, pane) {
		var _ = $.layout.buttons;
		switch (action.toLowerCase()) {
			case "toggle":			_.addToggle	(inst, selector, pane); break;	
			case "open":			_.addOpen	(inst, selector, pane); break;
			case "close":			_.addClose	(inst, selector, pane); break;
			case "pin":				_.addPin	(inst, selector, pane); break;
			case "toggle-slide":	_.addToggle	(inst, selector, pane, true); break;	
			case "open-slide":		_.addOpen	(inst, selector, pane, true); break;
		}
		return inst;
	}

	/**
	* Add a custom Toggler button for a pane
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}  			pane 		Name of the pane the button is for: 'north', 'south', etc.
	* @param {boolean=}			slide 		true = slide-open, false = pin-open
	*/
,	addToggle: function (inst, selector, pane, slide) {
		$.layout.buttons.get(inst, selector, pane, "toggle")
			.click(function(evt){
				inst.toggle(pane, !!slide);
				evt.stopPropagation();
			});
		return inst;
	}

	/**
	* Add a custom Open button for a pane
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}			pane 		Name of the pane the button is for: 'north', 'south', etc.
	* @param {boolean=}			slide 		true = slide-open, false = pin-open
	*/
,	addOpen: function (inst, selector, pane, slide) {
		$.layout.buttons.get(inst, selector, pane, "open")
			.attr("title", inst.options[pane].tips.Open)
			.click(function (evt) {
				inst.open(pane, !!slide);
				evt.stopPropagation();
			});
		return inst;
	}

	/**
	* Add a custom Close button for a pane
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}   		pane 		Name of the pane the button is for: 'north', 'south', etc.
	*/
,	addClose: function (inst, selector, pane) {
		$.layout.buttons.get(inst, selector, pane, "close")
			.attr("title", inst.options[pane].tips.Close)
			.click(function (evt) {
				inst.close(pane);
				evt.stopPropagation();
			});
		return inst;
	}

	/**
	* Add a custom Pin button for a pane
	*
	* Four classes are added to the element, based on the paneClass for the associated pane...
	* Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
	*  - ui-layout-pane-pin
	*  - ui-layout-pane-west-pin
	*  - ui-layout-pane-pin-up
	*  - ui-layout-pane-west-pin-up
	*
	* @param {Object}			inst		Layout Instance object
	* @param {(string|!Object)}	selector	jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
	* @param {string}   		pane 		Name of the pane the pin is for: 'north', 'south', etc.
	*/
,	addPin: function (inst, selector, pane) {
		var	_	= $.layout.buttons
		,	$E	= _.get(inst, selector, pane, "pin");
		if ($E.length) {
			var s = inst.state[pane];
			$E.click(function (evt) {
				_.setPinState(inst, $(this), pane, (s.isSliding || s.isClosed));
				if (s.isSliding || s.isClosed) inst.open( pane ); // change from sliding to open
				else inst.close( pane ); // slide-closed
				evt.stopPropagation();
			});
			// add up/down pin attributes and classes
			_.setPinState(inst, $E, pane, (!s.isClosed && !s.isSliding));
			// add this pin to the pane data so we can 'sync it' automatically
			// PANE.pins key is an array so we can store multiple pins for each pane
			s.pins.push( selector ); // just save the selector string
		}
		return inst;
	}

	/**
	* Change the class of the pin button to make it look 'up' or 'down'
	*
	* @see  addPin(), syncPins()
	*
	* @param {Object}			inst	Layout Instance object
	* @param {Array.<Object>}	$Pin	The pin-span element in a jQuery wrapper
	* @param {string}			pane	These are the params returned to callbacks by layout()
	* @param {boolean}			doPin	true = set the pin 'down', false = set it 'up'
	*/
,	setPinState: function (inst, $Pin, pane, doPin) {
		var updown = $Pin.attr("pin");
		if (updown && doPin === (updown=="down")) return; // already in correct state
		var
			o		= inst.options[pane]
		,	pin		= o.buttonClass +"-pin"
		,	side	= pin +"-"+ pane
		,	UP		= pin +"-up "+	side +"-up"
		,	DN		= pin +"-down "+side +"-down"
		;
		$Pin
			.attr("pin", doPin ? "down" : "up") // logic
			.attr("title", doPin ? o.tips.Unpin : o.tips.Pin)
			.removeClass( doPin ? UP : DN ) 
			.addClass( doPin ? DN : UP ) 
		;
	}

	/**
	* INTERNAL function to sync 'pin buttons' when pane is opened or closed
	* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
	*
	* @see  open(), close()
	*
	* @param {Object}			inst	Layout Instance object
	* @param {string}	pane	These are the params returned to callbacks by layout()
	* @param {boolean}	doPin	True means set the pin 'down', False means 'up'
	*/
,	syncPinBtns: function (inst, pane, doPin) {
		// REAL METHOD IS _INSIDE_ LAYOUT - THIS IS HERE JUST FOR REFERENCE
		$.each(inst.state[pane].pins, function (i, selector) {
			$.layout.buttons.setPinState(inst, $(selector), pane, doPin);
		});
	}


,	_load: function (inst) {
		var	_	= $.layout.buttons;
		// ADD Button methods to Layout Instance
		// Note: sel = jQuery Selector string
		$.extend( inst, {
			bindButton:		function (sel, action, pane) { return _.bind(inst, sel, action, pane); }
		//	DEPRECATED METHODS
		,	addToggleBtn:	function (sel, pane, slide) { return _.addToggle(inst, sel, pane, slide); }
		,	addOpenBtn:		function (sel, pane, slide) { return _.addOpen(inst, sel, pane, slide); }
		,	addCloseBtn:	function (sel, pane) { return _.addClose(inst, sel, pane); }
		,	addPinBtn:		function (sel, pane) { return _.addPin(inst, sel, pane); }
		});

		// init state array to hold pin-buttons
		for (var i=0; i<4; i++) {
			var pane = $.layout.config.borderPanes[i];
			inst.state[pane].pins = [];
		}

		// auto-init buttons onLoad if option is enabled
		if ( inst.options.autoBindCustomButtons )
			_.init(inst);
	}

,	_unload: function (inst) {
		// TODO: unbind all buttons???
	}

};

// add initialization method to Layout's onLoad array of functions
$.layout.onLoad.push(  $.layout.buttons._load );
//$.layout.onUnload.push( $.layout.buttons._unload );



/**
 * jquery.layout.browserZoom 1.0
 * $Date: 2011-12-29 08:00:00 (Thu, 29 Dec 2011) $
 *
 * Copyright (c) 2012 
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * @requires: UI Layout 1.3.0.rc30.1 or higher
 *
 * @see: http://groups.google.com/group/jquery-ui-layout
 *
 * TODO: Extend logic to handle other problematic zooming in browsers
 * TODO: Add hotkey/mousewheel bindings to _instantly_ respond to these zoom event
 */

// tell Layout that the plugin is available
$.layout.plugins.browserZoom = true;

$.layout.defaults.browserZoomCheckInterval = 1000;
$.layout.optionsMap.layout.push("browserZoomCheckInterval");

/*
 *	browserZoom methods
 */
$.layout.browserZoom = {

	_init: function (inst) {
		// abort if browser does not need this check
		if ($.layout.browserZoom.ratio() !== false)
			$.layout.browserZoom._setTimer(inst);
	}

,	_setTimer: function (inst) {
		// abort if layout destroyed or browser does not need this check
		if (inst.destroyed) return;
		var o	= inst.options
		,	s	= inst.state
		//	don't need check if inst has parentLayout, but check occassionally in case parent destroyed!
		//	MINIMUM 100ms interval, for performance
		,	ms	= inst.hasParentLayout ?  5000 : Math.max( o.browserZoomCheckInterval, 100 )
		;
		// set the timer
		setTimeout(function(){
			if (inst.destroyed || !o.resizeWithWindow) return;
			var d = $.layout.browserZoom.ratio();
			if (d !== s.browserZoom) {
				s.browserZoom = d;
				inst.resizeAll();
			}
			// set a NEW timeout
			$.layout.browserZoom._setTimer(inst);
		}
		,	ms );
	}

,	ratio: function () {
		var w	= window
		,	s	= screen
		,	d	= document
		,	dE	= d.documentElement || d.body
		,	b	= $.layout.browser
		,	v	= b.version
		,	r, sW, cW
		;
		// we can ignore all browsers that fire window.resize event onZoom
		if ((b.msie && v > 8)
		||	!b.msie
		) return false; // don't need to track zoom

		if (s.deviceXDPI && s.systemXDPI) // syntax compiler hack
			return calc(s.deviceXDPI, s.systemXDPI);
		// everything below is just for future reference!
		if (b.webkit && (r = d.body.getBoundingClientRect))
			return calc((r.left - r.right), d.body.offsetWidth);
		if (b.webkit && (sW = w.outerWidth))
			return calc(sW, w.innerWidth);
		if ((sW = s.width) && (cW = dE.clientWidth))
			return calc(sW, cW);
		return false; // no match, so cannot - or don't need to - track zoom

		function calc (x,y) { return (parseInt(x,10) / parseInt(y,10) * 100).toFixed(); }
	}

};
// add initialization method to Layout's onLoad array of functions
$.layout.onReady.push( $.layout.browserZoom._init );


})( jQuery );
});

/*!
 * Aloha Editor
 * Author & Copyright (c) 2012 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed under the terms of http://www.aloha-editor.com/license.html
 */
define('RepositoryBrowser', [
	'jquery',
	'Class',
	'PubSub',
	'repository-browser-i18n-' + (
		(window && window.__DEPS__ && window.__DEPS__.lang) || 'en'
	),
	'jstree',
	'jqgrid',
	'jquery-layout'
], function (
	$,
	Class,
	PubSub,
	i18n
) {
	

	var jQuery = $;
	var browserInstances = [];
	var numOpenedBrowsers = 0;
	var uid = (new Date()).getTime();
	var DEFAULTS = {
		repositoryManager: null,
		repositoryFilter: [],
		objectTypeFilter: [],
		renditionFilter: ['cmis:none'], // ['*']
		filter: ['url'],
		element: null,
		isFloating: false,
		verticalPadding: 100,
		horizontalPadding: 50,
		maxHeight: 1000,
		minHeight: 400,
		minWidth: 400,
		maxWidth: 1200,
		treeWidth: 300,
		listWidth: 'auto',
		pageSize: 8,
		adaptPageSize: false,
		rowHeight: 32,
		rootPath: '',
		rootFolderId: 'aloha',
		columns: {
			icon:    {title: '',        width: 30,  sortable: false, resizable: false},
			name:    {title: 'Name',    width: 200, sorttype: 'text'},
			url:     {title: 'URL',     width: 220, sorttype: 'text'},
			preview: {title: 'Preview', width: 150, sorttype: 'text'}
		},
		i18n: {
			'Browsing': 'Browsing',
			'Close': 'Close',
			'in': 'in',
			'Input search text...': 'Input search text...',
			'numerous': 'numerous',
			'of': 'of',
			'Repository Browser': 'Repository Browser',
			'Search': 'Search',
			'Searching for': 'Searching for',
			'Viewing': 'Viewing'
		}
	};

	/**
	 * Prevents native browser selection on the given element.
	 *
	 * @param {jQuery.<HTMLElement>} element An element on which to prevent
	 *                                       selection.
	 */
	function disableSelection($element) {
		$element.each(function () {
			jQuery(this)
				.attr('unselectable', 'on')
				.css({
				   '-moz-user-select': 'none',
				   '-webkit-user-select': 'none',
				   'user-select': 'none'
				})
				.each(function () {
				   this.onselectstart = function () { return false; };
				});
		});
	}

	/**
	 * Get icon name from given resource type.
	 *
	 * @param {string} type Resource type.
	 * @return {string} Icon name.
	 */
	function getIcon(type) {
		switch (type) {
		case 'folder':
			return 'folder';
		case 'document':
			return 'document';
		default:
			return '';
		}
	}

	/**
	 * Return unique id.
	 *
	 * @return {number}
	 */
	function uniqueId() {
		return ++uid;
	}

	/**
	 * FIXME: free cache
	 *
	 * @param {RepositoryBrowser} browser
	 * @param {object} obj
	 */
	function cacheWrite(browser, obj) {
		browser._cachedRepositoryObjects[obj.uid] = obj;
	}

	/**
	 * @param {RepositoryBrowser} browser
	 * @param {object} obj
	 * @return {object}
	 */
	function cacheRead(browser, uid) {
		return browser._cachedRepositoryObjects[uid];
	}

	/**
	 * @param {jQuery.<HTMLElement>} $node
	 * @return {string} Id of repository object tethered to the given DOM
	 *                  element.
	 */
	function getIdFromTreeNode($node) {
		return $node.find('a:first').attr('data-repo-obj');
	}

	/**
	 * Composite for jqGrid and jsTree
	 *
	 * Convert a repository object into an object that can be used with our tree
	 * component.  Also add a reference to this object in the cache.  According
	 * to the Repository specification, each object will at least have the
	 * following properties: id, name, url, and type.  Any and all other
	 * attributes are optional.
	 */
	function createComposite(props, browser) {
		var uid = uniqueId();

		var state =
				(false === props.hasMoreItems || 'folder' !== props.baseType)
				  ? null
				  : 'closed';

		// Initialize as array instead? will jstree accept empty list?
		var children = null;

		if (props.children) {
			state = 'open';
			children = [];
			var i;
			var child;
			for (i = 0; i < props.children.length; i++) {
				child = createComposite(props.children[i], browser);
				children.push(child);
				cacheWrite(browser, child.resource);
			}
		}

		var treeResource = $.extend(props, {
			uid: uid,
			loaded: false
		});

		return {
			state: state,
			resource: treeResource,
			children: children,
			// jsTreee <li> attributes
			attr: {
				rel: props.type,
				'data-repo-obj': uid
			},
			data: {
				title: props.name,
				icon: getIcon(props.baseType),
				attr: {
					'data-repo-obj': uid
				}
			}
		};
	}

	var RepositoryBrowser = Class.extend({

		/**
		 * @private
		 * @type <object> The repository objects queried through this browser.
		 */
		_cachedRepositoryObjects: {},

		/**
		 * @private
		 * @type <string> The last search query.
		 */
		_searchQuery: null,

		/**
		 * @private
		 * @type <?>
		 */
		_orderBy: null,

		/**
		 * @private
		 * @type <string> prefilled value of the search field
		 */
		_prefilledValue: null,

		/**
		 * @type {jsGrid<HTMLElement>}
		 */
		$_grid: null,

		/**
		 * @type {jsTree<HTMLElement>}
		 */
		$_tree: null,

		/**
		 * @type {jQuery.<HTMLElement>} The layout of the browser panels.
		 */
		$_list: null,

		/**
		 * @type {boolean} Whether or not the browser is visibly opened.  This
		 *                 flag is set to true when the show() function is
		 *                 called.
		 */
		_isOpened: true,

		/**
		 * @constructor
		 */
		_constructor: function () {
			this.init.apply(this, arguments);
		},

		/**
		 * Process the given browser configurations, and renders a hidden
		 * instance of this browser.
		 *
		 * @param {object} options User defined custom browser configurations.
		 */
		init: function (options) {
			options = jQuery.extend({}, DEFAULTS, options, {i18n: i18n});

			// If no element was specified on which to render the browser, then
			// we will create an overlay and render the browser on it.
			if (!options.element || 0 === options.element.length) {
				options.isFloating = true;
				options.element = this._createOverlay();
			}

			if (options.maxWidth < options.minWidth) {
				options.maxWidth = options.minWidth;
			}

			jQuery.extend(this, options);

			this._prefilledValue = this._i18n('Input search text...');
			this._cachedRepositoryObjects = {};
			this._searchQuery = null;
			this._orderBy = null;
			this._pagingCount = null;
			this._pagingOffset = 0;
			this._pagingBtns = {
				first: null,
				end: null,
				next: null,
				prev: null
			};

			this._initializeUI();
			browserInstances.push(this);
			PubSub.pub('repository-browser.initialized', {data: this});
		},

		/**
		 * Retrieves the corresponding internationalization string for the
		 * given keyword.
		 *
		 * @param {string} key The key for which a full i18n string is
		 *                     retrieved.
		 * @return {string} The return value is either the i18n value matched
		 *                  by the given key, or else the key.
		 */
		_i18n: function (key) {
			return this.i18n[key] || key;
		},

		/**
		 * Prepares the browser UI.
		 */
		_initializeUI: function () {
			this.element.attr('data-repository-browser', ++uid); //.html('');
			this.element.width(this.maxWidth);

			this.$_grid = this._createGrid(this.element).resize();
			this._setInitialHeight();
			this.$_tree = this._createTree(this.$_grid.find('.ui-layout-west'));
			this.$_list = this._createList(this.$_grid.find('.ui-layout-center'));

			var that = this;
			var give = this.treeWidth / 5;

			this.$_grid.layout({
				// Disable cursor hot keys since they interfere with text
				// editing.  For example, CTRL+left (wordwise left) and
				// CTRL+SHIFT+left (select wordwise left) would stop working.
				enableCursorHotkey: false,
				west__size: this.treeWidth - 1,
				west__minSize: this.treeWidth - give,
				west__maxSize: this.treeWidth + give,
				center__size: 'auto',
				paneClass: 'ui-layout-pane',
				resizerClass: 'ui-layout-resizer',
				togglerClass: 'ui-layout-toggler',
				onresize: function (name, $element) {
					if ('center' === name) {
						that.$_list.setGridWidth($element.width());
					}
				}
				// , applyDefaultStyles: true
			}).sizePane('west', this.treeWidth); // Fix for a ui-layout bug in
			                                     // chrome.
			disableSelection(this.$_grid);
			this._preloadImages();

			jQuery(function () {
				jQuery(window).resize(function () {
					that._onWindowResized();
				});
			});

			this.element.mousedown(function () {
				jQuery.each(browserInstances, function (index) {
					this.element.css('z-index', 99999 + index);
				});
				jQuery(this).css('z-index',
					99999 + browserInstances.length + 1);
			});

			// IE7 Work-around: Otherwise tree will not be displayed correctly.
			jQuery('.repository-browser-grid').css('width', this.maxWidth);

			this.close();

			// adapt the page size
			this._adaptPageSize();
		},

		/**
		 * Sets the initial height of the repository browser using the minHeight maxHeight setting.
		 */
		_setInitialHeight: function () {

			var overflow = this.maxHeight - jQuery(window).height() + this.verticalPadding;
			var targetHeight = overflow > 0 ? Math.max(this.minHeight, this.maxHeight - overflow) : this.maxHeight;

			this.$_grid.height(targetHeight);
		},

		/**
		 * Automatically resize the browser modal, constraining its dimensions
		 * between minWidth and maxWidth.
		 */
		_onWindowResized: function () {
			var overflow = this.maxWidth - jQuery(window).width() + this.horizontalPadding, $header, $container;
			var target = overflow > 0
			           ? Math.max(this.minWidth, this.maxWidth - overflow)
					   : this.maxWidth;
			this.element.width(target);
			this.$_grid.width(target);

			this._setInitialHeight();

			// adapt tree
			$header = this.$_grid.find('.repository-browser-tree-header');
			this.$_tree.height(this.$_grid.height() - $header.outerHeight(true));

			// adapt list
			$container = this.$_grid.find('.ui-layout-center');
			$container.find('.ui-jqgrid-bdiv').height(this.$_grid.height() - (
				$container.find('.ui-jqgrid-titlebar').height() +
				$container.find('.ui-jqgrid-hdiv').height() +
				$container.find('.ui-jqgrid-pager').height()
			));

			// adapt paging
			if (this._adaptPageSize()) {
				// if the pagesize changed, we need to refresh the list
				if (this._currentFolder) {
					this._fetchItems(this._currentFolder);
				}
			}
		},

		/**
		 * Preload images used by the browser UI.
		 */
		_preloadImages: function () {
			var path = this.rootPath + 'img/';
			var imgs = [
				'arrow-000-medium.png',
				'arrow-180.png',
				'arrow-315-medium.png',
				'arrow-stop-180.png',
				'arrow-stop.png',
				'arrow.png',
				'control-stop-square-small.png',
				'folder-horizontal-open.png',
				'folder-open.png',
				'magnifier-left.png',
				'page.png',
				'picture.png',
				'sort-alphabet-descending.png',
				'sort-alphabet.png'
			];
			var j = imgs.length;
			while (j) {
				var img = document.createElement('img');
				img.src = path + imgs[--j];
			}
		},

		/**
		 * Process the received repository items.
		 *
		 * @param {Array.<object>} items List objects retrieved by repository
		 *                               manager.
		 * @param {TODO} metainfo
		 * @param {function} callback Function to receive the processed items.
		 */
		_processRepoResponse: function (items, metainfo, callback) {
			// If the second parameter is a function, it is the callback;
			if (typeof metainfo === 'function') {
				callback = metainfo;
				metainfo = undefined;
			}

			var repobrowser = this;
			var collection = [];
			var currentFolder = repobrowser._currentFolder
			                 && repobrowser._currentFolder.id;
			var open = null;
			var composite;
			var i;

			for (i = 0; i < items.length; i++) {
				composite = createComposite(items[i], repobrowser);
				collection.push(composite);
				cacheWrite(repobrowser, composite.resource);

				if (currentFolder === composite.resource.id) {
					open = composite.resource;
				}
			}

			callback(collection, metainfo);

			if (open) {
				window.setTimeout(function () {
					repobrowser.$_tree.jstree(
						'select_node',
						'li[data-repo-obj="' + open.uid + '"]'
					);
				}, 0); // Is this timeout still necessary
			}
		},

		/**
		 * Retrieve the root node, and its children.
		 *
		 * @param {function(object)} callback A function to receive the
		 *                                    retrieved folder structure.
		 */
		_fetchRepoRoot: function (callback) {
			if (!this._currentFolder) {
				// get the selected folder
				this._currentFolder = this.getSelectedFolder();
			}

			if (this.repositoryManager) {
				this.getRepoChildren({
					inFolderId: this.rootFolderId,
					repositoryFilter: this.repositoryFilter
				}, callback);
			}
		},

		/**
		 * Retrieves a cached repository object that is associated with the
		 * given node element.
		 *
		 * @param {jQuery.<HTMLElement> $node The element whose corresponding
		 *                                    repository object is to be
		 *                                    retreived.
		 * @return {!Object} The cached repository object or null if none is
		 *                   found in the cache.
		 */
		_getObjectFromCache: function ($node) {
			if ($node && $node.length) {
				var id = $node.find('a:first').attr('data-repo-obj');
				return this._cachedRepositoryObjects[id];
			}
			return null;
		},

		/**
		 * Queries the repository manager for items contained in folder that
		 * was clicked.
		 *
		 * @param {jQuery.<Event>} $event jQuery event object.  Unused.
		 * @param {object} data An object containing information about the
		 *                      jstree node that was clicked.
		 */
		_onTreeNodeSelected: function ($event, data) {
			// Suppress a bug in jsTree.
			if (data.args[0].context) {
				return;
			}

			var repobrowser = this;
			var folder = cacheRead(repobrowser,
			                       getIdFromTreeNode(data.rslt.obj));

			if (folder) {
				// Reset paging and search
				repobrowser._pagingOffset = 0;
				repobrowser._currentFolder = folder;
				repobrowser._clearSearch();
				repobrowser._fetchItems(folder);
				repobrowser.folderSelected(folder);
			}
		},

		/**
		 * Render and initialize a jstree instance for the given container
		 * element.
		 *
		 * @param {jQuery.<HTMLElement>} $container The element in which the
		 *                                          jstree instance will be
		 *                                          rendered.
		 * @return {jQuery.<HTMLElement>} $tree Element which has been
		 *                                      initialized for jstree.
		 */
		_createTree: function ($container) {
			var $tree = jQuery('<div class="repository-browser-tree">');
			var $header = jQuery('<div class="repository-browser-tree-header' +
				' repository-browser-grab-handle">' +
				this._i18n('Repository Browser') + '</div>');

			$container.append($header, $tree);

			$tree.height(this.$_grid.height() - $header.outerHeight(true));

			$tree.bind('loaded.jstree', function ($event, data) {
				$(this).find('>ul>li:first').css('padding-top', 5);
				// Because jstree `open_node' will add substree items to every
				// item matched by the selector, we need to ensure that
				// `open_node' is invoked for one item at a time.
				$(this).find('li[rel="repository"]:first').each(
					function (i, li) {
						$tree.jstree('open_node', li);
					});
			});

			var that = this;

			// Bind the jsree select node event
			$tree.bind('select_node.jstree', function ($event, data) {
				that._onTreeNodeSelected($event, data);
			});

			// Bind the jsree open node event
			$tree.bind('open_node.jstree', function ($event, data) {
				that.folderOpened(data.rslt.obj);
			});

			// Bind the jsree close node event
			$tree.bind('close_node.jstree', function ($event, data) {
				that.folderClosed(data.rslt.obj);
			});

			$tree.jstree({
				types: this.types,
				rootFolderId: this.rootFolderId,
				plugins: ['themes', 'json_data', 'ui', 'types'],
				core: {animation: 250},
				themes: {
					url: this.rootPath + 'css/jstree.css',
					dots: true,
					icons: true,
					theme: 'browser'
				},
				json_data: {
					data: function (nodes, callback) {
						if (that.repositoryManager) {
							that.jstree_callback = callback;
							that._fetchSubnodes(nodes, callback);
						} else {
							callback();
						}
					},
					correct_state: true
				},
				ui: {select_limit: 1}
			});

			return $tree;
		},

		/**
		 * Creates a grid inside the given element.  The grid provides panels
		 * in which we render the folder list tree, and a the folder items
		 * list.
		 *
		 * @param {jQuery.<HTMLElement>} $container A DOM element in which to
		 *                                          render the grid.
		 * @return {jQuery.<HTMLElement} The grid element.
		 */
		_createGrid: function ($container) {
			var $grid = jQuery(
				'<div class="repository-browser-grid\
				             repository-browser-shadow\
							 repository-browser-rounded-top">\
					<div class="ui-layout-west"></div>\
					<div class="ui-layout-center"></div>\
				</div>'
			);
			$container.append($grid);
			return $grid;
		},

		/**
		 * Creates a table inwhich to render repository items.
		 *
		 * @param {jQuery.<HTMLElement>} $container A DOM element in which to
		 *                                          render the list.
		 * @return {jQuery.<HTMLElement} The list element.
		 */
		_createList: function ($container) {
			var $list = jQuery('<table id="repository-browser-list-' + (++uid)
			          + '" class="repository-browser-list"></table>');

			// This is a hidden utility column to help us with auto sorting.
			var model = [{
				name: 'id',
				sorttype: 'int',
				firstsortorder: 'asc',
				hidden: true
			}];

			var names = [''];

			jQuery.each(this.columns, function (key, value) {
				names.push(value.title || '&nbsp;');
				model.push({
					name: key,
					width: value.width,
					sortable: value.sortable,
					sorttype: value.sorttype,
					resizable: value.resizable,
					fixed: value.fixed
				});
			});

			/*
			 * jqGrid requires that we use an id, despite what the
			 * documentation says
			 * (http://www.trirand.com/jqgridwiki/doku.php?id=wiki:pager&s[]=pager).
			 * We need a unique id, however, in order to distinguish pager
			 * elements for each browser instance.
			 */
			var pagerUID = 'repository-browser-list-page-' + (++uid);
			$container.append($list, jQuery('<div id="' + pagerUID + '">'));

			$list.jqGrid({
				datatype: 'local',
				width: $container.width(),
				shrinkToFit: true,
				colNames: names,
				colModel: model,
				caption: '&nbsp;',
				altRows: true,
				altclass: 'repository-browser-list-altrow',
				resizeclass: 'repository-browser-list-resizable',

				// http://www.trirand.com/jqgridwiki/doku.php?id=wiki:pager&s[]=pager
				pager: '#' + pagerUID,

				// # of records to view in the grid.  Passed as parameter to url
				// when retrieving data from server.
				//rowNum: this.pageSize,
				viewrecords: true,

				// Event handlers:
				// http://www.trirand.com/jqgridwiki/doku.php?id=wiki:events
				// Fires after click on [page button] and before populating the
				// data.
				onPaging: function (button) {},

				// Called if the request fails.
				loadError: function (xhr, status, error) {},

				// Raised immediately after row was double clicked.
				ondblClickRow: function (rowid, iRow, iCol, e) {},

				// Fires after all the data is loaded into the grid and all
				// other processes are complete.
				gridComplete: function () {},

				// Executed immediately after every server request.
				loadComplete: function (data) {}
			});

			$container.find('.ui-jqgrid-bdiv').height(this.$_grid.height() - (
				$container.find('.ui-jqgrid-titlebar').height() +
				$container.find('.ui-jqgrid-hdiv').height() +
				$container.find('.ui-jqgrid-pager').height()
			));

			var that = this;

			$list.click(function () {
				that.rowClicked.apply(that, arguments);
			});

			// Override jqGrid paging.
			$container
				.find('.ui-pg-button').unbind()
				.find('>span.ui-icon').each(function () {
					var dir = this.className.match(/ui\-icon\-seek\-([a-z]+)/)[1];
					that._pagingBtns[dir] =
						jQuery(this)
							.parent()
							.addClass('ui-state-disabled')
							.click(function () {
								if (!jQuery(this).hasClass('ui-state-disabled')) {
									that._doPaging(dir);
								}
							});
					});

			// TODO: Implement this once repositories can handle it; hiding it
			// for now.
			$container.find('.ui-pg-input').parent().hide();
			$container.find('.ui-separator').parent().css('opacity', 0).first().hide();
			//$container.find('#repository-browser-list-pager-left').hide();

			this._createTitlebar($container);

			//this.$_grid.find('.loading').html('Loading...');

			// Override jqGrid sorting.
			var listProps = $list[0].p;
			$container.find('.ui-jqgrid-view tr:first th div').each(function (i) {
				if (false !== listProps.colModel[i].sortable) {
					jQuery(this).css('cursor', 'pointer');
					jQuery(this).unbind().click(function (event) {
						event.stopPropagation();
						that._sortList(listProps.colModel[i], this);
					});
				}
			});

			return $list;
		},

		/**
		 * Clear the search
		 */
		_clearSearch: function () {
			var $searchField = this.$_grid.find('.repository-browser-search-field');
			$searchField.val(this._prefilledValue).addClass('repository-browser-search-field-empty');
			this._searchQuery = null;
		},

		_createTitlebar: function ($container) {
			var $bar = $container.find('.ui-jqgrid-titlebar');

			var $btns = jQuery(
				'<div class="repository-browser-btns">\
					<input type="text" class="repository-browser-search-field" />\
					<span class="repository-browser-btn repository-browser-search-btn">\
						<span class="repository-browser-search-icon"></span>\
					</span>\
					<span class="repository-browser-btn repository-browser-close-btn">' +
						this._i18n('Close') +
					'</span>\
					<div class="repository-browser-clear"></div>\
				</div>'
			);

			var that = this;

			$bar.addClass('repository-browser-grab-handle').append($btns);

			$bar.find('.repository-browser-search-btn')
			    .html(this._i18n('Search'))
			    .click(function () {
					that._triggerSearch();
				});

			var $searchField = $bar.find('.repository-browser-search-field');

			this._clearSearch();

			$searchField.keypress(function (event) {
				// On ENTER.
				if (13 === event.keyCode) {
					that._triggerSearch();
				}
			});

			$searchField.focus(function () {
				if (jQuery(this).val() === that._prefilledValue) {
					jQuery(this)
						.val('')
						.removeClass('repository-browser-search-field-empty');
				}
			});

			$searchField.blur(function () {
				if (jQuery(this).val() === '') {
					that._clearSearch();
				}
			});

			$bar.find('.repository-browser-close-btn')
			    .click(function () {
					that.close();
				});

			$bar.find('.repository-browser-btn')
			    .mousedown(function () {
					jQuery(this).addClass('repository-browser-pressed');
				})
			    .mouseup(function () {
					jQuery(this).removeClass('repository-browser-pressed');
				});
		},

		_triggerSearch: function () {
			var $searchField = this.$_grid.find('input.repository-browser-search-field');
			var searchValue = $searchField.val();

			if (jQuery($searchField).hasClass('aloha-browser-search-field-empty') ||
			    '' === searchValue) {
				searchValue = null;
			}
			this._pagingOffset = 0;
			this._searchQuery = searchValue;
			this._fetchItems(this._currentFolder);
		},

		/**
		 * TODO: Fix this so that sorting does toggle between desc and asc when
		 *       you click on a column on which we were not sorting.
		 */
		_sortList: function (columnModel, element){
			// Reset sort properties in all column headers.
			this.$_grid.find('span.ui-grid-ico-sort').addClass('ui-state-disabled');

			columnModel.sortorder = ('asc' === columnModel.sortorder)
			                      ? 'desc'
								  : 'asc';

			jQuery(element)
				.find('span.s-ico').show()
				.find('.ui-icon-' + columnModel.sortorder)
				.removeClass('ui-state-disabled');

			this._setSortOrder(columnModel.name, columnModel.sortorder);
			this._fetchItems(this._currentFolder);
		},

		_doPaging: function (dir) {
			switch (dir) {
			case 'first':
				this._pagingOffset = 0;
				break;
			case 'end':
				if ((this._pagingCount % this.pageSize) === 0) {
					// item count is exactly divisible by page size
					this._pagingOffset = this._pagingCount - this.pageSize;
				} else {
					this._pagingOffset = this._pagingCount - (this._pagingCount % this.pageSize);
				}
				break;
			case 'next':
				this._pagingOffset += this.pageSize;
				break;
			case 'prev':
				this._pagingOffset -= this.pageSize;
				// avoid "ot of bounds" situation
				if (this._pagingOffset < 0) {
					this._pagingOffset = 0;
				}
				break;
			}
			this._fetchItems(this._currentFolder);
		},

		/**
		 * Adds new sort fields into the _orderBy array.  If a field already
		 * exists, it will be spliced from where it is and unshifted to the end
		 * of the array.
		 */
		_setSortOrder: function (by, order) {
			var sortItem = {};
			var isFound = false;
			var orderBy = this._orderBy || [];
			var orderItem;
			var field;
			var i;
			sortItem[by] = order || 'asc';

			for (i = 0; i < orderBy.length; ++i) {
				orderItem = orderBy[i];

				for (field in orderItem) {
					if (orderItem.hasOwnProperty(field)) {
						if (field === by) {
							orderBy.splice(i, 1);
							orderBy.unshift(sortItem);
							isFound = true;
							break;
						}
					}
				}

				if (isFound) {
					break;
				}
			}

			if (isFound) {
				orderBy.unshift(sortItem);
			}

			this._orderBy = orderBy;
		},

		_listItems: function (items) {
			var $list = this.$_list.clearGridData();
			var i;
			var obj;
			for (i = 0; i < items.length; i++) {
				obj = items[i].resource;
				$list.addRowData(
					obj.uid,
					jQuery.extend({id: obj.id}, this.renderRowCols(obj))
				);
			}
		},

		/**
		 * Handle repository timeouts
		 */
		handleTimeout: function () {
		},

		_processItems: function (data, metainfo) {
			// If the total number of items is known, we can calculate the
			// number of pages.
			this._pagingCount = (metainfo && jQuery.isNumeric(metainfo.numItems))
			                  ? metainfo.numItems
							  : null; // TODO: should we use undefined?

			this.$_grid.find('.loading').hide();
			this.$_list.show();
			this._listItems(data);

			var CSS_DISABLED = 'ui-state-disabled';
			var $btns = this._pagingBtns;

			if (this._pagingOffset <= 0) {
				$btns.first.add($btns.prev).addClass(CSS_DISABLED);
			} else {
				$btns.first.add($btns.prev).removeClass(CSS_DISABLED);
			}

			if (!jQuery.isNumeric(this._pagingCount)) {
				$btns.end.addClass(CSS_DISABLED);

				if (data.length <= this.pageSize) {
					$btns.next.addClass(CSS_DISABLED);
				} else {
					$btns.next.removeClass(CSS_DISABLED);
				}
			} else if (this._pagingOffset + this.pageSize >= this._pagingCount) {
				$btns.end.add($btns.next).addClass(CSS_DISABLED);
			} else {
				$btns.end.add($btns.next).removeClass(CSS_DISABLED);
			}

			var from;
			var to;

			if (0 === data.length && 0 === this._pagingOffset) {
				from = 0;
				to = 0;
			} else {
				from = this._pagingOffset + 1;
				to = from + data.length - 1;
			}

			this.$_grid.find('.ui-paging-info').html(
				this._i18n('Viewing') + ' ' +
				(from) + ' - '  +
				(to) + ' ' + this._i18n('of') + ' ' +
				(jQuery.isNumeric(this._pagingCount)
					? this._pagingCount : this._i18n('numerous'))
			);

			// when the repository manager reports a timeout, we handle it
			if (metainfo && metainfo.timeout) {
				this.handleTimeout();
			}
		},

		_createOverlay: function () {
			// We only want one overlay element.
			if (0 === jQuery('.repository-browser-modal-overlay').length) {
				jQuery('body').append(
					'<div class="repository-browser-modal-overlay" ' +
						'style="top: -99999px; z-index: 99999;"></div>');
			}

			var that = this;

			// Register a close procedure for each browser instance.
			jQuery('.repository-browser-modal-overlay').click(function () {
				that.close();
			});

			var $container = jQuery(
				'<div class="repository-browser-modal-window"' +
				' style="top: -99999px; z-index: 99999;">');

			jQuery('body').append($container);

			return $container;
		},

		_fetchSubnodes: function (nodes, callback) {
			if (-1 === nodes) {
				this._fetchRepoRoot(callback);
			} else {
				var i;
				for (i = 0; i < nodes.length; i++) {
					var obj = this._getObjectFromCache(nodes.eq(i));
					if (obj) {
						this.fetchChildren(obj, callback);
					}
				}
			}
		},

		// ====================================================================
		// API Methods
		// ====================================================================

		/**
		 * Get child folders of a specified repository node.
		 *
		 * @param {object} params An object containing parameters with which to
		 *                        fetch the objects.
		 * @param {function} callback Function to receive fetched items.
		 */
		getRepoChildren: function (params, callback) {
			if (this.repositoryManager) {
				var that = this;
				this.repositoryManager.getChildren(params, function (items) {
					that._processRepoResponse(items, callback);
				});
			}
		},

		/**
		 * Query repositories for items matching the given parameters.
		 *
		 * @param {object} params An object containing parameters with which to
		 *                        fetch the objects.
		 * @param {function} callback Function to receive fetched items.
		 */
		queryRepository: function (params, callback) {
			if (this.repositoryManager) {
				var that = this;
				this.repositoryManager.query(params, function (response) {
					that._processRepoResponse(
						(response.results > 0) ? response.items : [], {
							numItems: response.numItems,
							hasMoreItems: response.hasMoreItems,
							timeout: response.timeout
						}, callback
					);
				});
			}
		},

		/**
		 * Builds a row that an be rendered in the grid layout from the given
		 * repository item.
		 *
		 * @param {object} item The repository resource to render.
		 * @returns {object} An object representing the rendered row such that
		 *                   it can be used to populate the grid layout.
		 */
		renderRowCols: function (item) {
			var row = {};

			jQuery.each(this.columns, function (name, value) {
				switch (name) {
				case 'icon':
					row.icon = '<div class="repository-browser-icon '
							 + 'repository-browser-icon-' + item.type
							 + '"></div>';
					break;
				default:
					row[name] = item[name] || '--';
				}
			});

			return row;
		},

		/**
		 * User should implement this according to their needs.
		 *
		 * @param {object} item Repository resource for a row.
		 */
		onSelect: function (item) {},

		/**
		 * Fetch an object's children if we haven't already done so.
		 *
		 * @param {object} obj
		 * @param {function} callback A function to receive the fetched
		 *                            children object.
		 */
		fetchChildren: function (obj, callback) {
			if (true === obj.hasMoreItems || 'folder' === obj.baseType) {
				if (false === obj.loaded) {
					var that = this;
					this.getRepoChildren({
						inFolderId: obj.id,
						repositoryId: obj.repositoryId
					}, function (data) {
						that._cachedRepositoryObjects[obj.uid].loaded = true;
						if (typeof callback === 'function') {
							callback(data);
						}
					});
				}
			}
		},

		/**
		 * Handles click events on rows.
		 *
		 * @param {jQuery.<Event>} jQuery event object.
		 * @return {jQuery.<HTMLElement>} The clicked row of null.
		 */
		rowClicked: function ($event) {
			var row = jQuery($event.target).parent('tr');

			if (row.length) {
				var uid = row.attr('id');
				var item = this._cachedRepositoryObjects[uid];
				this.onSelect(item);
				return item;
			}

			return null;
		},

		getFieldOfHeader: function ($th) {
			return $th.find('div.ui-jqgrid-sortable').attr('id').replace('jqgh_', '');
		},

		_fetchItems: function (folder) {
			if (!folder) {
				return;
			}

			// When searching, we do this recursively.
			var recursive = (typeof this._searchQuery === 'string');

			this.$_list.setCaption((typeof this._searchQuery === 'string')
				? this._i18n('Searching for') + ' "' + this._searchQuery + '" ' +
				  this._i18n('in') + ' ' + folder.name
				: this._i18n('Browsing') + ': ' + folder.name);

			this.$_list.hide();
			this.$_grid.find('.loading').show();

			var that = this;

			this.queryRepository({
				repositoryId: folder.repositoryId,
				inFolderId: folder.id,
				queryString: this._searchQuery,
				orderBy: this._orderBy,
				skipCount: this._pagingOffset,
				maxItems: this.pageSize,
				objectTypeFilter: this.objectTypeFilter,
				renditionFilter: this.renditionFilter,
				filter: this.filter,
				recursive: recursive
			}, function (data, metainfo) {
				that._processItems(data, metainfo);
			});
		},

		setObjectTypeFilter: function (otf) {
			this.objectTypeFilter = (typeof otf === 'string') ? [otf] : otf;
		},

		getObjectTypeFilter: function () {
			return this.objectTypeFilter;
		},

		show: function () {
			this.open();
		},

		open: function () {
			if (this._isOpened) {
				return;
			}

			this._isOpened = true;
			var $element = this.element;

			if (this.isFloating) {
				//$element.find('.repository-browser-close-btn').show();
				jQuery('.repository-browser-modal-overlay')
					.stop()
					.css({top: 0, left: 0})
					.show();

				$element.stop().show();

				// recalculate sizes
				this._onWindowResized();
				this.$_grid.resize();

				var win	= jQuery(window);

				$element.css({
					left: this.horizontalPadding / 2,
					top: this.verticalPadding / 2
				}).draggable({
					handle: $element.find('.repository-browser-grab-handle')
				});

				// Wake-up animation.
				this.$_grid.css({
					marginTop: 0,
					opacity: 0
				}).animate({
					marginTop: 0,
					opacity: 1
				}, 1500, 'easeOutExpo', function () {
					// Disable filter to prevent IE<=8 filter bug.
					if (jQuery.browser.msie) {
						jQuery(this).add($element).css(
							'filter',
							'progid:DXImageTransform.Microsoft.gradient(enabled=false)'
						);
					}
				});
			} else {
				$element.stop().show().css({
					opacity: 1,
					filter: 'progid:DXImageTransform.Microsoft.gradient(enabled=false)'
				});
				this._onWindowResized();
				this.$_grid.resize();
			}

			++numOpenedBrowsers;
		},

		close: function () {
			if (!this._isOpened) {
				return;
			}

			this._isOpened = false;

			this.element.fadeOut(250, function () {
				jQuery(this).css('top', 0).hide();
				if (0 === numOpenedBrowsers || 0 === --numOpenedBrowsers) {
					jQuery('.repository-browser-modal-overlay').hide();
				}
			});
		},

		/**
		 * Refreshes the browser.
		 * TODO: Should we also refresh the tree?
		 */
		refresh: function () {
			// Refresh the list, if we have a current folder.
			if (this._currentFolder) {
				this._fetchItems(this._currentFolder);
			}
		},

		/**
		 * Gets called when a folder in the tree is opened.
		 *
		 * @param {jQuery.<HTMLElement>} $node Tree node.
		 */
		folderOpened: function($node) {
			var repobrowser = this;
			var folder = cacheRead(repobrowser, getIdFromTreeNode($node));
			if (folder) {
				repobrowser.repositoryManager.folderOpened(folder);
			}
		},

		/**
		 * This function gets called when a folder in the tree is closed
		 *
		 * @param {object} obj	folder data object
		 */
		folderClosed: function(obj) {
			var folder = this._getObjectFromCache(obj);

			if (folder) {
				if (this.repositoryManager) {
					this.repositoryManager.folderClosed(folder);
				}
			}
		},

		/**
		 * This function gets called when a folder in the tree is selected
		 *
		 * @param {object} obj	folder data object
		 */
		folderSelected: function(obj) {
			if (this.repositoryManager) {
				this.repositoryManager.folderSelected(obj);
			}
		},

		/**
		 * Get the selected folder
		 * @return {object} selected folder or undefined
		 */
		getSelectedFolder: function () {
			if (this.repositoryManager) {
				if (typeof this.repositoryManager.getSelectedFolder === 'function') {
					return this.repositoryManager.getSelectedFolder();
				}
			}
		},

		/**
		 * Adapt the page size.
		 * @return true if the page size was actually changed, false if not
		 */
		_adaptPageSize: function () {
			var listHeight, newPageSize, container;

			// if this is off, don't do anything
			if (!this.adaptPageSize || !this.$_list || !this.rowHeight) {
				return false;
			}

			// get the current height of the container
			container = this.$_grid.find('.ui-jqgrid-bdiv');
			// reduce by 20 px to leave place for a scrollbar
			listHeight = container.innerHeight() - 20;

			// do the math
			if (listHeight) {
				newPageSize = Math.floor(listHeight / this.rowHeight);
				if (newPageSize <= 0) {
					newPageSize = 1;
				}
				if (newPageSize !== this.pageSize) {
					this.pageSize = newPageSize;
					return true;
				} else {
					return false;
				}
			} else {
				return false;
			}
		}
	});

	return RepositoryBrowser;
});
define('repository-browser-i18n-de', [], function () {
	
	return {
		'Browsing': 'Durchsuchen',
		'Close': 'Schließen',
		'in': 'in',
		'Input search text...': 'Suchtext einfügen...',
		'numerous': 'zahlreiche',
		'of': 'von',
		'Repository Browser': 'Repository Browser',
		'Search': 'Suchen',
		'Searching for': 'Suche nach',
		'Viewing': 'Anzeige',
		'button.switch-metaview.tooltip': 'Zwischen Metaansicht und normaler Ansicht umschalten'
	};
});
define('repository-browser-i18n-en', [], function () {
	
	return {
		'Browsing': 'Browsing',
		'Close': 'Close',
		'in': 'in',
		'Input search text...': 'Input search text...',
		'numerous': 'numerous',
		'of': 'of',
		'Repository Browser': 'Repository Browser',
		'Search': 'Search',
		'Searching for': 'Searching for',
		'Viewing': 'Viewing',
		'button.switch-metaview.tooltip': 'Switch between meta and normal view'
	};
});

define('linkbrowser/nls/i18n',{
	"root":  {
		"button.addlink.tooltip": "Insert Link",
		"button.removelink.tooltip": "Remove Link",
		"newlink.defaulttext": "New Link",
		"floatingmenu.tab.link": "Link"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

define('linkbrowser/linkbrowser-plugin',[
	'aloha',
	'jquery',
	'aloha/plugin',
	'aloha/pluginmanager',
	'ui/ui',
	'ui/button',
	'link/link-plugin',
	'RepositoryBrowser',
	'i18n!linkbrowser/nls/i18n',
	'i18n!aloha/nls/i18n'
], function(
	Aloha,
	jQuery,
	Plugin,
	PluginManager,
	Ui,
	Button,
	Links,
	RepositoryBrowser,
	i18n,
	i18nCore
) {
	

	var LinkBrowser = RepositoryBrowser.extend({

		init: function (config) {
			this._super(config);

			var that = this;

			this._linkBrowserButton = Ui.adopt('linkBrowser', Button, {
				tooltip: i18n.t('button.addlink.tooltip'),
				icon: 'aloha-icon-tree',
				scope: 'Aloha.continuoustext',
				click: function () {
					that.show();
				}
			});

			this._linkBrowserButton.hide();

			this.url = Aloha.getAlohaUrl() + '/../plugins/extra/linkbrowser/';

			Aloha.bind('aloha-link-selected', function (event, rangeObject) {
				that._linkBrowserButton.show();
			});

			Aloha.bind('aloha-link-unselected', function (event, rangeObject) {
				that._linkBrowserButton.hide();
			});
		},

		onSelect: function (item) {
			Links.hrefField.setItem(item)

			// Now create a selection within the editable since the user should
			// be able to type once the link has been created.

			// 1. We need to save the current cursor position since the a
			//    activate editable event will be fired and this will set the
			//    cursor in the upper left cornor of the editable.
			var range = Aloha.Selection.getRangeObject();
			var currentStartContainer = range.startContainer = range.endContainer;
			var currentStartOffset = range.startOffset = range.endOffset;

			// 2. Do the first select - this will invoke the activate editable
			//    event.
			range.select();

			// 3. Restore the range.
			range.startContainer = range.endContainer = currentStartContainer;
			range.startOffset = range.endOffset = currentStartOffset;

			// 4. Invoke the final selection.
			range.select();

			Aloha.trigger('aloha-link-selected-in-linkbrowser', item);

			// Close the browser lightbox.
			this.close();
		},

		renderRowCols: function (item) {
			var row = {},
			    pluginUrl = this.url,
			    icon = '__page__',
			    idMatch = item.id.match(/(\d+)\./);

			jQuery.each(this.columns, function (colName, v) {
				switch (colName) {
				case 'icon':
					row.icon = '<div class="aloha-browser-icon\
						aloha-browser-icon-' + icon + '"></div>';
					break;
				case 'translations':
					if (!item.renditions) {
						break;
					}

					var rends = item.renditions,
					    i = rends.length,
					    strBldr = [],
					    r;

					for (; i > 0; --i) {
						r = rends[i];
						if (r.kind == 'translation') {
							strBldr.push(
								//'<a href="' + repo_host + r.url + '">' +
								'<img src="'   + pluginUrl  + 'img/flags/' + r.language + '.png"\
									  alt="'   + r.language + '"\
									  title="' + r.filename + '" />'
								//'</a>'
							);
						}
					}

					row.translations = strBldr.join('');
					break;
				case 'language':
					row.language = item.language;
					break;
				default:
					row[colName] = item[colName] || '--';
				}
			});

			return row;
		}

	});

	var LinkBrowserPlugin = Plugin.create('linkbrowser', {
		dependencies: ['link'],

		browser: null,

		init: function () {
			var config = {
				repositoryManager : Aloha.RepositoryManager,
				repositoryFilter  : [],
				objectTypeFilter  : ['website', 'file', 'image', 'language' /*, '*' */],
				renditionFilter	  : ['*'],
				filter			  : ['language'],
				columns : {
					icon         : {title: '',     width: 30,  sortable: false, resizable: false},
					name         : {title: 'Name', width: 320, sorttype: 'text'},
					language     : {title: '',     width: 30,  sorttype: 'text'},
					translations : {title: '',     width: 350, sorttype: 'text'}
				},
				rootPath : Aloha.settings.baseUrl + '/vendor/repository-browser/'
			};

			this.browser = new LinkBrowser(config);
		}
	});

	return LinkBrowserPlugin;
});

define('imagebrowser/nls/i18n',{
	"root":  {
		"button.addimage.tooltip": "Insert Image",
		"button.removeimage.tooltip": "Remove Image",
		"newimage.defaulttext": "New Image",
		"floatingmenu.tab.img": "Image"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* imagebrowser-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('imagebrowser/imagebrowser-plugin',[
	// js
	'aloha',
	'jquery',
	'aloha/plugin',
	'aloha/pluginmanager',
	'ui/ui',
	'ui/button',
	'image/image-plugin',
	'RepositoryBrowser',
	// i18n
	'i18n!imagebrowser/nls/i18n',
	'i18n!aloha/nls/i18n'
], function(
	Aloha,
    jQuery,
    Plugin,
    PluginManager,
    Ui,
    Button,
    Images,
    RepositoryBrowser,
    i18n,
    i18nCore
) {
	

	var ImageBrowser = RepositoryBrowser.extend( {

		init: function ( config ) {
			this._super( config );

			var browser = this;

			this._imageBrowserButton = Ui.adopt('imageBrowser', Button, {
				tooltip: i18n.t('button.addimage.tooltip'),
				icon: 'aloha-icon-tree',
				scope: 'Aloha.continuoustext',
				'class': 'aloha-imagebrowser-button',
				click: function () { browser.open(); }
			});

			this._imageBrowserButton.show(false);

			this.url = Aloha.getAlohaUrl() + '/../plugins/extra/imagebrowser/';

			Aloha.bind( 'aloha-image-selected', function ( event, rangeObject ) {
				browser._imageBrowserButton.show(true);
			});
			Aloha.bind( 'aloha-image-unselected', function ( event, rangeObject ) {
				browser._imageBrowserButton.show(false);
			});
		},
		onSelect: function ( item ) {
			if ( item.type.match( 'image' ) !== null ) {
				Images.ui.imgSrcField.setItem( item );
				Images.resetSize(); // reset to original image size
				this.close();
			}
		},

		/**
		 * Overrides browser list items to show only images in the grid panel
		 */
		listItems: function ( items ) {
			var browser = this;
			var list = this.list.clearGridData();

			jQuery.each( items, function () {
				var obj = this.resource;
				if ( obj.type.match( 'image' ) !== null ) {
					list.addRowData(
						obj.uid,
						jQuery.extend( { id: obj.id }, browser.renderRowCols( obj ) )
					);
				}
			});
		},

		 /**
		  * Overrides column rendering
		  */
		renderRowCols: function ( item ) {
			var row = {},
			    pluginUrl = this.url,
			    icon = '__page__',
			    idMatch = item.id.match( /(\d+)\./ );

			jQuery.each( this.columns, function ( colName, v ) {
				switch ( colName ) {
				case 'icon':
					if ( !item.renditions ) {
						break;
					}
					if ( item.renditions.length === 1 ) {
						if ( item.renditions[ 0 ].kind === 'thumbnail' ) {
							row.icon = '<img width="' + item.renditions[ 0 ].width
							+ '" height="' + item.renditions[ 0 ].height
							+ ' " src="' + item.renditions[ 0 ].url + '"/>';
						}
					}
					break;
				default:
					row[ colName ] = item[ colName ] || '--';
				}
			});

			return row;
		}

	});

	var ImageBrowserPlugin = Plugin.create( 'imagebrowser', {
		dependencies: [ 'image' ],
		browser: null,
		init: function () {
			var config = {
				repositoryManager : Aloha.RepositoryManager,
				repositoryFilter  : [],
				objectTypeFilter  : [ 'image' /*, '*' */ ],
				renditionFilter	  : [ '*' ],
				filter			  : [ 'language' ],
				columns : {
					icon : { title: '',     width: 75,  sortable: false, resizable: false },
					name : { title: 'Name', width: 320, sorttype: 'text' }
				},
				rootPath : Aloha.settings.baseUrl + '/vendor/repository-browser/'
			};
			this.browser = new ImageBrowser( config );
		}
	});

	return ImageBrowserPlugin;
});

define('ui/menuButton',[
	'jquery',
	'ui/component',
	'ui/utils',
	'jqueryui'
], function (
	$,
	Component,
	Utils
) {
	

	var MenuButton = Component.extend({
		init: function () {
			this.element = MenuButton.makeMenuButton(this);
		}
	});

	// static functions

	/**
	 * @param props button properties:
	 *        click - if provided will generate a split button,
	 *                  otherwise just a normal select button.
	 *        menu - array of props for nested buttons
	 *        text - button text
	 *        html - button html
	 *        iconUrl - button icon url
	 *        siblingContainer
	 *             - a $ object that will be searched for other split buttons.
	 *               If a split button is expanded, all the other split buttons in
	 *               this container will be closed.
	 */
	MenuButton.makeMenuButton = function (props) {
		var wrapper = $('<div>'   , {'class': 'aloha-ui-menubutton-container'});
		var expand  = Utils.makeButtonElement({'class': 'aloha-ui-menubutton-expand'});
		var menu    = $('<ul>'    , {'class': 'aloha-ui-menubutton-menu'});
		var action = null;
		var buttonset = null;

		if ($.browser.msie) {
			wrapper.addClass('aloha-ui-menubutton-iehack');
		}

		if (props.click) {
			action = Utils.makeButton(Utils.makeButtonElement({'class': 'aloha-ui-menubutton-action'}), props)
				.click(props.click);

			Utils.makeButton(expand, {}, true);

			buttonset = $('<div>')
				.buttonset()
				.append(action)
				.append(expand);
		} else {
			Utils.makeButton(expand, props, true)
			      .addClass('aloha-ui-menubutton-single');
		}

		if (!props.menu) {
			return wrapper.append(action);
		}

		function hideMenu(menu) {
			menu.hide().parent().removeClass('aloha-ui-menubutton-pressed');
		}

		expand
			.click(function (){
				wrapper.addClass('aloha-ui-menubutton-pressed');

				if (props.siblingContainer) {
					props.siblingContainer
						.find('.aloha-ui-menubutton-menu')
						.each(function (){
							if (this !== menu[0]) {
								hideMenu($(this));
							}
						});
				}

				if (menu.is(':visible')) {
					hideMenu(menu);
					return;
				}

				menu.show().position({
					my: 'left top',
					at: 'left bottom',
					of: action || expand
				});

				// In order to prevent the floating menu from being partially
				// covered by the ribbon, we use "position: relative" and an
				// invisible border to pad the top of the document.  This
				// throws off the offset to the menu button so we need to
				// compensate in ordet to ensure that the menu is placed
				// underneatht the menubutton.
				// NB: For the time being we are not using the above fix.
				/*
				var target = action || expand;
				var bodyOffset = parseInt($('body').css('border-top-width'), 10) || 0;
				menu.css('top', target.height() + target.offset().top + bodyOffset);
				*/

				$(document).bind('click', function (event){
					$(this).unbind(event);
					menu.hide();
					wrapper.removeClass('aloha-ui-menubutton-pressed');
				});

				return false;
			});

		wrapper.append(buttonset || expand).append(menu);

		menu.append(makeNestedMenus(makeCloseHandler(menu), props.menu));

		menu.hide().menu({
			'select': onSelect
		});

		return wrapper;
	};

	function makeNestedMenus(parentCloseHandler, menu){
		var elems = [];
		$.each(menu, function (_, item) {
			var elem = $('<li>');
			elem.append($('<a>', {'href': 'javascript:void 0', 'html': Utils.makeButtonLabelWithIcon(item)}));
			if (item.click) {
				elem.data('aloha-ui-menubutton-select', function (){
					parentCloseHandler();
					item.click();
				});
			}
			if (item.menu) {
				var nestedMenu = $('<ul>').appendTo(elem);
				nestedMenu.append(
					makeNestedMenus(makeCloseHandler(nestedMenu, parentCloseHandler),
									item.menu));
			}
			elems.push(elem[0]);
		});
		return elems;
	}

	function makeCloseHandler(menu, parentCloseHandler) {
		parentCloseHandler = parentCloseHandler || $.noop;
		return function (){
			// We must blur the parent menu otherwise it will remain in
			// focused state and not expand the next time it is hovered over
			// after the user has selected an item.
			menu.blur().hide();
			menu.parent().removeClass('aloha-ui-menubutton-pressed');
			parentCloseHandler();
		};
	}

	function onSelect(event, ui) {
		var clickHandler = ui.item.data('aloha-ui-menubutton-select');
		clickHandler && clickHandler(event, ui);
		// We use preventDefault() to keep a click on a menu item from
		// scrolling to the top of the page.
		event.preventDefault();
	}

	return MenuButton;
});

/* ribbon-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('ribbon/ribbon-plugin',[
    'jquery',
    'aloha/plugin',
    'ui/menuButton',
    'ui/utils',
    'jqueryui'
], function (
	$,
	Plugin,
	MenuButton,
	Utils
) {
	

	var ribbon = Plugin.create('ribbon', {

		init: function () {
			if (!this.settings.enable &&
				typeof this.settings.enable !== 'undefined') {
				return;
			}

			var that = this;
			this._visible = false;
            this._toolbar = $('<div>', {'class':
				'aloha-ribbon-toolbar ui-menubar ui-widget-header ui-helper-clearfix'});

			var fadeIn = Utils.makeButtonElement({'class': 'aloha-ribbon-in'})
				.button()
				.hide()
				.click(function () {
					that._toolbar.animate({
						'left': 0
					});
					$('body').animate({paddingTop: 30});
					fadeIn.hide();
				})
			    .appendTo(this._toolbar);

			var fadeOut = Utils.makeButtonElement({'class': 'aloha-ribbon-out'})
				.button()
				.click(function () {
					that._toolbar.animate({
						'left': -that._toolbar.outerWidth()
						        + fadeIn.outerWidth()
						        + 10
					});
					$('body').animate({paddingTop: 0});
					fadeIn.show();
				})
				.appendTo(this._toolbar);

			var wrapper = $('<div>', {'class': 'aloha aloha-ribbon'})
				.appendTo('body');

			this._icon = $('<div>').prependTo(this._toolbar);
			this.setIcon('');

			this._toolbar.appendTo(wrapper);

			$('body').css({
				position: 'relative',
				paddingTop: 30
			});
		},

		/**
		 * Sets the icon class for the ribbon icon
		 * @param {String} iconClass CSS class for the icon
		 */
		setIcon: function (iconClass) {
			if (!this._icon) {
				return;
			}
			this._icon.attr('class', 'aloha-ribbon-icon ' + iconClass);
		},

		addButton: function (props) {
			if (!this._toolbar) {
				return;
			}
			props = $.extend({}, props, {'siblingContainer': this._toolbar});
			this._toolbar.append(MenuButton.makeMenuButton(props));
		},

		/**
		 * Shows the Ribbon
		 */
		hide: function () {
			if (!this._toolbar) {
				return;
			}
			this._toolbar.hide();
			this._visible = false;
		},

		/**
		 * Hides the Ribbon
		 */
		show: function () {
			if (!this._toolbar) {
				return;
			}
			this._toolbar.show();
			this._visible = true;
		},

		/**
		 * Check whether the ribbon is visible right now
		 * @return true when the ribbon is visible, false when not
		 */
		isVisible: function () {
			return this._visible;
		}
	});

	return ribbon;
});

define('wai-lang/nls/i18n',{
	"root":  {
		"floatingmenu.tab.wai-lang": "Language annotation",
		"button.add-wai-lang-remove.tooltip": "Remove language annotation",
		"button.add-wai-lang.tooltip": "Add language annotation"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/*global define: true, require: true */
/*!
 * Aloha Editor
 * Author & Copyright (c) 2011 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed unter the terms of http://www.aloha-editor.com/license.html
 *
 * Language Repository
 * -------------------
 * Provides a set of language codes and images
 */
define('wai-lang/languages',['aloha', 'jquery', 'flag-icons/flag-icons-plugin', 'aloha/console', 'wai-lang/wai-lang-plugin'],
function(Aloha, jQuery, FlagIcons, console) {
	
	
	/**
	 * global Deferred Object
	 */
	var deferred = jQuery.Deferred();

	return new (Aloha.AbstractRepository.extend({

		/**
		 * Set of language codes
		 */
		languageCodes: [],
		
		/**
		 * Set default locale
		 */
		locale: 'de',
		
		/**
		 * Set default iso
		 */
		iso: 'iso639-1',

		/**
		 * Whether to show flags or not
		 */
		flags: false,

		_constructor: function () {
			this._super('wai-languages');
		},

		/**
		 * Initialize WAI Languages, load the language file and prepare the data.
		 */
		init: function () {
			
			var that = this;
			var waiLang = Aloha.require('wai-lang/wai-lang-plugin');
			var locale = Aloha.settings.locale;
			var iso = waiLang.iso639;

			if (locale !== 'de') {
				this.locale = 'en';
			}

			if (iso !== 'iso639-1') {
				this.iso = 'iso639-2';
			}

			this.flags = waiLang.flags;

			this.repositoryName = 'WaiLanguages';
			
			Aloha.require(['wai-lang/' + this.iso + '-' + this.locale], function (data) {
				that.storeLanguageCodes(data);
				deferred.resolve();
			});
			
		},

		markObject: function (obj, item) {
			//copied from wai-lang-plugin makeVisible to avoid a circular dependency
			// We do not need to add this class here since it already being
			// done in the wai-lang plugin
			// jQuery( obj ).addClass( 'aloha-wai-lang' );
		},

		/**
		 * This method will invoked if a error occurres while loading data via ajax
		 */
		errorHandler: function (text, error) {
			console.log("error", this, "Error while loading languages. " + text);
		},

		/**
		 * Stores the retrieved language code data in this object
		 */
		storeLanguageCodes: function (data) {
			var that = this;
			var waiLangPath = Aloha.getPluginUrl('wai-lang');

			// Transform loaded json into a set of repository documents
			jQuery.each(data, function (key, value) {
				var el = value;
				el.id = key;
				el.repositoryId = that.repositoryId;
				el.type = 'language';
				if (that.flags) {
					if (el.flag) {
						el.url =  FlagIcons.path + '/img/flags/' + el.flag + '.png';
					} else {
						el.url =  waiLangPath + '/img/button.png';
					}
				}
				// el.renditions.url = "img/flags/" + e.id + ".png";
				// el.renditions.kind.thumbnail = true;
				that.languageCodes.push(new Aloha.RepositoryDocument(el));
			});
		},
		
		/**
		 * Searches a repository for object items matching query if objectTypeFilter.
		 * If none found it returns null.
		 * Not supported: filter, orderBy, maxItems, skipcount, renditionFilter
		 */
		_searchInLanguageCodes: function (p, callback) {
			var query = new RegExp('^' + p.queryString, 'i'),
		    i,
		    d = [],
		    matchesName,
		    matchesType,
		    currentElement;

			for (i = 0; i < this.languageCodes.length; ++i) {
				currentElement = this.languageCodes[i];
				matchesName = (!p.queryString || currentElement.name.match(query));
				matchesType = (!p.objectTypeFilter || (!p.objectTypeFilter.length) || jQuery.inArray(currentElement.type, p.objectTypeFilter) > -1);
	
				if (matchesName && matchesType) {
					d.push(currentElement);
				}
			}
	
			callback.call(this, d);
		},

		/**
		 * Fetches the languageCodes if they are not already loaded and
		 * searches the collection with the given query.
		 */
		query: function (p, callback) {
			var that = this;
			
			deferred.done(function(){
				that._searchInLanguageCodes(p, callback);
			});
			
		},

		/**
		 * Get the repositoryItem with given id
		 * @param itemId {String} id of the repository item to fetch
		 * @param callback {function} callback function
		 * @return {Aloha.Repository.Object} item with given id
		 */
		getObjectById: function (itemId, callback) {
			var i, currentElement;

			for (i = 0; i < this.languageCodes.length; ++i) {
				currentElement = this.languageCodes[i];
				if (currentElement.id === itemId) {
					callback.call(this, [currentElement]);
					break;
				}
			}

		}
	}))();
});

/*!
 * Aloha Editor
 * Author & Copyright (c) 2011-2013 Gentics Software GmbH
 * aloha-sales@gentics.com
 * Licensed under the terms of http://www.aloha-editor.com/license.html
 */
define('wai-lang/wai-lang-plugin',[
	'aloha',
	'jquery',
	'PubSub',
	'aloha/plugin',
	'aloha/selection',
	'util/dom',
	'ui/ui',
	'ui/scopes',
	'ui/button',
	'ui/toggleButton',
	'ui/port-helper-attribute-field',
	'i18n!wai-lang/nls/i18n',
	'wai-lang/languages'
], function WaiLangPlugin(
	Aloha,
	$,
	PubSub,
	Plugin,
	Selection,
	Dom,
	Ui,
	Scopes,
	Button,
	ToggleButton,
	attributeField,
	i18n
) {
	

	var WAI_LANG_CLASS = 'aloha-wai-lang';

	/**
	 * Ui Attribute Field singleton.
	 *
	 * Needs to be defined in prepareUi(), because of a problem with the
	 * stateful initialization of jQuery UI autocomplete implementation for
	 * Aloha Editor.
	 *
	 * @type {AttributeField}
	 */
	var FIELD = null;

	/**
	 * UI Button to remove wai-lang
	 */
	var removeButton = null;

	/**
	 * Sets focus on the given field.
	 *
	 * @param {AttributeField} field
	 */
	function focusOn(field) {
		if (field) {
			field.foreground();
			field.focus();
		}
		// show the field and remove button
		FIELD.show();
		removeButton.show();
	}

	/**
	 * Maps the given value to boolean.
	 *
	 * We need to do this to support Gentics Content.Node which provides Aloha
	 * Editor configuration from serialized PHP.
	 *
	 * @return {boolean}
	 */
	function isTrue(value) {
		return (
			true === value
			|| 1 === value
			|| 'true' === value
			|| '1' === value
		);
	}

	/**
	 * Checks whether this element is a wai-lang wrapper element.
	 *
	 * @this {HTMLElement}
	 * @return {boolean} True if the given markup denotes wai-lang wrapper.
	 */
	function filterForWaiLangMarkup() {
		var $elem = $(this);
		return $elem.hasClass(WAI_LANG_CLASS) || $elem.is('[lang]');
	}

	/**
	 * Looks for a wai-Lang wrapper DOM element within the the given range.
	 *
	 * @param {RangeObject} range
	 * @return {HTMLElement|null} Wai-lang wrapper node; null otherwise.
	 */
	function findWaiLangMarkup(range) {
		return (
			Aloha.activeEditable
				? range.findMarkup(filterForWaiLangMarkup,
				                   Aloha.activeEditable.obj)
				: null
		);
	}

	/**
	 * Wraps the contents at the current range in a wai-lang wrapper element.
	 *
	 * @param {RangeObject} range
	 */
	function addMarkup(range) {
		if (range.isCollapsed()) {
			Dom.extendToWord(range);
		}
		Dom.addMarkup(
			range,
			$('<span class="' + WAI_LANG_CLASS + '"></span>'),
			false
		);
		range.select();
	}

	/**
	 * Removes wai-lang wrapper on the markup at the given range.
	 *
	 * @param {RangeObject} range
	 */
	function removeMarkup(range) {
		var markup = findWaiLangMarkup(range);
		if (markup) {
			Dom.removeFromDOM(markup, range, true);
			range.select();
		}
	}

	/**
	 * Prepares the markup at the current range for language annotation.
	 */
	function prepareAnnotation() {
		var range = Selection.getRangeObject();

		// Because we don't want to add markup to an area that already contains
		// wai-lang markup.
		if (!findWaiLangMarkup(range)) {
			addMarkup(range);
		}

		focusOn(FIELD);
	}

	/**
	 * Toggles language annotation on the markup at the current range.
	 */
	function toggleAnnotation() {
		if (Aloha.activeEditable) {
			var range = Selection.getRangeObject();
			if (findWaiLangMarkup(range)) {
				removeMarkup(range);
			} else {
				addMarkup(range);
				focusOn(FIELD);
			}
		}
	}

	/**
	 * Mark the given element as a wai-lang wrapper, with a class and
	 * annotation.
	 *
	 * @param {HTMLElement} element
	 */
	function annotate(element) {
		var $element = $(element);
		$element.addClass(WAI_LANG_CLASS)
		        .attr('data-gentics-aloha-repository', 'wai-languages')
		        .attr('data-gentics-aloha-object-id', $element.attr('lang'));
	}

	/**
	 * Initialize the buttons:
	 * Places the Wai-Lang UI buttons into the floating menu.
	 *
	 * Initializes `FIELD`.
	 *
	 * @param {Plugin} plugin Wai-lang plugin instance
	 */
	function prepareUi(plugin) {
		FIELD = attributeField({
			name: 'wailangfield',
			width: 320,
			valueField: 'id',
			minChars: 1,
			scope: 'Aloha.continuoustext'
		});

		plugin._wailangButton = Ui.adopt('wailang', ToggleButton, {
			tooltip: i18n.t('button.add-wai-lang.tooltip'),
			icon: 'aloha-icon aloha-icon-wai-lang',
			scope: 'Aloha.continuoustext',
			click: toggleAnnotation
		});

		removeButton = Ui.adopt('removewailang', Button, {
			tooltip: i18n.t('button.add-wai-lang-remove.tooltip'),
			icon: 'aloha-icon aloha-icon-wai-lang-remove',
			scope: 'Aloha.continuoustext',
			click: function onButtonClick() {
				removeMarkup(Selection.getRangeObject());
			}
		});

		FIELD.setTemplate(plugin.flags
				? '<div class="aloha-wai-lang-img-item">' +
				  '<img class="aloha-wai-lang-img" src="{url}" />' +
				  '<div class="aloha-wai-lang-label-item">{name} ({id})</div>' +
				  '</div>'
				: '<div class="aloha-wai-lang-img-item">' +
				  '<div class="aloha-wai-lang-label-item">{name} ({id})</div>' +
				  '</div>'
			);

		FIELD.setObjectTypeFilter(plugin.objectTypeFilter);
	}

	/**
	 * Create a unique id prefixed with the specified prefix.
	 *
	 * @param {string} prefix
	 * @return {string} Unique identifier string
	 */
	var uniqueId = (function (prefix) {
		var idCounter = 0;
		return function uniqueId() {
			return prefix + '-' + (++idCounter);
		};
	}());

	/**
	 * Return the unique id of the given wai-lang plugin instance.
	 * For use with the caching editable configurations.
	 *
	 * @param {Plugin} plugin Wai-lang plugin instance
	 * @return {string} Unique id of plugin instance
	 */
	function getPluginInstanceId(plugin) {
		return plugin._instanceId;
	}

	/**
	 * Cache of editable configuration for quicker lookup.
	 *
	 * Note that each configuration is the product of the per-editable
	 * configuration and the plugin configuration, and therefore the cache key
	 * for each configuration entry must be a function of both their
	 * identifiers.
	 *
	 * @type {string, object}
	 */
	var configurations = {};

	/**
	 * Get this plugin's configuration for the given editable.
	 *
	 * @param {Editable} editable
	 * @param {Plugin} plugin A wai-lang plugin instance
	 * @return {object} configuration
	 */
	function getConfig(editable, plugin) {
		var key = editable.getId() + ':' + getPluginInstanceId(plugin);
		if (!configurations[key]) {
			configurations[key] = plugin.getEditableConfig(editable.obj);
		}
		return configurations[key];
	}

	/**
	 * Registers event handlers for the given plugin instance.
	 *
	 * Assumes `FIELD` is initialized.
	 *
	 * @param {Plugin} plugin Instance of wai-lang plugin.
	 */
	function subscribeEvents(plugin) {
		PubSub.sub('aloha.editable.activated',
			function onEditableActivated(msg) {
				var config = getConfig(msg.data.editable, plugin);
				if ($.inArray('span', config) > -1) {
					plugin._wailangButton.show();
				} else {
					plugin._wailangButton.hide();
				}
			});

		Aloha.bind('aloha-selection-changed',
			function onSelectionChanged($event, range) {
				var markup = findWaiLangMarkup(range);
				if (markup) {
					plugin._wailangButton.setState(true);
					FIELD.setTargetObject(markup, 'lang');

					// show the field and remove button
					FIELD.show();
					removeButton.show();

					Scopes.enterScope(plugin.name, 'wai-lang');
				} else {
					plugin._wailangButton.setState(false);
					FIELD.setTargetObject(null);

					// hide the field and remove button
					FIELD.hide();
					removeButton.hide();

					Scopes.leaveScope(plugin.name, 'wai-lang', true);
				}
			});

		FIELD.addListener('blur', function onFieldBlur() {
			// @TODO Validate value to not permit values outside the set of
			//       configured language codes.
			if (!this.getValue()) {
				removeMarkup(Selection.getRangeObject());
			}
		});

		Aloha.bind('aloha-editable-created',
			function onEditableCreated($event, editable) {
				editable.obj.bind('keydown', plugin.hotKey.insertAnnotation,
					function () {
						prepareAnnotation();

						// Because on a MAC Safari, cursor would otherwise
						// automatically jump to location bar.  We therefore
						// prevent bubbling, so that the editor must hit ESC and
						// then META+I to manually do that.
						return false;
					});

				editable.obj.find('span[lang]').each(function () {
					annotate(this);
				});
			});
	}

	return Plugin.create('wai-lang', {

		/**
		 * Default configuration, allows spans everywhere.
		 *
		 * @type {Array.<string>}
		 */
		config: ['span'],

		/**
		 * Define the exact standard of language codes to use (possible values
		 * are 'iso639-1' and 'iso639-2', default is 'iso639-1')
		 *
		 * @type {string}
		 */
		iso639: 'iso639-1',

		/**
		 * Whether or not to show flag icons.
		 *
		 * @type {boolean}
		 */
		flags: false,

		/**
		 * The object types to be used for for annotations.
		 *
		 * @type {Array.<string>}
		 */
		objectTypeFilter: ['language'],

		/**
		 * HotKeys used for special actions.
		 *
		 * @type {object<string, string>}
		 */
		hotKey: {
			insertAnnotation: i18n.t('insertAnnotation', 'ctrl+shift+l')
		},

		init: function init() {
			this._instanceId = uniqueId('wai-lang');
			if (this.settings.objectTypeFilter) {
				this.objectTypeFilter = this.settings.objectTypeFilter;
			}
			if (this.settings.hotKey) {
				$.extend(true, this.hotKey, this.settings.hotKey);
			}
			if (this.settings.iso639) {
				this.iso639 = this.settings.iso639;
			}
			this.flags = isTrue(this.settings.flags);
			prepareUi(this);
			subscribeEvents(this);
		},

		/**
		 * Toggle language annotation on the current selection, or if collapsed,
		 * the current word.
		 */
		formatLanguageSpan: toggleAnnotation,

		/**
		 * Makes the given editable DOM element clean for export.  Find all
		 * elements with lang attributes and remove the attribute.
		 *
		 * It also removes data attributes attached by the repository manager.
		 * It adds a xml:lang attribute with the value of the lang attribute.
		 *
		 * @param {jQuery<HTMLElement>} $element jQuery unit set containing
		 *                                       element to clean up.
		 */
		makeClean: function makeClean($element) {
			$element.find('span[lang]').each(function onEachLangSpan() {
				var $span = $(this);
				$span.removeClass(WAI_LANG_CLASS)
				     .removeAttr('data-gentics-aloha-repository')
				     .removeAttr('data-gentics-aloha-object-id')
				     .attr('xml:lang', $span.attr('lang'));
			});
		}

	});
});

define('headerids/nls/i18n',{
	"root":  {
		"headerids.label.target": "Target",
		"headerids.button.reset": "Reset",
		"headerids.button.set": "Set",
		"internal_hyperlink": "Internal Hyperlink"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* headerids-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('headerids/headerids-plugin',[
	'aloha',
	'jquery',
	'PubSub',
	'aloha/plugin',
	'util/html',
	'i18n!headerids/nls/i18n'
], function (
	Aloha,
	jQuery,
	PubSub,
	Plugin,
	html,
	i18n
) {
	

	var $ = jQuery;

	// namespace prefix for this plugin
    var ns = 'aloha-headerids';
    
    
    // ------------------------------------------------------------------------
    // Local (helper) functions
    // ------------------------------------------------------------------------
    
    // Creates a selector string with this component's namepsace prefixed the each classname
    function nsSel () {
        var strBldr = [], prx = ns;
        $.each(arguments, function () { strBldr.push('.' + (this == '' ? prx : prx + '-' + this)); });
        return jQuery.trim(strBldr.join(' '));
    }
    
    // Creates string with this component's namepsace prefixed the each classname
    function nsClass () {
        var strBldr = [], prx = ns;
        $.each(arguments, function () { strBldr.push(this == '' ? prx : prx + '-' + this); });
        return jQuery.trim(strBldr.join(' '));
    }

	/**
	 * Returns a jQuery collection of all heading elements in the given editable
	 * which are safe to have their ids automatically set.
	 *
	 * Do not include heading elements which are annotated with the class
	 * "aloha-customized" because these have had their ids manually set.
	 *
	 * Do not include heading elements which are Aloha editables or blocks because
	 * implementations often use the ids of these elements to track them in the
	 * DOM.
	 *
	 * @param {jQuery.<HTMLElement>} $editable
	 * @return {jQuery.<HTMLHeadingElement>} A jQuery container with a
	 *                                       collection of zero or more heading
	 *                                       elements.
	 */
	function getHeadingElements($editable) {
		return (
			$editable.find('h1,h2,h3,h4,h5,h6')
			        .not('.aloha-customized,.aloha-editable,.aloha-block')
		);
	}
	
	/**
	 * Check in the entire document if has a element with the same ID, and try 
	 * to get an unique ID
	 * 
	 * @param {String} proposedID ID to check, uses this as base and concatenate
	 *                 a dangling with a number
	 * 
	 * @return {String}
	 */
	function checkDuplicatedID(proposedID){
		var baseID = proposedID, i = 1;
		
		while($('#' + proposedID).length > 0){
			proposedID = baseID + '_' + ( ++i ).toString();
		}
		
		return proposedID;
	}
	
	return Plugin.create('headerids', {
		_constructor: function(){
			this._super('headerids');
		},
		
		config: ['true'],
				
		/**
		 * Initialize the plugin
		 */
		init: function () {
			var plugin = this,
				invokeCheck = function(msg){
					var $editable = msg.data.obj || msg.data.editable.obj;
					plugin.check($editable);
				}
			;
			
			
			
			PubSub.sub('aloha.editable.created', invokeCheck);

			// mark active Editable with a css class
			PubSub.sub('aloha.editable.activated', invokeCheck);
			PubSub.sub('aloha.editable.deactivated', invokeCheck);

			Aloha.bind('aloha-plugins-loaded', function (ev) {
				plugin.initSidebar(Aloha.Sidebar.right);
			});
		},

		/**
		 * Automatically sets the id attribute of heading elements in the given
		 * editable element wherever it is safe to do so.
		 *
		 * @TODO: This function should be removed from being a plugin method and
		 *        made a local closure function.
		 *
		 * @TODO: Rename to setHeadingIds()
		 *
		 * @param {jQuery.<HTMLElement>} $editable
		 */
		check: function($editable) {
			var plugin = this;
			if($.inArray('true', plugin.getEditableConfig($editable)) > -1) {
				getHeadingElements($editable).each(function (i, heading) {
					plugin.processH(heading);
				});
			}
		},

		/**
		 * Automatically sets the id of the given heading element to a sanitized
		 * version of the element's content text string, only if the heading has
		 * not a ID already set.
		 *
		 * @TODO: Rename to setHeadingId()
		 *
		 * @TODO: Make this function a local closure function rather than a
		 *        plugin method.
		 *
		 * @param {HTMLHeadingElement} heading One of the six HTML heading
		 *                                     elements.
		 * @returns {String} ID attribute of the Heading element
		 */
		processH: function (heading) {
			var headingId = heading.id;
			if (!headingId) {
				// We prefix the Id with "heading_" to not run accross
				// problems with the Id starting with a number which
				// would be disallowedÜ in HTML.
				var $heading = $(heading);
				headingId = "heading_" + this.sanitize($heading.text());

				headingId = checkDuplicatedID(headingId);

				$heading.attr('id', headingId);
			}
			return headingId;
		},

		/**
		 * Tranforms the given string into a ideal HTMLElement id attribute
		 * string by replacing non-alphanumeric characters with an underscore.
		 *
		 * @TODO: This method should be removed from being a plugin method and
		 *        made into a local closure function.
		 *
		 * @TODO: The regular expression should be defined once (as a constant)
		 *        outside of the scope of this function.
		 *
		 * @param {String} str
		 * @return {String} Santized copy of `str`.
		 */
		sanitize: function(str) {
			return html.trimWhitespaceCharacters(str)
				.replace(/[^a-z0-9]+/gi, '_');
		},

		//ns = headerids
		initSidebar: function(sidebar) {
			var thisPlugin = this;
			thisPlugin.sidebar = sidebar;
			sidebar.addPanel({
				id: nsClass('sidebar-panel'),
				title: i18n.t('internal_hyperlink'),
				content: '',
				expanded: true,
				activeOn: 'h1, h2, h3, h4, h5, h6',

				onInit: function () {
					var thisSidebarPanel = this,
					    content = this.setContent('<label class="' + nsClass('label') + '" for="' + nsClass('input') + '">' + i18n.t('headerids.label.target') + '</label><input id="' + nsClass('input') + '" class="' + nsClass('input') + '" type="text" name="value"/> <button class="' + nsClass('reset-button') + '">' + i18n.t('headerids.button.reset') + '</button><button class="' + nsClass('set-button') + '">' + i18n.t('headerids.button.set') + '</button>').content;

					content.find(nsSel('set-button')).click(function () {
						var content = thisSidebarPanel.content;
						jQuery(thisSidebarPanel.effective)
							.attr('id', jQuery(nsSel('input')).val())
							.addClass('aloha-customized');
					});

					content.find(nsSel('reset-button')).click(function () {
						var content = thisSidebarPanel.content;
						thisPlugin.processH(thisSidebarPanel.effective);
						jQuery(thisSidebarPanel.effective)
							.removeClass('aloha-customized');
						thisSidebarPanel.content.find(nsSel('input')).val(thisSidebarPanel.effective.attr('id'));
					});
				},

				onActivate: function (effective) {
					this.effective = effective;
					this.content.find(nsSel('input')).val(thisPlugin.processH(effective[0]));
				}
			});

			sidebar.show();
		},
		
		/**
		 * Make the given jQuery object (representing an editable) clean for saving
		 * If the headerids plugin is active it checks the current editable and 
		 * generates ids for headers.
		 * 
		 * @param {jQuery} obj jQuery object to make clean
		 */
		makeClean: function (obj) {
			this.check(obj);
		}
		
	});
});

define('metaview/nls/i18n',{
	"root":  {
		"button.switch-metaview.tooltip": "Switch between meta and normal view"
	},
		"ca": true,
		"de": true,
		"mk": true,
		"pt-br": true,
		"ru": true,
		"uk": true,
		"zh-hans": true
});

/* metaview-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
 * Copyright (c) 2010-2013 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php
 *
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
define('metaview/metaview-plugin',[
	'jquery',
	'aloha/core',
	'aloha/plugin',
	'ui/ui',
	'ui/toggleButton',
	'i18n!metaview/nls/i18n',
	'i18n!aloha/nls/i18n'
], function (
	$,
	Aloha,
	Plugin,
	Ui,
	ToggleButton,
	i18n
) {
	

	/**
	 * Map of editable ids against their metaview states.
	 *
	 * @type {object<string, boolean>}
	 */
	var states = {};

	/**
	 * Checks whether the given editable is has metaview enabled.
	 *
	 * @param {jQuery<HTMLElement>} $editable
	 * @return {boolean} True if editable is in metaview-enabled state.
	 */
	function enabled($editable) {
		return states[$editable[0].id];
	}

	/**
	 * Enables metaview for the given editable, and update UI appropriately.
	 *
	 * @param {jQuery<HTMLElement>} $editable
	 * @param {ToggleButton} button
	 */
	function enable($editable, button) {
		$editable.addClass('aloha-metaview');
		button.setState(true);
		states[$editable[0].id] = true;
	}

	/**
	 * Disables metaview for the given editable, and update UI appropriately.
	 *
	 * @param {jQuery<HTMLElement>} $editable
	 * @param {ToggleButton} button
	 */
	function disable($editable, button) {
		$editable.removeClass('aloha-metaview');
		button.setState(false);
		states[$editable[0].id] = false;
	}

	/**
	 * Toggles metaview on the given editable.
	 *
	 * @param {jQuery<HTMLElement>} $editable
	 * @param {ToggleButton} button
	 */
	function toggle($editable, button) {
		if (enabled($editable)) {
			disable($editable, button);
		} else {
			enable($editable, button);
		}
	}

	/**
	 * Singleton meta view toggle button to be used across all meta view plugin
	 * instances.
	 *
	 * @type {ToggleButton}
	 */
	var METAVIEW_TOGGLE_BUTTON = Ui.adopt('toggleMetaView', ToggleButton, {
		tooltip : i18n.t('button.switch-metaview.tooltip'),
		icon: 'aloha-icon aloha-icon-metaview',
		scope: 'Aloha.continuoustext',
		click: function () {
			toggle(Aloha.activeEditable.obj, this);
		}
	});

	/**
	 * Gets the configuration for the given editable/plugin combination.
	 *
	 * TODO: Memoize
	 *
	 * @param {Plugin} plugin Instance of metaview plugin
	 * @param {Editable} editable
	 */
	function getConfiguration(plugin, editable) {
		return plugin.getEditableConfig(editable);
	}

	/**
	 * Checks whether or not meta-view is automatically enable on an editable.
	 *
	 * @param {object} The plugin/editable configuration.
	 * @return {boolean} True if activated.
	 */
	function isAutomaticallyEnabled(config) {
		return (
			$.type(config) === 'array' && $.inArray('enabled', config) !== -1
		);
	}

	/**
	 * Checks whether or not meta-view is activated for an editable.
	 *
	 * @param {object} The plugin/editable configuration.
	 * @return {boolean} True if activated.
	 */
	function isPluginActivated(config) {
		return (
			$.type(config) === 'array' && $.inArray('metaview', config) !== -1
		);
	}

    return Plugin.create('metaview', {
		config: ['metaview'],
		init: function () {
			var plugin = this;

			Aloha.bind('aloha-editable-activated', function () {
				var config = getConfiguration(plugin, Aloha.activeEditable.obj);

				if (isAutomaticallyEnabled(config)) {
					METAVIEW_TOGGLE_BUTTON.show(true);
					enable(Aloha.activeEditable.obj, METAVIEW_TOGGLE_BUTTON);
				} else if (isPluginActivated(config)) {
					METAVIEW_TOGGLE_BUTTON.show(true);
					if (enabled(Aloha.activeEditable.obj)) {
						enable(
							Aloha.activeEditable.obj,
							METAVIEW_TOGGLE_BUTTON
						);
					} else {
						disable(
							Aloha.activeEditable.obj,
							METAVIEW_TOGGLE_BUTTON
						);
					}
				} else {
					METAVIEW_TOGGLE_BUTTON.show(false);
				}
			});

			Aloha.bind('aloha-editable-deactivated', function () {
				Aloha.activeEditable.obj.removeClass('aloha-metaview');
			});
		}
	});
});

/* listenforcer-plugin.js is part of Aloha Editor project http://aloha-editor.org
 *
 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://aloha-editor.org/contribution.php 
 * 
 * Aloha Editor is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * Aloha Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 * As an additional permission to the GNU GPL version 2, you may distribute
 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 * source code without the copy of the GNU GPL normally required,
 * provided you include this license notice and a URL through which
 * recipients can access the Corresponding Source.
 */
/* Aloha List Enforcer
 * -------------------
 * Enforces a one top-level list per editable policy ;-)
 * This plugin will register editables and enforce lists in them. List enforced
 * editables will be permitted to contain, exactly one top-level element which
 * must be a (OL or a UL) list element.
 */
define('listenforcer/listenforcer-plugin', [
	'aloha',
	'jquery',
	'aloha/plugin',
	'aloha/console'
], function( Aloha, jQuery, Plugin, console ) {
	

	/**
	 * An internal array of all editables inwhich to enforce lists.
	 *
	 * @private
	 */
	var listEnforcedEditables = [];

	/**
	 * Given an editable which has been configured to enforce lists,
	 * ensures that there is exactly one top-level list in the editable.
	 * If there are no lists, one will be added, using the
	 * placeHolderListString. If there is more than one list, they will be
	 * merged into the first list.
	 * If there is any other content in the editable it will be removed.
	 *
	 * @private
	 * @param {jQuery} $editable
	 * @param {String} placeHolderListString
	 */
	function enforce ( $editable, placeHolderListString ) {
		// Check if this editable is configured to enforce lists
		if ( jQuery.inArray( $editable[ 0 ], listEnforcedEditables ) === -1 ) {
			return;
		}

		// Remove all temporary <br>s in the editable, which we may have
		// inserted when we activated this editable and found it empty. These
		// <br>s are needed to make the otherwise empty <li> visible (in IE).
		//
		// Note: We no longer insert  temporary <br>s with the "aloha-end-br"
		// class on them.  But we should leave this removal here to ensure that
		// content that was generated with legacy Aloha Editor is cleaned
		// correctly.
		$editable.find('.aloha-end-br').remove();

		// Check for the presence of at least one non-empty list. We consider
		// a list to be not empty if it has atleast one item whose contents are
		// more than a single (propping) <br> tag.

		var hasList = false;

		$editable.find( 'li' ).each( function(){
			// nb: jQuery text() method returns the text contents of the
			// element without <br>s being rendered.
			if ( jQuery.trim( jQuery( this ).text() ) !== '' ) {
				hasList = true;
				// Stop looking for lists as soon as we find our first
				// non-empty list
				return false;
			}
		} );

		// If we found no non-empty list, then we add our empty dummy list that
		// the user can work with.
		if ( !hasList ) {
			$editable.html( placeHolderListString );
		}

		// Concatinate all top-level lists into the first, before, thereby
		// merging all top-level lists into one.
		var $lists = $editable.find( '>ul,>ol' ),
		    j = $lists.length,
		    i;
		if ( j > 1 ) {
			var $firstList = jQuery( $lists[0] );
			for ( i = 1; i < j; ++i ) {
				$firstList.append( jQuery( $lists[ i ] ).find( '>li' ) );
				jQuery( $lists[ i ] ).remove();
			}
		}

		// Remove all top-level elements which are not lists
		$editable.find( '>*:not(ul,ol)' ).remove();
	};

	return Plugin.create( 'listenforcer', {

		_constructor: function() {
			this._super( 'listenforcer' );
		},

		/**
		 * Initializes the listenforcer plugin:
		 * We read the aloha configuration settings to determine which
		 * editables are to have list enforced in them.
		 * We bind handlers to 3 events (aloha-editable-activated,
		 * aloha-editable-deactivated, and aloha-smart-content-changed) on
		 * which we will process the current active editable and enfore lists
		 * in it.
		 */
		init: function() {
			var that = this,
			    elemsToEnforce = this.settings.editables || [],
				elemToEnforce,
				i,
				j = elemsToEnforce.length;

			// Register all editables that are to enforce lists.
			// The following types of items can be used as jQuery selectors:
			// String, DOMElement, and jQuery
			for ( i = 0; i < j; i++ ) {
				elemToEnforce = elemsToEnforce[ i ];
				if ( typeof elemToEnforce === 'string' ||
						elemToEnforce.nodeName ||
							elemToEnforce instanceof jQuery ) {
					jQuery(elemToEnforce).each(function(){
						that.addEditableToEnforcementList( this );
					});
				} else {
					console.warn(
						'Aloha List Enforcer Plugin',
						'Object "' + elemToEnforce.toString() + '" can not ' +
						'be used as a jQuery selector with which to register' +
						' an editable to be list enforced.'
					);
				}
			}

			Aloha.bind('aloha-editable-activated', function ($event, params) {
				enforce(params.editable.obj,
					'<ul><li><br /></li></ul>');
			});

			Aloha.bind('aloha-editable-deactivated', function ($event, params) {
				enforce(params.editable.obj, '');
			});

			Aloha.bind('aloha-smart-content-changed', function ($event, params) {
				//We only want to do this is if the editable is actually active
			 	if (Aloha.activeEditable && Aloha.activeEditable.isActive === true) {
			 		enforce( params.editable.obj,
			 			'<ul><li><br /></li></ul>');
			 	}
			});
		},

		/**
		 * Registers the given editable to be list-enforced.
		 *
		 * @param {DOMElement} editable
		 */
		addEditableToEnforcementList: function( editable ) {
			if ( editable ) {
				listEnforcedEditables.push( editable );
			}
		}

	} );
} );
	if (Aloha._load) {
		Aloha._load(); // initialized in aloha.js
	}
}()); // bye Aloha Editor ;-)
;